From 5556012c22547f47353717b88768c2c4cf9bc5e3 Mon Sep 17 00:00:00 2001 From: Tom Hinton Date: Mon, 18 Jul 2022 10:24:34 +0100 Subject: [PATCH] Better logging behaviour; read only switch; transmit prefs by hack --- .../larkery/simpleorgsync/PrefsActivity.java | 20 +- .../simpleorgsync/cal/CalSyncAdapter.java | 179 +++++++------ .../simpleorgsync/cal/parse/Heading.java | 7 +- .../simpleorgsync/cal/parse/Timestamp.java | 1 - .../simpleorgsync/con/ConSyncAdapter.java | 19 +- .../simpleorgsync/con/ContactsJson.java | 7 - .../simpleorgsync/lib/Application.java | 50 +++- .../simpleorgsync/lib/FileJobService.java | 32 +-- .../larkery/simpleorgsync/lib/FileUtils.java | 243 ------------------ .../larkery/simpleorgsync/lib/JSONPrefs.java | 57 ++++ .../com/larkery/simpleorgsync/lib/Log.java | 29 +++ app/src/main/res/xml/prefs.xml | 5 + 12 files changed, 267 insertions(+), 382 deletions(-) delete mode 100644 app/src/main/java/com/larkery/simpleorgsync/lib/FileUtils.java create mode 100644 app/src/main/java/com/larkery/simpleorgsync/lib/JSONPrefs.java create mode 100644 app/src/main/java/com/larkery/simpleorgsync/lib/Log.java diff --git a/app/src/main/java/com/larkery/simpleorgsync/PrefsActivity.java b/app/src/main/java/com/larkery/simpleorgsync/PrefsActivity.java index 9571c7d..d931630 100644 --- a/app/src/main/java/com/larkery/simpleorgsync/PrefsActivity.java +++ b/app/src/main/java/com/larkery/simpleorgsync/PrefsActivity.java @@ -6,25 +6,20 @@ import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; import android.support.annotation.Nullable; -import android.util.Log; import android.widget.Toast; import com.larkery.simpleorgsync.lib.Application; -import com.larkery.simpleorgsync.lib.FileUtils; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.text.DateFormat; + +import java.lang.reflect.Field; import java.text.SimpleDateFormat; import java.util.Date; @@ -37,8 +32,6 @@ protected void onCreate(Bundle savedInstanceState) { getFragmentManager() .beginTransaction() .replace(android.R.id.content, frag).commit(); - - } @Override @@ -149,7 +142,7 @@ public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) { final Application a = (Application) OrgPreferenceFragment.this.getActivity().getApplication(); - a.requestSync(); + a.requestSync("user pressed button in prefs"); return true; } } @@ -174,7 +167,6 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); String path = null; String pref = null; - Log.i("ORGSYNC/PREF", "got uri " + data.getData()); switch (requestCode) { case SELECT_AGENDA_FILE: getContext().getContentResolver().takePersistableUriPermission( diff --git a/app/src/main/java/com/larkery/simpleorgsync/cal/CalSyncAdapter.java b/app/src/main/java/com/larkery/simpleorgsync/cal/CalSyncAdapter.java index 9ca9fff..92d4fab 100644 --- a/app/src/main/java/com/larkery/simpleorgsync/cal/CalSyncAdapter.java +++ b/app/src/main/java/com/larkery/simpleorgsync/cal/CalSyncAdapter.java @@ -1,6 +1,7 @@ package com.larkery.simpleorgsync.cal; import android.accounts.Account; +import android.accounts.AccountManager; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; @@ -15,33 +16,28 @@ import android.os.Build; import android.os.Bundle; import android.os.RemoteException; -import android.os.StrictMode; import android.preference.PreferenceManager; import android.provider.CalendarContract; import android.provider.CalendarContract.Calendars; import android.support.annotation.RequiresApi; import android.support.v4.provider.DocumentFile; import android.text.TextUtils; -import android.util.Log; -import android.widget.Toast; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; +import com.google.gson.JsonObject; import com.larkery.simpleorgsync.cal.parse.Edit; import com.larkery.simpleorgsync.cal.parse.Heading; import com.larkery.simpleorgsync.cal.parse.OrgParser; import com.larkery.simpleorgsync.cal.parse.Timestamp; -import com.larkery.simpleorgsync.lib.Application; -import com.larkery.simpleorgsync.lib.FileUtils; +import com.larkery.simpleorgsync.lib.JSONPrefs; +import com.larkery.simpleorgsync.lib.Log; -import org.w3c.dom.Document; +import org.json.JSONException; +import org.json.JSONObject; import java.io.BufferedReader; -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; @@ -56,12 +52,13 @@ import java.util.TimeZone; public class CalSyncAdapter extends AbstractThreadedSyncAdapter { - private static final String TAG = Application.TAG + "/CALSYNC"; + private static final String TAG = "CalSyncAdapter"; private Timestamp.Type ttype = Timestamp.Type.ACTIVE; private TimeZone calTimezone = TimeZone.getDefault(); public CalSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); + Log.i(TAG, "Constructed"); } public static DocumentFile docFile(final Context con, final String uri) { @@ -73,12 +70,16 @@ public static DocumentFile docFile(final Context con, final String uri) { } } - public static void collectOrgFiles(DocumentFile rootFile, final Set output) { + public static void collectOrgFiles(DocumentFile rootFile, + final Set output, + boolean ignoreConflicts) { if (rootFile.isDirectory()) { for (DocumentFile child : rootFile.listFiles()) { - collectOrgFiles(child, output); + collectOrgFiles(child, output, ignoreConflicts); } - } else if (rootFile.isFile() && rootFile.getName().endsWith(".org")) { + } else if (rootFile.isFile() && rootFile.getName().endsWith(".org") + && (ignoreConflicts || !rootFile.getName().contains(".sync-conflict")) + ) { output.add(rootFile); } } @@ -86,11 +87,11 @@ public static void collectOrgFiles(DocumentFile rootFile, final Set distinctAgendaFiles = new HashSet<>(); final DocumentFile agendaRootFile = docFile(getContext(), agendaRoot); - collectOrgFiles(agendaRootFile, distinctAgendaFiles); + collectOrgFiles(agendaRootFile, distinctAgendaFiles, + prefs.getBoolean("ignore_syncthing_conflicts", true)); final Map fileContents = new HashMap<>(); final ListMultimap headingsByFile = ArrayListMultimap.create(); final ListMultimap headingsByCategory = ArrayListMultimap.create(); @@ -117,7 +119,8 @@ public void onPerformSync(Account account, Bundle extras, String authority, // load it all for (final DocumentFile df : distinctAgendaFiles) { try { - Log.i(TAG, "Parsing" + df); + final long now = System.currentTimeMillis(); + Log.i(TAG, "Parsing " + df.getName()); final StringBuffer sb = readFile(df.getUri()); fileContents.put(df, sb); String category = df.getName(); @@ -126,9 +129,11 @@ public void onPerformSync(Account account, Bundle extras, String authority, } final List headings = OrgParser.parse(sb, TimeZone.getTimeZone("Europe/London"), category); headingsByFile.putAll(df, headings); - Log.i(TAG, df + " contains " + headings.size() + " headings"); + final long delta = System.currentTimeMillis() - now; + Log.i(TAG, df.getName() + " contains " + headings.size() + " headings" + + ", parsed in " + delta +"ms"); } catch (IOException ex) { - Log.e(TAG, "Reading " + df, ex); + Log.e(TAG, "Reading " + df.getName(), ex); } } @@ -146,9 +151,12 @@ public void onPerformSync(Account account, Bundle extras, String authority, } } + boolean readOnly = prefs.getBoolean("read_only", false); + Log.i(TAG, readOnly ? "Read-only mode" : "Read-write mode"); final Map calendarIDs; + try { - calendarIDs = createCalendars(account, calendarsURI, categories, provider); + calendarIDs = createCalendars(account, calendarsURI, categories, provider, readOnly); } catch (RemoteException e) { Log.e(TAG, "Unable to create calendars", e); return; @@ -173,7 +181,7 @@ public void onPerformSync(Account account, Bundle extras, String authority, syncCalendar(eventsURI, calendarID, headingsByCategory.get(category), provider, - operations, newHeadings); + operations, newHeadings, readOnly); // I think at this point we need to put the new headings into the files @@ -203,51 +211,55 @@ public void onPerformSync(Account account, Bundle extras, String authority, } // now write each file, one at a time - for (final DocumentFile file : distinctAgendaFiles) { - final List fileHeadings = headingsByFile.get(file); - final List edits = new ArrayList<>(); - final StringBuffer appends = new StringBuffer(); - for (final Heading h : fileHeadings) { - try { - if (h.exists()) { - int nedits = edits.size(); - h.edit(edits); - nedits = edits.size() - nedits; - if (nedits > 0) Log.i(TAG, h.getHeading() + ": " + nedits + " edits"); - if (nedits > 0) updatedInOrg++; - } else if (!h.exists()) { - Log.i(TAG, "Appending " + h); - h.append(appends); - updatedInOrg++; + if (!readOnly) { + for (final DocumentFile file : distinctAgendaFiles) { + final List fileHeadings = headingsByFile.get(file); + final List edits = new ArrayList<>(); + final StringBuffer appends = new StringBuffer(); + for (final Heading h : fileHeadings) { + try { + if (h.exists()) { + int nedits = edits.size(); + h.edit(edits); + nedits = edits.size() - nedits; + if (nedits > 0) Log.i(TAG, h.getHeading() + ": " + nedits + " edits"); + if (nedits > 0) updatedInOrg++; + } else if (!h.exists()) { + Log.i(TAG, "Appending " + h); + h.append(appends); + updatedInOrg++; + } + } catch (Exception e) { + Log.e(TAG, "Error with heading" + h, e); } - } catch (Exception e) { - Log.e(TAG, "Error with heading" + h, e); } - } - Log.i(TAG, edits.size() + " edits to " + file + ", and " + appends.length() + " to append"); - if (appends.length() == 0 && edits.isEmpty()) continue; + Log.i(TAG, edits.size() + " edits to " + file.getName() + ", and " + appends.length() + " to append"); + if (appends.length() == 0 && edits.isEmpty()) continue; - // now perform the edits and modify/replace the file + // now perform the edits and modify/replace the file - final StringBuffer newContents = Edit.apply(edits, fileContents.get(file)); - if (appends.length() > 0) { - newContents.append("\n"); - newContents.append(appends); - } - try { - final OutputStreamWriter w = new OutputStreamWriter( - getContext().getContentResolver().openOutputStream( - file.getUri() - ), - StandardCharsets.UTF_8); + final StringBuffer newContents = Edit.apply(edits, fileContents.get(file)); + if (appends.length() > 0) { + newContents.append("\n"); + newContents.append(appends); + } try { - w.write(newContents.toString()); - } finally { - w.close(); + final OutputStreamWriter w = new OutputStreamWriter( + getContext().getContentResolver().openOutputStream( + file.getUri() + ), + StandardCharsets.UTF_8); + try { + w.write(newContents.toString()); + } finally { + w.close(); + } + } catch (IOException e) { + Log.e(TAG, "Error updating " + file.getName(), e); } - } catch (IOException e) { - Log.e(TAG, "Error updating " + file, e); } + } else { + Log.i(TAG, "Skip any writes, because we are in read-only mode"); } try { @@ -261,10 +273,17 @@ public void onPerformSync(Account account, Bundle extras, String authority, } } + private SharedPreferences getMPSharedPreferences(Context context) { + return context.getSharedPreferences( + context.getPackageName() + "_preferences", + Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS); + } + private Map createCalendars(Account account, Uri calendarsURI, Set categories, - ContentProviderClient provider) throws RemoteException { + ContentProviderClient provider, + boolean readOnly) throws RemoteException { final Map result = new HashMap<>(); try (final Cursor knownCalendars = provider.query( @@ -295,6 +314,16 @@ private Map createCalendars(Account account, TextUtils.join(", ", extraCalendars) + ")", null ); + { + final ContentValues readOnlyValues = new ContentValues(); + readOnlyValues.put(Calendars.CALENDAR_ACCESS_LEVEL, readOnly + ? Calendars.CAL_ACCESS_READ : Calendars.CAL_ACCESS_OWNER); + provider.update(calendarsURI, + readOnlyValues, + Calendars.ACCOUNT_NAME + "= ? AND "+ Calendars.ACCOUNT_TYPE + "= ?", + new String[] {account.type, account.name} + ); + } for (final String s : missingCategories) { final ContentValues values = new ContentValues(); @@ -307,9 +336,9 @@ private Map createCalendars(Account account, values.put(Calendars.CALENDAR_COLOR, s.hashCode()); values.put(Calendars.CALENDAR_DISPLAY_NAME, properCase(s)); values.put(Calendars.OWNER_ACCOUNT, account.name); - values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER); + values.put(Calendars.CALENDAR_ACCESS_LEVEL, readOnly + ? Calendars.CAL_ACCESS_READ : Calendars.CAL_ACCESS_OWNER); values.put(Calendars.CALENDAR_TIME_ZONE, TimeZone.getDefault().getID()); - values.put(Calendars.SYNC_EVENTS, 1); result.put(s, ContentUris.parseId(provider.insert(calendarsURI, values))); @@ -395,11 +424,12 @@ private void syncCalendar( List headings, ContentProviderClient provider, final List operations, - List newHeadings) { + List newHeadings, + boolean readOnly) { final Map orgIDs = new HashMap<>(); for (final Heading h : headings) { - orgIDs.put(h.ensureID(), h); + orgIDs.put(h.syncID(readOnly), h); } try (final Cursor query = provider.query(eventsUri, EventsProjection.PROJECTION, @@ -412,7 +442,7 @@ private void syncCalendar( if (orgID == null || orgID.isEmpty()) { Log.i(TAG, "New heading for " + query.getString(EventsProjection.TITLE.ordinal())); - createHeading(eventsUri, query, operations, newHeadings); + createHeading(eventsUri, query, operations, newHeadings, readOnly); } else { calIDs.add(orgID); final Heading heading = orgIDs.get(orgID); @@ -456,7 +486,7 @@ private void syncCalendar( for (final String id : Sets.difference(orgIDs.keySet(), calIDs)) { final Heading heading = orgIDs.get(id); Log.i(TAG, "Create: " + heading.getHeading()); - createInsertOperation(eventsUri,calendarID,heading, operations); + createInsertOperation(eventsUri,calendarID,heading, operations, readOnly); } } catch (RemoteException e) { Log.e(TAG, "Error querying events from calendar ", e); @@ -544,7 +574,8 @@ private void createDeleteOperation(Uri calURI, Cursor query, List operations, - List newHeadings) { + List newHeadings, + boolean readOnly) { final Heading h = new Heading( query.getString(EventsProjection.TITLE.ordinal()), @@ -583,7 +614,7 @@ private void createHeading(final Uri calURI, EventsProjection.ID.field + "= ?", new String[]{String.valueOf(localID)} ) - .withValue(EventsProjection.SYNC_ID.field, h.ensureID()) + .withValue(EventsProjection.SYNC_ID.field, h.syncID(readOnly)) .withValue(EventsProjection.ORG_HASH.field, h.checksum()) .withValue(EventsProjection.DIRTY.field, 0) .build(); @@ -591,11 +622,15 @@ private void createHeading(final Uri calURI, operations.add(update); } - private void createInsertOperation(Uri calURI, long calendarId, Heading heading, List out) { + private void createInsertOperation(Uri calURI, + long calendarId, + Heading heading, + List out, + boolean readOnly) { final ContentProviderOperation.Builder create = ContentProviderOperation.newInsert(calURI) .withValue(CalendarContract.Events.CALENDAR_ID, calendarId) - .withValue(CalendarContract.Events._SYNC_ID, heading.ensureID()); + .withValue(CalendarContract.Events._SYNC_ID, heading.syncID(readOnly)); out.add(copy(create, heading).build()); diff --git a/app/src/main/java/com/larkery/simpleorgsync/cal/parse/Heading.java b/app/src/main/java/com/larkery/simpleorgsync/cal/parse/Heading.java index 0a0c3ab..93559cd 100644 --- a/app/src/main/java/com/larkery/simpleorgsync/cal/parse/Heading.java +++ b/app/src/main/java/com/larkery/simpleorgsync/cal/parse/Heading.java @@ -213,7 +213,7 @@ public void addProperty(Property property) { this.endOfLastProperty = Math.max(property.getEndPosition(), endOfLastProperty); } - public String ensureID() { + private String ensureID() { if (!hasProperty("ID")) { setProperty("ID", UUID.randomUUID().toString()); } @@ -248,4 +248,9 @@ public void addInheritedTags(Set filetags) { public Set getTags() { return tags; } + + public String syncID(boolean readOnly) { + if (readOnly) return checksum(); + else return ensureID(); + } } diff --git a/app/src/main/java/com/larkery/simpleorgsync/cal/parse/Timestamp.java b/app/src/main/java/com/larkery/simpleorgsync/cal/parse/Timestamp.java index 6273329..fda7afd 100644 --- a/app/src/main/java/com/larkery/simpleorgsync/cal/parse/Timestamp.java +++ b/app/src/main/java/com/larkery/simpleorgsync/cal/parse/Timestamp.java @@ -3,7 +3,6 @@ import android.annotation.SuppressLint; import android.os.Build; import android.support.annotation.RequiresApi; -import android.util.Log; import java.text.ParseException; import java.text.SimpleDateFormat; diff --git a/app/src/main/java/com/larkery/simpleorgsync/con/ConSyncAdapter.java b/app/src/main/java/com/larkery/simpleorgsync/con/ConSyncAdapter.java index 746bcdb..37fea2b 100644 --- a/app/src/main/java/com/larkery/simpleorgsync/con/ConSyncAdapter.java +++ b/app/src/main/java/com/larkery/simpleorgsync/con/ConSyncAdapter.java @@ -1,6 +1,7 @@ package com.larkery.simpleorgsync.con; import android.accounts.Account; +import android.accounts.AccountManager; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; @@ -23,23 +24,22 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.RawContacts; -import android.util.Log; import com.google.common.base.Objects; import com.google.common.collect.Sets; -import com.larkery.simpleorgsync.lib.Application; +import com.larkery.simpleorgsync.lib.JSONPrefs; +import com.larkery.simpleorgsync.lib.Log; + +import org.json.JSONException; +import org.json.JSONObject; import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -48,7 +48,7 @@ import java.util.UUID; public class ConSyncAdapter extends AbstractThreadedSyncAdapter { - static final String TAG = Application.TAG +"/CON"; + static final String TAG = "ConSyncAdapter"; private Uri rawContactsURI; private Uri dataURI; private Account account; @@ -89,9 +89,12 @@ private Uri syncAdapterURI(Uri contentUri, Account account) { @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + JSONPrefs prefs = JSONPrefs.fromContext(getContext(), account); + + Log.i(TAG, "Prefs: " + prefs); final String contactsFile = prefs.getString("contacts_file", null); Log.i(TAG, "Sync Contacts File " + contactsFile); + if (contactsFile == null) return; this.rawContactsURI = syncAdapterURI(RawContacts.CONTENT_URI, account); this.dataURI = syncAdapterURI(ContactsContract.Data.CONTENT_URI, account); diff --git a/app/src/main/java/com/larkery/simpleorgsync/con/ContactsJson.java b/app/src/main/java/com/larkery/simpleorgsync/con/ContactsJson.java index 2063c2d..f566217 100644 --- a/app/src/main/java/com/larkery/simpleorgsync/con/ContactsJson.java +++ b/app/src/main/java/com/larkery/simpleorgsync/con/ContactsJson.java @@ -19,16 +19,9 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; /** * Created by hinton on 10/04/20. diff --git a/app/src/main/java/com/larkery/simpleorgsync/lib/Application.java b/app/src/main/java/com/larkery/simpleorgsync/lib/Application.java index e26b043..30a1875 100644 --- a/app/src/main/java/com/larkery/simpleorgsync/lib/Application.java +++ b/app/src/main/java/com/larkery/simpleorgsync/lib/Application.java @@ -2,26 +2,31 @@ import android.accounts.Account; import android.accounts.AccountManager; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.CalendarContract; import android.provider.ContactsContract; -import android.util.Log; +import android.support.v4.app.ShareCompat; import com.larkery.simpleorgsync.R; +import com.larkery.simpleorgsync.cal.CalSyncService; +import com.larkery.simpleorgsync.con.ConSyncService; + +import org.json.JSONException; +import org.json.JSONObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Calendar; +import java.util.Map; public class Application extends android.app.Application implements SharedPreferences.OnSharedPreferenceChangeListener { - public static final String TAG = "ORGSYNC"; + private final static String TAG = "Application"; private AccountManager accountManager; private Account account; @Override @@ -34,10 +39,13 @@ public void onCreate() { preferences.registerOnSharedPreferenceChangeListener(this); FileJobService.register(getApplicationContext()); + + transmitPreferences(PreferenceManager.getDefaultSharedPreferences(getApplicationContext())); } public Account getAccount() { accountManager.addAccountExplicitly(account, null, null); + return account; } @@ -60,13 +68,16 @@ private void informSystemSyncable() { } } - public void requestSync() { + public void requestSync(final String cause) { informSystemSyncable(); + + transmitPreferences(PreferenceManager.getDefaultSharedPreferences(getApplicationContext())); + boolean contacts = isContactsSyncable(); boolean calendar = isCalendarSyncable(); if (contacts) { - Log.i(TAG, "Request contacts sync"); + Log.i(TAG, "Request contacts sync due to " + cause); Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); @@ -74,7 +85,7 @@ public void requestSync() { } if (calendar) { - Log.i(TAG, "Request calendar sync"); + Log.i(TAG, "Request calendar sync due to " + cause); Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); @@ -129,25 +140,38 @@ private boolean isCalendarSyncable() { .getString("agenda_files", null) != null; } + void transmitPreferences(SharedPreferences sharedPreferences) { + final JSONObject obj = new JSONObject(); + for (final Map.Entry e : sharedPreferences.getAll().entrySet()) { + try { + obj.put(e.getKey(), (Object)e.getValue()); + } catch (JSONException jsonException) { + jsonException.printStackTrace(); + } + } + getApplicationContext().getSystemService(AccountManager.class) + .setUserData(getAccount(), "prefs", obj.toString()); + } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { final String cf = sharedPreferences.getString("contacts_file", null); final String af = sharedPreferences.getString("agenda_files", null); Log.i(TAG, "Pref change " + cf + " " + af); + transmitPreferences(sharedPreferences); switch (s) { case "contacts_file": case "agenda_files": case "date_type": case "ignore_syncthing_conflicts": - { - requestSync(); + case "read_only": { + requestSync("a preference was changed: " + s); } - break; + break; case "inotify": FileJobService.register(getApplicationContext()); break; - case "sync_frequency": - { + case "sync_frequency": { int syncFrequency = Integer.parseInt(sharedPreferences.getString("sync_frequency", "-1")); if (af != null || cf != null) { if (af != null) { @@ -181,7 +205,7 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin } } } - break; + break; } } } diff --git a/app/src/main/java/com/larkery/simpleorgsync/lib/FileJobService.java b/app/src/main/java/com/larkery/simpleorgsync/lib/FileJobService.java index 811a56e..0f91f80 100644 --- a/app/src/main/java/com/larkery/simpleorgsync/lib/FileJobService.java +++ b/app/src/main/java/com/larkery/simpleorgsync/lib/FileJobService.java @@ -1,7 +1,5 @@ package com.larkery.simpleorgsync.lib; -import android.app.Notification; -import android.app.Service; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; @@ -9,36 +7,23 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; -import android.database.Cursor; import android.net.Uri; import android.os.Build; -import android.os.Handler; -import android.os.IBinder; import android.preference.PreferenceManager; -import android.provider.MediaStore; -import android.support.v4.app.NotificationManagerCompat; import android.support.v4.provider.DocumentFile; import android.util.Log; -import android.widget.Toast; -import com.larkery.simpleorgsync.R; import com.larkery.simpleorgsync.cal.CalSyncAdapter; -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Set; public class FileJobService extends JobService { - private static final String TAG = "ORGSYNC/FILEJOBSERVICE"; + private static final String TAG = "FileJobService"; public FileJobService() { } @@ -67,6 +52,7 @@ public static void register(Context context) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String contactsFile = prefs.getString("contacts_file", null); final String agendaFiles = prefs.getString("agenda_files", null); + final boolean ignoreConflict = prefs.getBoolean("ignore_syncthing_conflicts", true); final Set toWatch = new HashSet<>(); if (contactsFile != null) { @@ -76,15 +62,15 @@ public static void register(Context context) { if (agendaFiles != null) { CalSyncAdapter.collectOrgFiles( CalSyncAdapter.docFile(context, agendaFiles), - toWatch); + toWatch, + ignoreConflict); } final ArrayList paths = new ArrayList<>(); StringBuffer sb = new StringBuffer(); - - int i = 0; for (DocumentFile f : toWatch) { - i++; + Log.i(TAG, "Watching " + f.getName()); + b.addTriggerContentUri( new JobInfo.TriggerContentUri( f.getUri(), @@ -93,7 +79,6 @@ public static void register(Context context) { ); } - Log.i(TAG, "Watching " + i + " contenturi for " + paths.size() + " files..."); b.setTriggerContentUpdateDelay(100); b.setTriggerContentMaxDelay(3000); scheduler.schedule(b.build()); @@ -103,8 +88,9 @@ public static void register(Context context) { @Override public boolean onStartJob(JobParameters jobParameters) { - Log.i(TAG, "Agenda sync triggered!"); - ((Application)getApplication()).requestSync(); + ((Application)getApplication()).requestSync("a file was changed: " + + Arrays.toString(jobParameters.getTriggeredContentUris()) + ); this.jobFinished(jobParameters, false); register(getApplicationContext()); return false; diff --git a/app/src/main/java/com/larkery/simpleorgsync/lib/FileUtils.java b/app/src/main/java/com/larkery/simpleorgsync/lib/FileUtils.java deleted file mode 100644 index 5a9119c..0000000 --- a/app/src/main/java/com/larkery/simpleorgsync/lib/FileUtils.java +++ /dev/null @@ -1,243 +0,0 @@ -package com.larkery.simpleorgsync.lib; - -import android.annotation.TargetApi; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.provider.DocumentsContract; -import android.provider.MediaStore; -import android.support.v4.content.ContextCompat; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * Created by Kimcy929 on 13/03/2018. - */ - - -@SuppressWarnings("WeakerAccess") -public class FileUtils { - - private static final String PATH_TREE = "tree"; - private static final String PRIMARY_TYPE = "primary"; - private static final String RAW_TYPE = "raw"; - - @TargetApi(Build.VERSION_CODES.KITKAT) - public static String getFilePathFromUri(final Context context, final Uri uri) { - - // DocumentProvider - if (DocumentsContract.isDocumentUri(context, uri)) { - // ExternalStorageProvider - if (isExternalStorageDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - //Timber.d("docId -> %s", docId); - final String[] split = docId.split(":"); - final String type = split[0]; - - if (PRIMARY_TYPE.equalsIgnoreCase(type)) { - return Environment.getExternalStorageDirectory() + "/" + split[1]; - } else { - // TODO handle non-primary volumes - StringBuilder path = new StringBuilder(); - String pathSegment[] = docId.split(":"); - return path.append(getRemovableStorageRootPath(context, pathSegment[0])).append(File.separator).append(pathSegment[1]).toString(); - } - } else if (isDownloadsDocument(uri)) { // DownloadsProvider - - final String id = DocumentsContract.getDocumentId(uri); - - if (id.contains("raw:")) { - return id.substring(id.indexOf(File.separator)); - } else { - Uri contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - - return getDataColumn(context, contentUri, null, null); - } - - } else if (isMediaDocument(uri)) { // MediaProvider - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - - final String selection = "_id=?"; - final String[] selectionArgs = new String[]{ - split[1] - }; - - return getDataColumn(context, contentUri, selection, selectionArgs); - } - } else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general) - return getDataColumn(context, uri, null, null); - } else if ("file".equalsIgnoreCase(uri.getScheme())) { // File - return uri.getPath(); - } - - return null; - } - - /** - * Get the value of the data column for this Uri. This is useful for - * MediaStore Uris, and other file-based ContentProviders. - * - * @param context The context. - * @param uri The Uri to query. - * @param selection (Optional) Filter used in the query. - * @param selectionArgs (Optional) Selection arguments used in the query. - * @return The value of the _data column, which is typically a file path. - */ - public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { - - Cursor cursor = null; - final String column = MediaStore.MediaColumns.DATA; - final String[] projection = {column}; - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, - null); - if (cursor != null && cursor.moveToFirst()) { - final int column_index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(column_index); - } - } finally { - if (cursor != null) - cursor.close(); - } - - return null; - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - public static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - public static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - public static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } - - /** - * @param uri - * @return file path of Uri - */ - public static String getDirectoryPathFromUri(Context context, Uri uri) { - - if ("file".equals(uri.getScheme())) { - return uri.getPath(); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT - && isTreeUri(uri)) { - - String treeId = getTreeDocumentId(uri); - - if (treeId != null) { - - String[] paths = treeId.split(":"); - String type = paths[0]; - String subPath = paths.length == 2 ? paths[1] : ""; - - if (RAW_TYPE.equalsIgnoreCase(type)) { - return treeId.substring(treeId.indexOf(File.separator)); - } else if (PRIMARY_TYPE.equalsIgnoreCase(type)) { - return Environment.getExternalStorageDirectory() + File.separator + subPath; - } else { - StringBuilder path = new StringBuilder(); - String[] pathSegment = treeId.split(":"); - if (pathSegment.length == 1) { - path.append(getRemovableStorageRootPath(context, paths[0])); - } else { - String rootPath = getRemovableStorageRootPath(context, paths[0]); - path.append(rootPath).append(File.separator).append(pathSegment[1]); - } - - return path.toString(); - } - } - } - return null; - } - - private static String getRemovableStorageRootPath(Context context, String storageId) { - StringBuilder rootPath = new StringBuilder(); - File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null); - for (File fileDir : externalFilesDirs) { - if (fileDir.getPath().contains(storageId)) { - String[] pathSegment = fileDir.getPath().split(File.separator); - for (String segment : pathSegment) { - if (segment.equals(storageId)) { - rootPath.append(storageId); - break; - } - rootPath.append(segment).append(File.separator); - } - //rootPath.append(fileDir.getPath().split("/Android")[0]); // faster - break; - } - } - return rootPath.toString(); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static List getListRemovableStorage(Context context) { - - List paths = new ArrayList<>(); - - File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null); - for (File fileDir : externalFilesDirs) { - if (Environment.isExternalStorageRemovable(fileDir)) { - String path = fileDir.getPath(); - if (path.contains("/Android")) { - paths.add(path.substring(0, path.indexOf("/Android"))); - } - } - } - - return paths; - } - - //https://github.com/rcketscientist/DocumentActivity/blob/master/library/src/main/java/com/anthonymandra/framework/DocumentUtil.java#L56 - /** - * Extract the via {@link DocumentsContract.Document#COLUMN_DOCUMENT_ID} from the given URI. - * From {@link DocumentsContract} but return null instead of throw - */ - public static String getTreeDocumentId(Uri uri) { - final List paths = uri.getPathSegments(); - if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) { - return paths.get(1); - } - return null; - } - - public static boolean isTreeUri(Uri uri) { - final List paths = uri.getPathSegments(); - return (paths.size() == 2 && PATH_TREE.equals(paths.get(0))); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/larkery/simpleorgsync/lib/JSONPrefs.java b/app/src/main/java/com/larkery/simpleorgsync/lib/JSONPrefs.java new file mode 100644 index 0000000..0a3014d --- /dev/null +++ b/app/src/main/java/com/larkery/simpleorgsync/lib/JSONPrefs.java @@ -0,0 +1,57 @@ +package com.larkery.simpleorgsync.lib; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; + +import org.json.JSONException; +import org.json.JSONObject; + +public class JSONPrefs { + JSONObject value; + private JSONPrefs(String s) { + try { + value = new JSONObject(s); + } catch (Exception e) { + Log.e("JSONPrefs", "Invalid prefs", e); + value = new JSONObject(); + } + } + public static JSONPrefs fromString(String s) { + return new JSONPrefs(s); + } + + public static JSONPrefs fromContext(Context context, Account account) { + return fromString( + context.getSystemService(AccountManager.class).getUserData(account, "prefs") + ); + } + + public String toString() { + return value.toString(); + } + + public String getString(final String key, final String def) { + try { + return value.getString(key); + } catch (JSONException e) { + return def; + } + } + + public boolean getBoolean(final String key, final boolean def) { + try { + return value.getBoolean(key); + } catch (JSONException e) { + return def; + } + } + + public int getInt(final String key, final int def) { + try { + return value.getInt(key); + } catch (JSONException e) { + return def; + } + } +} diff --git a/app/src/main/java/com/larkery/simpleorgsync/lib/Log.java b/app/src/main/java/com/larkery/simpleorgsync/lib/Log.java new file mode 100644 index 0000000..af44d5e --- /dev/null +++ b/app/src/main/java/com/larkery/simpleorgsync/lib/Log.java @@ -0,0 +1,29 @@ +package com.larkery.simpleorgsync.lib; + +public class Log { + public static final String TAG = "orgcal/"; + public static void i(String tag, String msg) { + android.util.Log.i(TAG + tag, msg); + } + public static void e(String tag, String msg) { + android.util.Log.e(TAG + tag, msg); + } + public static void w(String tag, String msg) { + android.util.Log.w(TAG + tag, msg); + } + public static void d(String tag, String msg) { + android.util.Log.d(TAG + tag, msg); + } + public static void i(String tag, String msg, Throwable th) { + android.util.Log.i(TAG + tag, msg, th); + } + public static void e(String tag, String msg, Throwable th) { + android.util.Log.e(TAG + tag, msg, th); + } + public static void w(String tag, String msg, Throwable th) { + android.util.Log.w(TAG + tag, msg, th); + } + public static void d(String tag, String msg, Throwable th) { + android.util.Log.d(TAG + tag, msg, th); + } +} diff --git a/app/src/main/res/xml/prefs.xml b/app/src/main/res/xml/prefs.xml index 3074dd2..229d3c3 100644 --- a/app/src/main/res/xml/prefs.xml +++ b/app/src/main/res/xml/prefs.xml @@ -22,6 +22,11 @@ a:title="Watch for file changes" a:summary="If the agenda files change, trigger calendar sync" /> +