From e91c49fbec2059455fe4cfd78b50b5b87796aa1c Mon Sep 17 00:00:00 2001 From: korelstar Date: Sun, 26 Jun 2016 14:25:58 +0200 Subject: [PATCH 01/25] Improved presentation of errors if login (SettingsActivity) or synchronization fails. --- .../activity/NotesListViewActivity.java | 4 +- .../android/activity/SettingsActivity.java | 11 ++--- .../persistence/NoteServerSyncHelper.java | 18 ++++++-- .../owncloud/notes/util/NotesClientUtil.java | 43 ++++++++++++++++--- app/src/main/res/values-de/strings.xml | 8 ++-- app/src/main/res/values/strings.xml | 8 ++-- 6 files changed, 69 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 022b180c5..4813188a3 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -105,7 +105,9 @@ public void onFinish() { adapter.checkForUpdates(db.getNotes()); } }); - db.getNoteServerSyncHelper().downloadNotes(); + if (!PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean(SettingsActivity.SETTINGS_FIRST_RUN, true)) { + db.getNoteServerSyncHelper().downloadNotes(); + } super.onResume(); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java index 702eb395a..953d064f7 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java @@ -20,6 +20,7 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.util.NotesClientUtil; +import it.niedermann.owncloud.notes.util.NotesClientUtil.LoginStatus; /** * Allows to set Settings like URL, Username and Password for Server-Synchronization @@ -194,7 +195,7 @@ protected void onPostExecute(Boolean o) { /** * If Log-In-Credentials are correct, save Credentials to Shared Preferences and finish First Run Wizard. */ - private class LoginValidatorAsyncTask extends AsyncTask { + private class LoginValidatorAsyncTask extends AsyncTask { String url, username, password; @Override @@ -208,7 +209,7 @@ protected void onPreExecute() { * @return isValidLogin Boolean */ @Override - protected Boolean doInBackground(String... params) { + protected LoginStatus doInBackground(String... params) { url = params[0]; username = params[1]; password = params[2]; @@ -216,8 +217,8 @@ protected Boolean doInBackground(String... params) { } @Override - protected void onPostExecute(Boolean isValidLogin) { - if (isValidLogin) { + protected void onPostExecute(LoginStatus status) { + if (LoginStatus.OK.equals(status)) { SharedPreferences.Editor editor = preferences.edit(); editor.putString(SETTINGS_URL, url); editor.putString(SETTINGS_USERNAME, username); @@ -238,7 +239,7 @@ protected void onPostExecute(Boolean isValidLogin) { Log.e("Note", "invalid login"); btn_submit.setText(R.string.settings_submit); setInputsEnabled(true); - Toast.makeText(getApplicationContext(), getString(R.string.error_invalid_login), Toast.LENGTH_LONG).show(); + Toast.makeText(getApplicationContext(), getString(R.string.error_invalid_login, getString(status.str)), Toast.LENGTH_LONG).show(); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 019ed6e4f..f82db47ab 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -1,6 +1,7 @@ package it.niedermann.owncloud.notes.persistence; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.AsyncTask; @@ -8,6 +9,7 @@ import android.os.Message; import android.preference.PreferenceManager; import android.view.View; +import android.widget.Toast; import org.json.JSONException; @@ -15,11 +17,13 @@ import java.util.ArrayList; import java.util.List; +import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.SettingsActivity; import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NotesClient; +import it.niedermann.owncloud.notes.util.NotesClientUtil.LoginStatus; /** * Helps to synchronize the Database to the Server. @@ -205,7 +209,7 @@ protected void onPostExecute(Void aVoid) { } private class DownloadNotesTask extends AsyncTask> { - private boolean serverError = false; + private LoginStatus status = null; @Override protected List doInBackground(Object... params) { @@ -213,9 +217,12 @@ protected List doInBackground(Object... params) { List notes = new ArrayList<>(); try { notes = client.getNotes(); - } catch (IOException | JSONException e) { - serverError = true; + status = LoginStatus.OK; + } catch (IOException e) { e.printStackTrace(); + status = LoginStatus.CONNECTION_FAILED; + } catch (JSONException e) { + status = LoginStatus.JSON_FAILED; } return notes; } @@ -223,8 +230,11 @@ protected List doInBackground(Object... params) { @Override protected void onPostExecute(List result) { // Clear Database only if there was no Server Error - if (!serverError) { + if (status==LoginStatus.OK) { db.clearDatabase(); + } else { + Context c = db.getContext(); + Toast.makeText(c, c.getString(R.string.error_sync, c.getString(status.str)), Toast.LENGTH_LONG).show(); } for (Note note : result) { db.addNote(note); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java index b52a51835..16801f7c2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java @@ -2,6 +2,7 @@ import android.util.Base64; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -12,12 +13,27 @@ import java.net.MalformedURLException; import java.net.URL; +import it.niedermann.owncloud.notes.R; + /** * Utils for Validation etc * Created by stefan on 25.09.15. */ public class NotesClientUtil { + public enum LoginStatus { + OK(0), + AUTH_FAILED(R.string.error_username_password_invalid), + CONNECTION_FAILED(R.string.error_io), + JSON_FAILED(R.string.error_json), + SERVER_FAILED(R.string.error_server); + + public final int str; + LoginStatus(int str) { + this.str = str; + } + } + /** * Checks if the given url String starts with http:// or https:// * @@ -34,7 +50,7 @@ public static boolean isHttp(String url) { * @param password String * @return Username and Password are a valid Login-Combination for the given URL. */ - public static boolean isValidLogin(String url, String username, String password) { + public static LoginStatus isValidLogin(String url, String username, String password) { try { String targetURL = url + "index.php/apps/notes/api/v0.2/notes"; HttpURLConnection con = (HttpURLConnection) new URL(targetURL) @@ -48,14 +64,27 @@ public static boolean isValidLogin(String url, String username, String password) con.setConnectTimeout(10 * 1000); // 10 seconds con.connect(); if (con.getResponseCode() == 200) { - return true; + StringBuilder result = new StringBuilder(); + BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream())); + String line; + while ((line = rd.readLine()) != null) { + result.append(line); + } + System.out.println(result.toString()); + new JSONArray(result.toString()); + return LoginStatus.OK; + } else if (con.getResponseCode() >= 401 && con.getResponseCode() <= 403) { + return LoginStatus.AUTH_FAILED; + } else { + return LoginStatus.SERVER_FAILED; } - } catch (MalformedURLException e1) { - return false; + } catch (MalformedURLException e) { + return LoginStatus.CONNECTION_FAILED; } catch (IOException e) { - return false; + return LoginStatus.CONNECTION_FAILED; + } catch (JSONException e) { + return LoginStatus.JSON_FAILED; } - return false; } /** @@ -65,7 +94,7 @@ public static boolean isValidLogin(String url, String username, String password) * @return true if there is a installed instance, false if not */ public static boolean isValidURL(String url) { - StringBuffer result = new StringBuffer(); + StringBuilder result = new StringBuilder(); try { HttpURLConnection con = (HttpURLConnection) new URL(url + "status.php") .openConnection(); diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8f3123fbe..a936fb8e2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -45,9 +45,11 @@ Kein Netzwerk verfügbar - JSON-Fehler - IO-Fehler - Login fehlgeschlagen + Synchronisation fehlgeschlagen: %1$s + Login fehlgeschlagen: %1$s + ist die Server-App ownCloud Notes aktiviert? + Server-Verbindung nicht möglich + URL/Server fehlerhaft URL nicht korrekt Benutzername / Passwort nicht korrekt diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ecdee1abe..26a8684ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,9 +45,11 @@ No network available - JSON error - IO error - Invalid login + Synchronization failed: %1$s + Invalid login: %1$s + is the owncloud Notes app activated on the server? + server connection is broken + URL/Server has errors Wrong server address Wrong username or password From 37724e9720d7b4f55415fd37ff2545362da1943d Mon Sep 17 00:00:00 2001 From: korelstar Date: Wed, 29 Jun 2016 22:09:46 +0200 Subject: [PATCH 02/25] Show icon if a note is not synchronized (hint to a possible error) --- .../android/activity/EditNoteActivity.java | 5 ++-- .../activity/NotesListViewActivity.java | 7 +++-- .../activity/SelectSingleNoteActivity.java | 3 +- .../notes/android/widget/AllNotesWidget.java | 8 +++-- .../owncloud/notes/model/DBNote.java | 28 ++++++++++++++++++ .../owncloud/notes/model/DBStatus.java | 8 +++++ .../owncloud/notes/model/ItemAdapter.java | 14 +++++---- .../persistence/NoteSQLiteOpenHelper.java | 22 +++++++------- .../res/drawable-hdpi/ic_sync_error_holo.png | Bin 0 -> 1154 bytes .../res/drawable-mdpi/ic_sync_error_holo.png | Bin 0 -> 790 bytes .../res/drawable-xhdpi/ic_sync_error_holo.png | Bin 0 -> 1411 bytes .../drawable-xxhdpi/ic_sync_error_holo.png | Bin 0 -> 1889 bytes .../drawable-xxxhdpi/ic_sync_error_holo.png | Bin 0 -> 2231 bytes .../layout/fragment_notes_list_note_item.xml | 9 ++++++ 14 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java create mode 100644 app/src/main/res/drawable-hdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-mdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_sync_error_holo.png diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index c1d24d966..e04959e88 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.ICallback; @@ -24,7 +25,7 @@ public class EditNoteActivity extends AppCompatActivity { private final long DELAY = 1000; // in ms private EditText content = null; - private Note note = null; + private DBNote note = null; private Timer timer = new Timer(); private ActionBar actionBar; @@ -32,7 +33,7 @@ public class EditNoteActivity extends AppCompatActivity { protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit); - note = (Note) getIntent().getSerializableExtra( + note = (DBNote) getIntent().getSerializableExtra( NoteActivity.EDIT_NOTE); content = (EditText) findViewById(R.id.editContent); content.setEnabled(false); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 022b180c5..44c9a2228 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -21,6 +21,7 @@ import java.util.List; import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; import it.niedermann.owncloud.notes.model.Note; @@ -128,7 +129,7 @@ public void onClick(View v) { * @param noteList List<Note> */ @SuppressWarnings("WeakerAccess") - public void setListView(List noteList) { + public void setListView(List noteList) { List itemList = new ArrayList<>(); // #12 Create Sections depending on Time // TODO Move to ItemAdapter? @@ -288,7 +289,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { //not need because of db.synchronisation in createActivity - Note createdNote = (Note) data.getExtras().getSerializable( + DBNote createdNote = (DBNote) data.getExtras().getSerializable( CREATED_NOTE); adapter.add(createdNote); //setListView(db.getNotes()); @@ -299,7 +300,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { SELECTED_NOTE_POSITION); adapter.remove(adapter.getItem(notePosition)); if (resultCode == RESULT_OK) { - Note editedNote = (Note) data.getExtras().getSerializable( + DBNote editedNote = (DBNote) data.getExtras().getSerializable( NoteActivity.EDIT_NOTE); adapter.add(editedNote); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java index 7bcf60cac..b9bb6d7b0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java @@ -15,6 +15,7 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.widget.SingleNoteWidget; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; import it.niedermann.owncloud.notes.model.Note; @@ -61,7 +62,7 @@ public void onCreate(Bundle savedInstanceState) { * * @param noteList List<Note> */ - private void setListView(List noteList) { + private void setListView(List noteList) { List itemList = new ArrayList<>(); itemList.addAll(noteList); adapter = new ItemAdapter(itemList); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java index e85c7e645..83a84f785 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java @@ -13,6 +13,8 @@ import java.util.List; import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.model.DBNote; +import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; @@ -51,7 +53,7 @@ public RemoteViewsFactory onGetViewFactory(Intent intent) { class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final int mCount = 10; - private List mWidgetItems = new ArrayList<>(); + private List mWidgetItems = new ArrayList<>(); private Context mContext; private int mAppWidgetId; @@ -62,11 +64,11 @@ public StackRemoteViewsFactory(Context context, Intent intent) { NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(mContext); db.synchronizeWithServer(); mWidgetItems = db.getNotes(); - mWidgetItems.add(new Note(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung")); + mWidgetItems.add(new DBNote(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); } public void onCreate() { - mWidgetItems.add(new Note(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung")); + mWidgetItems.add(new DBNote(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); } @Override diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java new file mode 100644 index 000000000..646d869c2 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java @@ -0,0 +1,28 @@ +package it.niedermann.owncloud.notes.model; + +import java.util.Calendar; + +import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; + +public class DBNote extends Note { + + private DBStatus status; + + public DBNote(long id, Calendar modified, String title, String content, DBStatus status) { + super(id, modified, title, content); + this.status = status; + } + + public DBStatus getStatus() { + return status; + } + + public void setStatus(DBStatus status) { + this.status = status; + } + + @Override + public String toString() { + return "#" + getId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java index fc27f8265..ea3393b31 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java @@ -17,4 +17,12 @@ public String getTitle() { DBStatus(String title) { this.title = title; } + + public static DBStatus parse(String str) { + if(str.isEmpty()) { + return DBStatus.VOID; + } else { + return DBStatus.valueOf(str); + } + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java index b0bc49e9f..f7a291049 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java @@ -4,6 +4,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; @@ -37,7 +38,7 @@ public static void setNoteClickListener(NoteClickListener noteClickListener) { * Adds the given note to the top of the list. * @param note Note that should be added. */ - public void add(Note note) { + public void add(DBNote note) { itemList.add(0, note); notifyItemInserted(0); notifyItemChanged(0); @@ -55,12 +56,12 @@ public void removeAll() { * Compares the given List of notes to the current internal holded notes and updates the list if necessairy * @param newNotes List of more up to date notes */ - public void checkForUpdates(List newNotes) { - for(Note newNote : newNotes) { + public void checkForUpdates(List newNotes) { + for(DBNote newNote : newNotes) { boolean foundNewNoteInOldList = false; for(Item oldItem : itemList) { if(!oldItem.isSection()) { - Note oldNote = (Note) oldItem; + DBNote oldNote = (DBNote) oldItem; if(newNote.getId() == oldNote.getId()) { // Notes have the same id, check which is newer if(newNote.getModified().after(oldNote.getModified())) { @@ -109,9 +110,10 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ((SectionViewHolder) holder).sectionTitle.setText(section.geTitle()); ((SectionViewHolder) holder).setPosition(position); } else { - Note note = (Note) item; + DBNote note = (DBNote) item; ((NoteViewHolder) holder).noteTitle.setText(note.getTitle()); ((NoteViewHolder) holder).noteExcerpt.setText(note.getExcerpt()); + ((NoteViewHolder) holder).noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.GONE : View.VISIBLE); ((NoteViewHolder) holder).setPosition(position); } } @@ -168,12 +170,14 @@ public static class NoteViewHolder extends RecyclerView.ViewHolder implements Vi // each data item is just a string in this case public TextView noteTitle; public TextView noteExcerpt; + public ImageView noteStatus; public int position = -1; private NoteViewHolder(View v) { super(v); this.noteTitle = (TextView) v.findViewById(R.id.noteTitle); this.noteExcerpt = (TextView) v.findViewById(R.id.noteExcerpt); + this.noteStatus = (ImageView) v.findViewById(R.id.noteStatus); v.setOnClickListener(this); v.setOnLongClickListener(this); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index d554bd49d..cb27390f2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Locale; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.util.NoteUtil; @@ -112,7 +113,7 @@ public void addNote(Note note) { * @return requested Note */ @SuppressWarnings("unused") - public Note getNote(long id) { + public DBNote getNote(long id) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.query(table_notes, @@ -134,7 +135,7 @@ public Note getNote(long id) { } catch (ParseException e) { e.printStackTrace(); } - Note note = new Note(Long.valueOf(cursor != null ? cursor.getString(0) : null), modified, cursor != null ? cursor.getString(2) : null, cursor.getString(4)); + DBNote note = new DBNote(Long.valueOf(cursor != null ? cursor.getString(0) : null), modified, cursor != null ? cursor.getString(2) : null, cursor.getString(4), DBStatus.parse(cursor.getString(1))); cursor.close(); return note; } @@ -144,8 +145,8 @@ public Note getNote(long id) { * * @return List<Note> */ - public List getNotes() { - List notes = new ArrayList<>(); + public List getNotes() { + List notes = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle()}); if (cursor.moveToFirst()) { @@ -158,7 +159,7 @@ public List getNotes() { } catch (ParseException e) { e.printStackTrace(); } - notes.add(new Note(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4))); + notes.add(new DBNote(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4), DBStatus.parse(cursor.getString(1)))); } while (cursor.moveToNext()); } cursor.close(); @@ -170,8 +171,8 @@ public List getNotes() { * * @return List<Note> */ - public List searchNotes(String query) { - List notes = new ArrayList<>(); + public List searchNotes(String query) { + List notes = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? AND " + key_content + " LIKE ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}); if (cursor.moveToFirst()) { @@ -184,7 +185,7 @@ public List searchNotes(String query) { } catch (ParseException e) { e.printStackTrace(); } - notes.add(new Note(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4))); + notes.add(new DBNote(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4), DBStatus.parse(cursor.getString(1)))); } while (cursor.moveToNext()); } cursor.close(); @@ -224,7 +225,7 @@ public List getNotesByStatus(DBStatus status) { * @return The number of the Rows affected. */ @SuppressWarnings("UnusedReturnValue") - public int updateNoteAndSync(Note note) { + public int updateNoteAndSync(DBNote note) { SQLiteDatabase db = this.getWritableDatabase(); DBStatus newStatus = DBStatus.LOCAL_EDITED; Cursor cursor = @@ -239,11 +240,12 @@ public int updateNoteAndSync(Note note) { if (cursor != null) { cursor.moveToFirst(); String status = cursor.getString(1); - if (!"".equals(status) && DBStatus.valueOf(status) == DBStatus.LOCAL_CREATED) { + if (DBStatus.parse(status) == DBStatus.LOCAL_CREATED) { newStatus = DBStatus.LOCAL_CREATED; } cursor.close(); } + note.setStatus(newStatus); ContentValues values = new ContentValues(); values.put(key_id, note.getId()); values.put(key_status, newStatus.getTitle()); diff --git a/app/src/main/res/drawable-hdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-hdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..cfa2d2b92daf5f2eeef58e5d86fe491eae242986 GIT binary patch literal 1154 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+eS5K)hhiu0R{01Y44~ zy9>jA5L~c#`DCC7XMsm#F_88EW4Dvpb_@*6L7py-ArXh)&all0aTGXS|6MQLV8xF{ zF}Dqz9AR}LVQO9Rt@2wPlTsI-t1mg__Q-|yS*BzR$3xJJtn zsRhhGI7Nh0>KKbHb6gH2tv;8_daQvpL28ESc^R$y8mqa$`T40$-%Ky4Gm1wX()^$7t5nG?!*)-4hjVMp^!KWjiw^A4 zdm#6L;f97;k@C_&IT2>l!d0^v71!>bd_;5Js!_|CG8+k!l&u3RLwZh?BsdgkkAs@96W&X}#RU{@7uZ0V~oA+Nv( zwSqTTW5n3G4u~e`uUjT?WJ;E14e#`8zYnM;OntGRTWdBoRqU6uNPL!m>IPp8OCMwY3BR)IypsPcHaO<6 zWqW?U>l-7yKEr0Mvhthx=H74ZrplYY{$9OpwgA(O%x7^PELsapLlyqBMvMG>p5~YP z*HNS7OTO73_4tNkhrZsQ%5S;aN=-P&RMDkt(w6*%i_UrZ_O%PlGO9_Rr10_WUls|2 zJxWVgehs{xR#M~n*(~JBV_lcGi!+{f@916hYq#xf-$M_!JxW);GFfekP=x#j{~u+N z{1SVGcPJmb)%8RuroFTn$j@`B8R;qD)=AB~Q z&am#(9qR<{g1#O5XC06Azn1oy`{UhDLOXQtg*PTX*z!T};lAVQcMW29e9r0EQ7?6@ zbZM2@(n(4e8nZ=nUK?<%`MSW*EAr-vYdp97Pu{GHcw*JrUsamLcD*KYxz5j(YrJM{ zDYbmEmRstZXRvwaZAs-Fx9jC@v}DVg99kN6!>adrpzEawRcCvVqF34lTo&GU_%&B9 zTxCm^p3Lw_e^tlYnCZak+&bGG9orb|ih)croLsa#h0)uiqB$)$YeMv@-N zXR1zbe=N0gNw#}^&%YDh0UV9{clrfs@;m)^`oHPV<)l`r7v}B{UoKlZrD8QOo2iz# zMwFx^mZVxG7o`Fz1|tJQ6I}yyT_ej7LsKh5Ln{+AAlJ&kApPe3Boqy~`6-!cmAEyC zwWQwzYLEok5S*V@Ql40p%HWuipOmWLnVXoN8kCxtQdxL16;u{5c)I$ztaD0e0svFC B;>!R4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-mdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..77a6077fd6804fe145fc57df5aa0fb3df9d53bf7 GIT binary patch literal 790 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ueoXKL{?^yL>WGgtNdSvKUBvfU(=jY&)Rw&pcfmLp+Y3opw@>B~YYo|D|VQSLa6D z_{bn8mRrGpk2$(bNO29v`y}0O+f>?EIg`57STa)!|4+7yUA0R^sc`Z+&%4Hd()aP! z*DPwvw>}wfJ*nL5*I$h$4hgon;5{rAIvtBxYPL;&DKK~X1?B?oD~-kqWkqV%9BY_N zkDvA~>^^>)DYN4qbEtZ9k*MV)hG>U;w=g5dQwJ7`2tV*UCJ=CyaV^97Nw-<6ZZ$@{ zxE-`3P=x;g!v+2rhUZ@Wa|<81SXpaE-|zAF_`oZ{JguSJHFVl`|8;727nweA-8DH< z;ab*gp<4xeSQ9v2O#ZlwxkKi}uGQ_^Z%$gz%<21j_vuQZ(-l$&egt2#{ZRTfk%wt< zi>1Z2*yi_}C+RKt`fp{4drhxZX1!3l!o=?{Z&l7T$;j(kX2d0WQD@urmqK&Tc?Uk? z+N5taS$JW8n#2rdyApHlUFuPvCeBzsc~{NColI-IlOAyGP&?x?^TN6AmT7g9-?_Mb z(G@q#P;XH@#291Mz<5Z&jKle6*@YV2Dvsp&0&LkwKPJu6jZ$1AsQqc8!{ySmEH71g zdUCIIJ)Z3|bpyxWq!>LWgJrD|%-1r$ZH>Qfm^k%1=jyv=ujlv&hcH~yC{S7UHL+r8Vb{*JddJqh7VdHKHUXu_Vl#^x7@Arc8d{l{0l8KN2I)8NC!uJ_%}>cptHiBAtR?*(P=h4MhT#0PlJdl&R0hYC h{G?O`&)mfH)S%SFl*+=Bsi5@9;OXk;vd$@?2>|L1FDL*2 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-xhdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..1394a19cdce2bd27a50632b51dcc44ca28fb0138 GIT binary patch literal 1411 zcmZ`(do&Yz9RKa%F>hHSyBJs6?qpkD^L|X5*AA0sSD4MX5!M!!$0j!v<#oCw?mEIr z(X7W3nS1jJJ>)of)Dn?GO+tyf>;8BDxaWJm-_QAczUTA({P!&kq){~0cB%mYG<~RK zx&rY(T2)yw!=PXC6#!>@1b6_@dQN>aR!PAjN9Yt!(DDfNRzXbTsPq5;j_(41p9jE( zLgLQ@z*Ag#7XyG}B>+28FWwDuRxFemeiSnJ{-cEV8p{<5B9$6(M1jPLibmlo;1-mR2D+AcCT7`uIMWP`PJ>zu=oY@d zz;^d6Ay1dUn*+NssgP~fy;{9%L{!u1D~anvYl}37_e5Ewi<3)KNKQx?k7w z5#y7VoFuQU9tpTuP41m9yFoKfT>C~6m4L3^Il+#uovE7PoZf{eP}bSuu%_27*u}|` zzx@Irn@bwY+HzZb<4ki;KH)C)xgO8BKX)wrX~}j*w%s(0j>Dj_hRDl`YjkU-%gBd-`7f} zyXfp4(-oTZZ?<57o=DXhf#V$Pi$~q}qhg(HAYixnMVtHF-l?&IwxE4)FxEA$Wu&6g zmy4m$$3?I%nF)BWBK`yRLPj653N6WM*lJmGGdsK#BvTFnt;Qp=U2pTPqPgMn$(KY1v4AZy>TzEi#>bVeZZ%X28;3; zs#1G?6Zll3*_&XLF%dZ4F->AUipGx2`SH?#yll62o+S#6%Lib6yqAYrEV)&UW_~U!(OCem44?y!ESDUDFDG*)+Qv1r+L_DlTl}!=#VoLPYEYg&9Xw{@+Y6CLHfKFswg*=Qdy4xjs$kiiq9y0da)v|; zQWWWl208w=-z1nYX=+9(Ci3ZCd)MPwQUWo*wzI{5Vn>Y#kqm4J8G->axG zdJORcsVJgEtFN*eJxd-iu{5Ngbg6cKRywda86DExd^IOxt#ec0P>%_C>n60X>l207 zCf@5&{22?bcQ}_B%VjynaaalfL_ER93U6;kB<&~IIuZzuHg*c?h{vDk7o#cylLbX&zRhQ E0P1&EMF0Q* literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-xxhdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb1ee383ed732b028a3ffeb9c3e8b135bea2d8f GIT binary patch literal 1889 zcmZ`)dpy(o8~<)tu954pxn>JR3^_Af+L&$0Vxg2vW^yQ(Tw)ekC|VcSm9SFM9GbG! zIjGj@v^6GHwsZn9x4w2fD)PH zM3o@vN611Xx95U@C;{nUoGT6hp4?E_2#}F*P&Ac@2ly>Ivl3zuNTRv|K*BBnNKOTS zHHnlw0|2p>05EqF0MNGpKr6Da#vLPhfY4lsPQccWbN9u=9ElxBCU0c+cIF=%`&;tKZwgxN!q}(%NdvNVxqm%>VWo zH{@O^m=M|q>0U>P!H;AMp~4A@4k2fA8~^cvxt1Okii`!123d}w$;Dem)H8}9G#D%LDy4McuFyu4EF(l&@iMPf}o~+ z=jIYidch&J_Tsir{-B40)ly2&%eB)o%-c+=PR7jW75b{)I1VWt`YO>^bj!^A)Lrhb zCwtalMavy$KZK3e)qarGVCGzJEEi&{T>GC$gX3j>Ba9=Hwg#rpw+tQejw;hFLgXu4 zNoY%7FTgre`PE!YnezvG3v!QKh7TUpBHN=tW~G@+zMBTD<0RF5<0m?{K9`cUvmXmm z9{6xr#!Z@J;~Do+mzoLeBw=&K4;&^VjWv1lQ~@H47E({PFCE!gbcZ-y zuku%|=sx)iQPsD4|1NCHNK{V=A}2_J&fpqj9K@iXG2FCb$8V*anDXx-ExbnTYRBf5i`s`)# za0ghkj`HPvQ*5W=U^(3(ism(jL+w?{mU;7GBQ2!1&8GxHX)g+%dBTEURnb$bRe!}c z6L`v&`1su$V0Y$pC+9df{{eRGz=ENz4n_kCFTB!HoOz0@+`|g14HFf%^hUn&?S|OO zxZP80`dK?dYbcJr_t%PEV*d(}S~Yw-ZOy%wD3@w|#%k(igdhe#uHlKdOT0syhigO3 z*#}%lkwzTEV)>5I$2hy8QYO%Ki72w%qHD|)3qR?OKF5`3f?VK;J?zUFa5t$k_Uk0& zwbsP_&3aD{_NLUQtJ6Z#IT@U*HZW@qm_YU^yTYSw!BlA!wDS9md{^`82|W|zp>a2! z(rNnQh8cJP*pg1thg{dP!M}Ir(3wvas&9>=#^B5T!6(#NnnS@ODKXzH#&Ix_ZDYzX zQoPx1k=p?eP(FSY(WbY^UMZ;mWNp_!o94RAbgR_Tt%7qKXT5I-#%*`t4{h=Lcq0w! z*)5;w20CiDtKvIcZ)bW$4t47NrLY?cvIQ7-r=* zdH)Ife!0swOV6wNvt|`Rr5}AOy2zt?@_Au#&)`ZX$pjl^*UV4W7pdg$3eDCzD zy2!3CVQ<8r)%h0;S~*F5Je8-#u#$-W)4G@l-qa)Z>}HlZwIu-)j0xv_HcnjnC>J{> zN2fa?@@%P5tP<%Q&$}4dspYjyl7)dOK7B$y+duC(s?I54EOLkGFm8Eu@6OCm@1jY6 zTRnW18Q1u(<{L%5a%a-Ows^H zv))(jak)+WYE8b3X{nq%9_}Jk<0?IP9X>gLaQGtMh#AU1(=VFxo@UCaFWoaAo%;39 zzX=ILva}EISI6Si7_+93D%q0nBT|0RE1orR`vv3qIFM+00&8W1us zMNe78&hZZAG?O>&+30MT2=1MCV0kg8;b5^Y7w`AFdN>ekvM`lqB~!&{znr2@#)c>| z@|kZl2c7W|D&TKBeb0s|+6*43>Y68clQP1$lMF7rzZJWnXf}A}p_<5{&L;t$#X>c$ezTR0H+E^7LRH1 z|A8IaiTb#zUtoXEpL?V^RI*7BF@$3=^njQkbYN7F1OO{bq>Z`d&*oNk9>{%YBob|7 zE1_sh%iz8d+P@585%iE#jQ=)hrUnm74B9^`9Ems;6BiH_1mNfqv>*d=SU_+PH7J12 TINK2PW7Pp<0>z1sKY8&VH{eea literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-xxxhdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..bb093d946b36ca0d76527193c85e3a1c0206c4c1 GIT binary patch literal 2231 zcmbVMc~nzZ8oyasLZJ=d0%*%KWGo=r10l~)2uWCk0EMuXfyqnqfM_<8Kv+h}K{~>4 zpcWAo%QP;uqOv)nR1^bL+&Tgyjz~uZHh+lvj3P?7VQz zpg^xw%p1ZeuSsP{lOU1ah(f{iV49N2W*VzJOI9J=0^$6)dKdNMz#K-wn7$4{YmiFc8rGTKBnnFzqX;oDSac>st6hle zmA1*8jQ)qlJGD)+Vgt%ZMoszxqY|q}uHPaU%iXU#T2RE;z==jRRun`d)+-CNsLmWK z4x?aS=qj}eW^#~RmV(QqaYB{3G%knBqamtXgr-83A$*jpj0)z8Bl)2m{vtM3XEGx?CA#Qajro3!4UPI$EG#mjh*@ux>Ghh$ zE=bDPoAsuAy#W%5Ab%;MRO=SxfQ1^p4q1#E)rF`k%Ba^uuaXR_-$6hbqC!<1o{Gj~ z^FnCc5S9`P9%a%LD3_Vb<1@KT6-s%7SN*@qV_>8h3!U;`owIm_wd2C^?Hpjj+et!o z*!UQ+IT@)ASONfXJXS1}6^%b?ii=4VdYv1J-Ee0O4-v|f#WGpG`qK@Nf(?I6R|itFGy_d1== z@OKDLd2ny(kQb1Nl3H^n(COo?lOp~~j>lKNh$%W92kI*11TcGGcGXTmd^qws1K9uEKZTy8UIBGhxu^B~jsD^` zVEwlFmI5^Ohb!v;(O~F(4~&1fi?p1?`*!QsY_zAUAo?2|ckS^douADeojoRgKCW>{ zS|iwxv$|NX_WS%{_D_Nbq!dF6ovj zuJ=0vo5$p{iWip~@gVNRotVjQuK2m`?()k5)r+JdOm3#A=$PJGcInv8JFn_rk}W7 zIFbFa^;!%9E7Q1W02ofC-a2s-)Vr5=T$?EA4NP@^Lf>AK_f^xz`EBO>Lk#>cqdPy! zP^Hv7@~t7ro$7w3&dRs`=s|nTLT=ht+LuvZ7EiS2P#&%*0AtnzWwT!P71Yy_PS&nV zUiPV#%I(fp3U$+{65NR?>3rntinbpn4Up%B2Sc4#KOIVBT&6X9yASO+lm(i%^}1AF zJ5o!3z^ludV1Xm^FPjN=3w2Y?Ij~L(n3V^1mb%J4>ps~_uzyV5boe$@QJK2lR68J} zfx^8No-4CE(^(C*=oNzw;v7=-XqO)+)|&R)cjW0m1^A&h^@7KNjGD8|2`#`iGTL zye-l{!9u$}J?`5ReE}EYIoXn*zS2%D{2M}UBR>)3;(_Me9+%nHsvQl4^NvWO)fe9V ztoO=k{5&}ORVP7m->m^^J$!Xaa3m3%<9`Ko+OK)$lMdmMZfrk)DsV4Haj#;z z@op)<*HZ%k<7Pn!4qT0uf!d0+wJsn^;^dK)88c@q+#{CAD@y_<-IHxNdy9Byp@$=R z-6=a|An56V!Aujq?@Ytocw>1FMV>V{OUpF^QDXV@ ok-4&A`TzQbcmaZ3J?x8f1nHYC?3~AC{{+~Il_ZGUBIV`(23x>AdH?_b literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_notes_list_note_item.xml b/app/src/main/res/layout/fragment_notes_list_note_item.xml index 5a2c834bb..715f5bed8 100644 --- a/app/src/main/res/layout/fragment_notes_list_note_item.xml +++ b/app/src/main/res/layout/fragment_notes_list_note_item.xml @@ -18,6 +18,7 @@ android:layout_alignParentEnd="false" android:layout_alignParentRight="true" android:layout_alignParentStart="true" + android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_alignWithParentIfMissing="true" android:ellipsize="end" @@ -38,6 +39,14 @@ android:textColor="@drawable/list_item_color_selector_low" android:textSize="14sp"/> + + Date: Fri, 1 Jul 2016 21:45:19 +0200 Subject: [PATCH 03/25] Refactoring: move common code to new private method getNotesRawQuery(String sql, String[] selectionArgs) New method getLocalModifiedNotes() is a preparation for bugfixing #117 --- .../persistence/NoteSQLiteOpenHelper.java | 70 +++++++------------ 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index cb27390f2..409e152ef 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -141,14 +141,15 @@ public DBNote getNote(long id) { } /** - * Returns a list of all Notes in the Database - * - * @return List<Note> + * Query the database with a custom raw query. + * @param sql SQL query + * @param selectionArgs Arguments for the SQL query + * @return List of Notes */ - public List getNotes() { + private List getNotesRawQuery(String sql, String[] selectionArgs) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.rawQuery(sql, selectionArgs); List notes = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle()}); if (cursor.moveToFirst()) { do { Calendar modified = Calendar.getInstance(); @@ -166,30 +167,22 @@ public List getNotes() { return notes; } + /** + * Returns a list of all Notes in the Database + * + * @return List<Note> + */ + public List getNotes() { + return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle()}); + } + /** * Returns a list of all Notes in the Database * * @return List<Note> */ public List searchNotes(String query) { - List notes = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? AND " + key_content + " LIKE ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}); - if (cursor.moveToFirst()) { - do { - Calendar modified = Calendar.getInstance(); - try { - String modifiedStr = cursor.getString(3); - if (modifiedStr != null) - modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); - } catch (ParseException e) { - e.printStackTrace(); - } - notes.add(new DBNote(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4), DBStatus.parse(cursor.getString(1)))); - } while (cursor.moveToNext()); - } - cursor.close(); - return notes; + return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? AND " + key_content + " LIKE ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}); } /** @@ -197,25 +190,16 @@ public List searchNotes(String query) { * * @return List<Note> */ - public List getNotesByStatus(DBStatus status) { - List notes = new ArrayList<>(); - SQLiteDatabase db = this.getWritableDatabase(); - Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " = ?", new String[]{status.getTitle()}); - if (cursor.moveToFirst()) { - do { - Calendar modified = Calendar.getInstance(); - try { - String modifiedStr = cursor.getString(3); - if (modifiedStr != null) - modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); - } catch (ParseException e) { - e.printStackTrace(); - } - notes.add(new Note(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4))); - } while (cursor.moveToNext()); - } - cursor.close(); - return notes; + public List getNotesByStatus(DBStatus status) { + return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " = ?", new String[]{status.getTitle()}); + } + /** + * Returns a list of all Notes in the Database with were modified locally + * + * @return List<Note> + */ + public List getLocalModifiedNotes() { + return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ?", new String[]{DBStatus.VOID.getTitle()}); } /** From 5f455f618e0c9662c491e1eba8f5ea1dc33cd838 Mon Sep 17 00:00:00 2001 From: korelstar Date: Sat, 2 Jul 2016 22:07:32 +0200 Subject: [PATCH 04/25] last part from the previous refactoring --- .../owncloud/notes/persistence/NoteServerSyncHelper.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 019ed6e4f..c6335a673 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -16,6 +16,7 @@ import java.util.List; import it.niedermann.owncloud.notes.android.activity.SettingsActivity; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.util.ICallback; @@ -99,7 +100,7 @@ private void asyncTaskFinished() { } public void uploadEditedNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_EDITED); + List notes = db.getNotesByStatus(DBStatus.LOCAL_EDITED); for (Note note : notes) { UploadEditedNotesTask editedNotesTask = new UploadEditedNotesTask(); editedNotesTask.execute(note); @@ -107,7 +108,7 @@ public void uploadEditedNotes() { } public void uploadNewNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_CREATED); + List notes = db.getNotesByStatus(DBStatus.LOCAL_CREATED); for (Note note : notes) { UploadNewNoteTask newNotesTask = new UploadNewNoteTask(); newNotesTask.execute(note); @@ -115,7 +116,7 @@ public void uploadNewNotes() { } public void uploadDeletedNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_DELETED); + List notes = db.getNotesByStatus(DBStatus.LOCAL_DELETED); for (Note note : notes) { UploadDeletedNoteTask deletedNotesTask = new UploadDeletedNoteTask(); deletedNotesTask.execute(note); From b031f47d33f22a9fa2152e0cfb3e6b0320fb9e98 Mon Sep 17 00:00:00 2001 From: korelstar Date: Tue, 5 Jul 2016 14:49:00 +0200 Subject: [PATCH 05/25] use material design icon and remove old holo icon --- .../drawable-hdpi/ic_sync_alert_black_18dp.png | Bin 0 -> 518 bytes .../res/drawable-hdpi/ic_sync_error_holo.png | Bin 1154 -> 0 bytes .../drawable-mdpi/ic_sync_alert_black_18dp.png | Bin 0 -> 344 bytes .../res/drawable-mdpi/ic_sync_error_holo.png | Bin 790 -> 0 bytes .../drawable-xhdpi/ic_sync_alert_black_18dp.png | Bin 0 -> 658 bytes .../res/drawable-xhdpi/ic_sync_error_holo.png | Bin 1411 -> 0 bytes .../drawable-xxhdpi/ic_sync_alert_black_18dp.png | Bin 0 -> 855 bytes .../res/drawable-xxhdpi/ic_sync_error_holo.png | Bin 1889 -> 0 bytes .../ic_sync_alert_black_18dp.png | Bin 0 -> 1003 bytes .../res/drawable-xxxhdpi/ic_sync_error_holo.png | Bin 2231 -> 0 bytes .../res/layout/fragment_notes_list_note_item.xml | 6 +++--- 11 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-mdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_sync_error_holo.png diff --git a/app/src/main/res/drawable-hdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-hdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d4f7994ef087505aa470ab188a6a8178e290a133 GIT binary patch literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i1|)m0d(}6TtKf~3pEvFe67$-)rm$RcltG)BOghZX5i4k`W zxp7BbnlRt`~km%N(uSznOAJ(U!WoM-${wX^Gnnloyh5TXLE)%%5cm! zFs?2;_lG5IhS?d-zhVvf3*p1j>SfKCo6UJ zFDEekvDp9nKyS_c6Uy2re=bw$I(Y48f6u;<^+jPtdb^nHRDVlMd-~?t-VtD3k zB=@X-{jBnx_UX3|a2JSt=skO8v&XNcXEWMf9@J0U(Qr|JdDwekOfh)6`njxgN@xNA DzE{|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+eS5K)hhiu0R{01Y44~ zy9>jA5L~c#`DCC7XMsm#F_88EW4Dvpb_@*6L7py-ArXh)&all0aTGXS|6MQLV8xF{ zF}Dqz9AR}LVQO9Rt@2wPlTsI-t1mg__Q-|yS*BzR$3xJJtn zsRhhGI7Nh0>KKbHb6gH2tv;8_daQvpL28ESc^R$y8mqa$`T40$-%Ky4Gm1wX()^$7t5nG?!*)-4hjVMp^!KWjiw^A4 zdm#6L;f97;k@C_&IT2>l!d0^v71!>bd_;5Js!_|CG8+k!l&u3RLwZh?BsdgkkAs@96W&X}#RU{@7uZ0V~oA+Nv( zwSqTTW5n3G4u~e`uUjT?WJ;E14e#`8zYnM;OntGRTWdBoRqU6uNPL!m>IPp8OCMwY3BR)IypsPcHaO<6 zWqW?U>l-7yKEr0Mvhthx=H74ZrplYY{$9OpwgA(O%x7^PELsapLlyqBMvMG>p5~YP z*HNS7OTO73_4tNkhrZsQ%5S;aN=-P&RMDkt(w6*%i_UrZ_O%PlGO9_Rr10_WUls|2 zJxWVgehs{xR#M~n*(~JBV_lcGi!+{f@916hYq#xf-$M_!JxW);GFfekP=x#j{~u+N z{1SVGcPJmb)%8RuroFTn$j@`B8R;qD)=AB~Q z&am#(9qR<{g1#O5XC06Azn1oy`{UhDLOXQtg*PTX*z!T};lAVQcMW29e9r0EQ7?6@ zbZM2@(n(4e8nZ=nUK?<%`MSW*EAr-vYdp97Pu{GHcw*JrUsamLcD*KYxz5j(YrJM{ zDYbmEmRstZXRvwaZAs-Fx9jC@v}DVg99kN6!>adrpzEawRcCvVqF34lTo&GU_%&B9 zTxCm^p3Lw_e^tlYnCZak+&bGG9orb|ih)croLsa#h0)uiqB$)$YeMv@-N zXR1zbe=N0gNw#}^&%YDh0UV9{clrfs@;m)^`oHPV<)l`r7v}B{UoKlZrD8QOo2iz# zMwFx^mZVxG7o`Fz1|tJQ6I}yyT_ej7LsKh5Ln{+AAlJ&kApPe3Boqy~`6-!cmAEyC zwWQwzYLEok5S*V@Ql40p%HWuipOmWLnVXoN8kCxtQdxL16;u{5c)I$ztaD0e0svFC B;>!R4 diff --git a/app/src/main/res/drawable-mdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-mdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b4947b7531f4ba404127680aaa435498fd9b7edd GIT binary patch literal 344 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|wj^(N7a$D;Kb?2i11Zh|kH}&m z?E%JaC$sH9f@KAc=|CE+pF!Co&kM+T?CIhdVsZNEWJA6t1BurCD-zWnvh}&KOC38TKvFd|AC`-~PYc_3f$~%GQKCtp3O# zX4lMM;ZgA?aSEU6P46v}G!KL=HeS^z9`M6Qh_kFuWa>Wesgs#`1$OrsJ>Ya!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ueoXKL{?^yL>WGgtNdSvKUBvfU(=jY&)Rw&pcfmLp+Y3opw@>B~YYo|D|VQSLa6D z_{bn8mRrGpk2$(bNO29v`y}0O+f>?EIg`57STa)!|4+7yUA0R^sc`Z+&%4Hd()aP! z*DPwvw>}wfJ*nL5*I$h$4hgon;5{rAIvtBxYPL;&DKK~X1?B?oD~-kqWkqV%9BY_N zkDvA~>^^>)DYN4qbEtZ9k*MV)hG>U;w=g5dQwJ7`2tV*UCJ=CyaV^97Nw-<6ZZ$@{ zxE-`3P=x;g!v+2rhUZ@Wa|<81SXpaE-|zAF_`oZ{JguSJHFVl`|8;727nweA-8DH< z;ab*gp<4xeSQ9v2O#ZlwxkKi}uGQ_^Z%$gz%<21j_vuQZ(-l$&egt2#{ZRTfk%wt< zi>1Z2*yi_}C+RKt`fp{4drhxZX1!3l!o=?{Z&l7T$;j(kX2d0WQD@urmqK&Tc?Uk? z+N5taS$JW8n#2rdyApHlUFuPvCeBzsc~{NColI-IlOAyGP&?x?^TN6AmT7g9-?_Mb z(G@q#P;XH@#291Mz<5Z&jKle6*@YV2Dvsp&0&LkwKPJu6jZ$1AsQqc8!{ySmEH71g zdUCIIJ)Z3|bpyxWq!>LWgJrD|%-1r$ZH>Qfm^k%1=jyv=ujlv&hcH~yC{S7UHL+r8Vb{*JddJqh7VdHKHUXu_Vl#^x7@Arc8d{l{0l8KN2I)8NC!uJ_%}>cptHiBAtR?*(P=h4MhT#0PlJdl&R0hYC h{G?O`&)mfH)S%SFl*+=Bsi5@9;OXk;vd$@?2>|L1FDL*2 diff --git a/app/src/main/res/drawable-xhdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-xhdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..031a5c40ccb183a427f898234d736d927e18c3b5 GIT binary patch literal 658 zcmV;D0&V??P) zCUFiI5^V3F-RIdxGw(E!NTh*fe8f6V51H5}oXy@n*q7kEA5U;;ND`G~QV1QyD;$mb zcJO`(LRZSjRACyMx;1ZM6*D=z6S$o{aVg8z&^o5-5PA>=`HiI#yZFcVO8UQGFZ#Y) z_S3ROeFQ7f_f0&lla)}OQ@MC6m>q=BEn$npORNn+rn7(_;dRzOEtEJ3li1ar-h~SL zvXU+PS7#O@+piL3XI`&(RiTVf5gNjJ>e$ca4T=~Zi`s8er!^66-LetZY9*lqQTyA7 z2@y)w!Qm7_?TYbX(TduQ{9ut%_syUH?*z>vsCpF+MeUuG4CeF-S|i120BlF`=TjUe zvRUdlE#}6XxK@+QLKJsbSL-UV7CFXcXl5D4+q>*6E5eH??&Co+nZ>Wj`9?&h-Dri!3kgOss;bAV`)}TSAgsVc)6(XLAGq|5U`K#?r_VZTc zyD6MywQxLvP#G@bN6znqaOo>WKjCZQS=~&8BPdq==QxS)bqF=Fq5hhQv4yL+jGtA; sMM^y?GH-ESkc~ehNNkKT#uy3y0hHSyBJs6?qpkD^L|X5*AA0sSD4MX5!M!!$0j!v<#oCw?mEIr z(X7W3nS1jJJ>)of)Dn?GO+tyf>;8BDxaWJm-_QAczUTA({P!&kq){~0cB%mYG<~RK zx&rY(T2)yw!=PXC6#!>@1b6_@dQN>aR!PAjN9Yt!(DDfNRzXbTsPq5;j_(41p9jE( zLgLQ@z*Ag#7XyG}B>+28FWwDuRxFemeiSnJ{-cEV8p{<5B9$6(M1jPLibmlo;1-mR2D+AcCT7`uIMWP`PJ>zu=oY@d zz;^d6Ay1dUn*+NssgP~fy;{9%L{!u1D~anvYl}37_e5Ewi<3)KNKQx?k7w z5#y7VoFuQU9tpTuP41m9yFoKfT>C~6m4L3^Il+#uovE7PoZf{eP}bSuu%_27*u}|` zzx@Irn@bwY+HzZb<4ki;KH)C)xgO8BKX)wrX~}j*w%s(0j>Dj_hRDl`YjkU-%gBd-`7f} zyXfp4(-oTZZ?<57o=DXhf#V$Pi$~q}qhg(HAYixnMVtHF-l?&IwxE4)FxEA$Wu&6g zmy4m$$3?I%nF)BWBK`yRLPj653N6WM*lJmGGdsK#BvTFnt;Qp=U2pTPqPgMn$(KY1v4AZy>TzEi#>bVeZZ%X28;3; zs#1G?6Zll3*_&XLF%dZ4F->AUipGx2`SH?#yll62o+S#6%Lib6yqAYrEV)&UW_~U!(OCem44?y!ESDUDFDG*)+Qv1r+L_DlTl}!=#VoLPYEYg&9Xw{@+Y6CLHfKFswg*=Qdy4xjs$kiiq9y0da)v|; zQWWWl208w=-z1nYX=+9(Ci3ZCd)MPwQUWo*wzI{5Vn>Y#kqm4J8G->axG zdJORcsVJgEtFN*eJxd-iu{5Ngbg6cKRywda86DExd^IOxt#ec0P>%_C>n60X>l207 zCf@5&{22?bcQ}_B%VjynaaalfL_ER93U6;kB<&~IIuZzuHg*c?h{vDk7o#cylLbX&zRhQ E0P1&EMF0Q* diff --git a/app/src/main/res/drawable-xxhdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-xxhdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..83cf8b06c40dc57250529a7aed921eb17cfc9170 GIT binary patch literal 855 zcmV-d1E~CoP)r zt5ogbD-F70aE)|q0jEs{JY)#=6u73@egXG@7p`ZqtGC@8QQZKZxJpn)t&D!p&l&pL ztk}^;Q5Ae!z>Ev91tIui2LG&J`tG+Z_%G#!8V8zM&@aH4CopMCKMU+@{y%}yw4}YB zvHQe#u;;*!yfY?f4Rw3BHG30yrrFM5{wRp5X*2a3n8+BzWW`FPzkH{FLoLQiTGC!C zc3V4t4}8lrQwiE5zEV-`c5_E{(-y1%T+nQwU}%gKGhA3!DHDIMnSd2B-#n&Ktvo4i1JwfbqFLhiyo*loBJR3^_Af+L&$0Vxg2vW^yQ(Tw)ekC|VcSm9SFM9GbG! zIjGj@v^6GHwsZn9x4w2fD)PH zM3o@vN611Xx95U@C;{nUoGT6hp4?E_2#}F*P&Ac@2ly>Ivl3zuNTRv|K*BBnNKOTS zHHnlw0|2p>05EqF0MNGpKr6Da#vLPhfY4lsPQccWbN9u=9ElxBCU0c+cIF=%`&;tKZwgxN!q}(%NdvNVxqm%>VWo zH{@O^m=M|q>0U>P!H;AMp~4A@4k2fA8~^cvxt1Okii`!123d}w$;Dem)H8}9G#D%LDy4McuFyu4EF(l&@iMPf}o~+ z=jIYidch&J_Tsir{-B40)ly2&%eB)o%-c+=PR7jW75b{)I1VWt`YO>^bj!^A)Lrhb zCwtalMavy$KZK3e)qarGVCGzJEEi&{T>GC$gX3j>Ba9=Hwg#rpw+tQejw;hFLgXu4 zNoY%7FTgre`PE!YnezvG3v!QKh7TUpBHN=tW~G@+zMBTD<0RF5<0m?{K9`cUvmXmm z9{6xr#!Z@J;~Do+mzoLeBw=&K4;&^VjWv1lQ~@H47E({PFCE!gbcZ-y zuku%|=sx)iQPsD4|1NCHNK{V=A}2_J&fpqj9K@iXG2FCb$8V*anDXx-ExbnTYRBf5i`s`)# za0ghkj`HPvQ*5W=U^(3(ism(jL+w?{mU;7GBQ2!1&8GxHX)g+%dBTEURnb$bRe!}c z6L`v&`1su$V0Y$pC+9df{{eRGz=ENz4n_kCFTB!HoOz0@+`|g14HFf%^hUn&?S|OO zxZP80`dK?dYbcJr_t%PEV*d(}S~Yw-ZOy%wD3@w|#%k(igdhe#uHlKdOT0syhigO3 z*#}%lkwzTEV)>5I$2hy8QYO%Ki72w%qHD|)3qR?OKF5`3f?VK;J?zUFa5t$k_Uk0& zwbsP_&3aD{_NLUQtJ6Z#IT@U*HZW@qm_YU^yTYSw!BlA!wDS9md{^`82|W|zp>a2! z(rNnQh8cJP*pg1thg{dP!M}Ir(3wvas&9>=#^B5T!6(#NnnS@ODKXzH#&Ix_ZDYzX zQoPx1k=p?eP(FSY(WbY^UMZ;mWNp_!o94RAbgR_Tt%7qKXT5I-#%*`t4{h=Lcq0w! z*)5;w20CiDtKvIcZ)bW$4t47NrLY?cvIQ7-r=* zdH)Ife!0swOV6wNvt|`Rr5}AOy2zt?@_Au#&)`ZX$pjl^*UV4W7pdg$3eDCzD zy2!3CVQ<8r)%h0;S~*F5Je8-#u#$-W)4G@l-qa)Z>}HlZwIu-)j0xv_HcnjnC>J{> zN2fa?@@%P5tP<%Q&$}4dspYjyl7)dOK7B$y+duC(s?I54EOLkGFm8Eu@6OCm@1jY6 zTRnW18Q1u(<{L%5a%a-Ows^H zv))(jak)+WYE8b3X{nq%9_}Jk<0?IP9X>gLaQGtMh#AU1(=VFxo@UCaFWoaAo%;39 zzX=ILva}EISI6Si7_+93D%q0nBT|0RE1orR`vv3qIFM+00&8W1us zMNe78&hZZAG?O>&+30MT2=1MCV0kg8;b5^Y7w`AFdN>ekvM`lqB~!&{znr2@#)c>| z@|kZl2c7W|D&TKBeb0s|+6*43>Y68clQP1$lMF7rzZJWnXf}A}p_<5{&L;t$#X>c$ezTR0H+E^7LRH1 z|A8IaiTb#zUtoXEpL?V^RI*7BF@$3=^njQkbYN7F1OO{bq>Z`d&*oNk9>{%YBob|7 zE1_sh%iz8d+P@585%iE#jQ=)hrUnm74B9^`9Ems;6BiH_1mNfqv>*d=SU_+PH7J12 TINK2PW7Pp<0>z1sKY8&VH{eea diff --git a/app/src/main/res/drawable-xxxhdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-xxxhdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..768785eac639c0234b45e1511d5681b697175e57 GIT binary patch literal 1003 zcmVX5XGK^Jd9|A4Y0By{oU9_rLZsK3I4 z#|~Z06cp*yL8wDQH#IC>5Vuyh!xV_TZ}xNdo1NX5AH2)=eBaFc-Z*dGyje&j5{X12 zkw_#GiNycRHN#w0gQFN*2tYSb09JuY3kixYrw@1q9GqR5_El0a#Pf7=oh9IjvE_Q{XKy9zoCvFcC}61hsq4Ixrld z?2GiEV<6vbIRik+Q>ilW4CrXE!4`t5zz)#gEID1kM^9z;x%eFmEvO2tpub@aG3Tko z9xzl#PAfrgfL+h|LTEWRkpH#Hz-XO$Ed&++p1X@1cck9k8&c4Mr!mtFP#`zquEad!&dx6Qf3ig!s+atyj>{_^B22MTie7udhW;I4qFf6Spk|eHP*mj2aP=5h1>%xmYV` zU5G!z?oNjw2RszcZCDo6i@bE8?M3qu1dR!GJ|(~+{+dwOqEb$bceW*=&ZfQRA5+Ln z2fl^+d2AZn-k6z-6+>qIq;PW9yl84!Or4Vf>nLmY1H!%+!$6 zOhMD0oC~4kfE%p$g@TW9pIjt4%@Q<KEils$P$C>O{q$s>Q_4Y|H^mSNm1 z-&baZ$a{pO8FolP4 zpcWAo%QP;uqOv)nR1^bL+&Tgyjz~uZHh+lvj3P?7VQz zpg^xw%p1ZeuSsP{lOU1ah(f{iV49N2W*VzJOI9J=0^$6)dKdNMz#K-wn7$4{YmiFc8rGTKBnnFzqX;oDSac>st6hle zmA1*8jQ)qlJGD)+Vgt%ZMoszxqY|q}uHPaU%iXU#T2RE;z==jRRun`d)+-CNsLmWK z4x?aS=qj}eW^#~RmV(QqaYB{3G%knBqamtXgr-83A$*jpj0)z8Bl)2m{vtM3XEGx?CA#Qajro3!4UPI$EG#mjh*@ux>Ghh$ zE=bDPoAsuAy#W%5Ab%;MRO=SxfQ1^p4q1#E)rF`k%Ba^uuaXR_-$6hbqC!<1o{Gj~ z^FnCc5S9`P9%a%LD3_Vb<1@KT6-s%7SN*@qV_>8h3!U;`owIm_wd2C^?Hpjj+et!o z*!UQ+IT@)ASONfXJXS1}6^%b?ii=4VdYv1J-Ee0O4-v|f#WGpG`qK@Nf(?I6R|itFGy_d1== z@OKDLd2ny(kQb1Nl3H^n(COo?lOp~~j>lKNh$%W92kI*11TcGGcGXTmd^qws1K9uEKZTy8UIBGhxu^B~jsD^` zVEwlFmI5^Ohb!v;(O~F(4~&1fi?p1?`*!QsY_zAUAo?2|ckS^douADeojoRgKCW>{ zS|iwxv$|NX_WS%{_D_Nbq!dF6ovj zuJ=0vo5$p{iWip~@gVNRotVjQuK2m`?()k5)r+JdOm3#A=$PJGcInv8JFn_rk}W7 zIFbFa^;!%9E7Q1W02ofC-a2s-)Vr5=T$?EA4NP@^Lf>AK_f^xz`EBO>Lk#>cqdPy! zP^Hv7@~t7ro$7w3&dRs`=s|nTLT=ht+LuvZ7EiS2P#&%*0AtnzWwT!P71Yy_PS&nV zUiPV#%I(fp3U$+{65NR?>3rntinbpn4Up%B2Sc4#KOIVBT&6X9yASO+lm(i%^}1AF zJ5o!3z^ludV1Xm^FPjN=3w2Y?Ij~L(n3V^1mb%J4>ps~_uzyV5boe$@QJK2lR68J} zfx^8No-4CE(^(C*=oNzw;v7=-XqO)+)|&R)cjW0m1^A&h^@7KNjGD8|2`#`iGTL zye-l{!9u$}J?`5ReE}EYIoXn*zS2%D{2M}UBR>)3;(_Me9+%nHsvQl4^NvWO)fe9V ztoO=k{5&}ORVP7m->m^^J$!Xaa3m3%<9`Ko+OK)$lMdmMZfrk)DsV4Haj#;z z@op)<*HZ%k<7Pn!4qT0uf!d0+wJsn^;^dK)88c@q+#{CAD@y_<-IHxNdy9Byp@$=R z-6=a|An56V!Aujq?@Ytocw>1FMV>V{OUpF^QDXV@ ok-4&A`TzQbcmaZ3J?x8f1nHYC?3~AC{{+~Il_ZGUBIV`(23x>AdH?_b diff --git a/app/src/main/res/layout/fragment_notes_list_note_item.xml b/app/src/main/res/layout/fragment_notes_list_note_item.xml index 715f5bed8..2c38a8a59 100644 --- a/app/src/main/res/layout/fragment_notes_list_note_item.xml +++ b/app/src/main/res/layout/fragment_notes_list_note_item.xml @@ -41,11 +41,11 @@ + android:src="@drawable/ic_sync_alert_black_18dp"/> From 1acb0340a6ed0fcd6d42300ebfef4ebd5380a164 Mon Sep 17 00:00:00 2001 From: korelstar Date: Sun, 10 Jul 2016 14:37:08 +0200 Subject: [PATCH 06/25] Refactoring NoteSQLiteOpenHelper and NoteServerSyncHelper in order to fix several bugs belonging concurrency and synchronization. Outstanding: documentation, testing, cosmetic changes --- .../owncloud/notes/model/NoteTest.java | 2 +- .../android/activity/EditNoteActivity.java | 5 +- .../notes/android/activity/NoteActivity.java | 10 +- .../activity/NotesListViewActivity.java | 20 +- .../activity/SelectSingleNoteActivity.java | 5 +- .../notes/android/widget/AllNotesWidget.java | 7 +- .../android/widget/SingleNoteWidget.java | 4 +- .../owncloud/notes/model/DBNote.java | 41 +- .../owncloud/notes/model/DBStatus.java | 24 +- .../model/{Note.java => OwnCloudNote.java} | 39 +- .../persistence/NoteSQLiteOpenHelper.java | 256 +++++++------ .../persistence/NoteServerSyncHelper.java | 349 ++++++++++-------- .../owncloud/notes/util/NotesClient.java | 20 +- 13 files changed, 444 insertions(+), 338 deletions(-) rename app/src/main/java/it/niedermann/owncloud/notes/model/{Note.java => OwnCloudNote.java} (59%) diff --git a/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java b/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java index 6d6f2dece..c98281312 100644 --- a/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java +++ b/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java @@ -11,7 +11,7 @@ public class NoteTest extends TestCase { public void testMarkDownStrip() { - Note note = new Note(0, Calendar.getInstance(), "#Title", ""); + OwnCloudNote note = new OwnCloudNote(0, Calendar.getInstance(), "#Title", ""); assertTrue("Title".equals(note.getTitle())); note.setTitle("* Aufzählung"); assertTrue("Aufzählung".equals(note.getTitle())); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index e04959e88..edfc27b09 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -17,7 +17,6 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.model.DBNote; -import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; @@ -106,7 +105,7 @@ public boolean onOptionsItemSelected(MenuItem item) { private void saveData() { ActionBar ab = getSupportActionBar(); if (ab != null) { - ab.setSubtitle(getResources().getString(R.string.action_edit_saving)); + ab.setSubtitle(getString(R.string.action_edit_saving)); } // #74 note.setModified(Calendar.getInstance()); @@ -127,7 +126,7 @@ public void run() { runOnUiThread(new Runnable() { @Override public void run() { - getSupportActionBar().setSubtitle(null); + getSupportActionBar().setSubtitle(getString(R.string.action_edit_editing)); } }); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java index c1c2f22d2..f93a98278 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java @@ -12,13 +12,13 @@ import android.widget.TextView; import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; public class NoteActivity extends AppCompatActivity implements View.OnClickListener { public final static String EDIT_NOTE = "it.niedermann.owncloud.notes.edit_note_id"; public final static int EDIT_NOTE_CMD = 1; - private Note note = null; + private DBNote note = null; private int notePosition = 0; private TextView noteContent = null; private ActionBar actionBar = null; @@ -27,10 +27,10 @@ public class NoteActivity extends AppCompatActivity implements View.OnClickListe protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_note); - note = (Note) getIntent().getSerializableExtra( + note = (DBNote) getIntent().getSerializableExtra( NotesListViewActivity.SELECTED_NOTE); if (savedInstanceState != null) { - note = (Note) savedInstanceState.getSerializable("note"); + note = (DBNote) savedInstanceState.getSerializable("note"); } notePosition = getIntent().getIntExtra( NotesListViewActivity.SELECTED_NOTE_POSITION, 0); @@ -114,7 +114,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == EDIT_NOTE_CMD) { // Make sure the request was successful if (resultCode == RESULT_OK) { - Note editedNote = (Note) data.getExtras().getSerializable( + DBNote editedNote = (DBNote) data.getExtras().getSerializable( EDIT_NOTE); if (editedNote != null) { note = editedNote; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index db3af0e8e..a708446ac 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -12,6 +12,7 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -24,7 +25,6 @@ import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; -import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.model.SectionItem; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.ICallback; @@ -82,7 +82,7 @@ public void onFinish() { setListView(db.getNotes()); } }); - db.getNoteServerSyncHelper().downloadNotes(); + db.getNoteServerSyncHelper().scheduleSync(false); } }); @@ -103,11 +103,12 @@ public void onFinish() { if (mActionMode != null) { mActionMode.finish(); } - adapter.checkForUpdates(db.getNotes()); + // adapter.checkForUpdates(db.getNotes()); // FIXME deactivated, since it doesn't remove remotely deleted notes + setListView(db.getNotes()); } }); if (!PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean(SettingsActivity.SETTINGS_FIRST_RUN, true)) { - db.getNoteServerSyncHelper().downloadNotes(); + db.getNoteServerSyncHelper().scheduleSync(false); } super.onResume(); } @@ -132,6 +133,9 @@ public void onClick(View v) { */ @SuppressWarnings("WeakerAccess") public void setListView(List noteList) { + Log.d(getClass().getSimpleName(), "setListView("+noteList.size()+" notes)"); + //db.debugPrintFullDB(); // FIXME remove + List itemList = new ArrayList<>(); // #12 Create Sections depending on Time // TODO Move to ItemAdapter? @@ -162,7 +166,7 @@ public void setListView(List noteList) { month.set(Calendar.SECOND, 0); month.set(Calendar.MILLISECOND, 0); for (int i = 0; i < noteList.size(); i++) { - Note currentNote = noteList.get(i); + DBNote currentNote = noteList.get(i); if (!todaySet && recent.getTimeInMillis() - currentNote.getModified().getTimeInMillis() >= 600000 && currentNote.getModified().getTimeInMillis() >= today.getTimeInMillis()) { // < 10 minutes but after 00:00 today //if (i > 0) { @@ -319,7 +323,7 @@ public void onFinish() { swipeRefreshLayout.setRefreshing(false); } }); - db.synchronizeWithServer(); + db.getNoteServerSyncHelper().scheduleSync(false); } } @@ -355,7 +359,7 @@ public void onNoteClick(int position, View v) { NoteActivity.class); Item item = adapter.getItem(position); - intent.putExtra(SELECTED_NOTE, (Note) item); + intent.putExtra(SELECTED_NOTE, (DBNote) item); intent.putExtra(SELECTED_NOTE_POSITION, position); startActivityForResult(intent, show_single_note_cmd); @@ -413,7 +417,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { case R.id.menu_delete: List selection = adapter.getSelected(); for (Integer i : selection) { - Note note = (Note) adapter.getItem(i); + DBNote note = (DBNote) adapter.getItem(i); db.deleteNoteAndSync(note.getId()); // Not needed because of dbsync //adapter.remove(note); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java index b9bb6d7b0..0b171f1d1 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java @@ -18,7 +18,6 @@ import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; -import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; /** @@ -39,7 +38,7 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_select_single_note); // Display Data db = new NoteSQLiteOpenHelper(this); - db.synchronizeWithServer(); + db.getNoteServerSyncHelper().scheduleSync(false); setListView(db.getNotes()); @@ -80,7 +79,7 @@ public void onNoteClick(int position, View v) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_single_note); appWidgetManager.updateAppWidget(appWidgetId, views); - SingleNoteWidget.updateAppWidget((Note) adapter.getItem(position), context, appWidgetManager, appWidgetId); + SingleNoteWidget.updateAppWidget((DBNote) adapter.getItem(position), context, appWidgetManager, appWidgetId); Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java index 83a84f785..726c335f9 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java @@ -15,7 +15,6 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; -import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; /** @@ -62,13 +61,13 @@ public StackRemoteViewsFactory(Context context, Intent intent) { mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(mContext); - db.synchronizeWithServer(); + db.getNoteServerSyncHelper().scheduleSync(false); mWidgetItems = db.getNotes(); - mWidgetItems.add(new DBNote(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); + mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); } public void onCreate() { - mWidgetItems.add(new DBNote(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); + mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); } @Override diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/SingleNoteWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/SingleNoteWidget.java index d2c695629..26593ff4b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/SingleNoteWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/SingleNoteWidget.java @@ -12,7 +12,7 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.NoteActivity; import it.niedermann.owncloud.notes.android.activity.NotesListViewActivity; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.DBNote; /** * Widget which displays a single selected note. @@ -20,7 +20,7 @@ */ public class SingleNoteWidget extends AppWidgetProvider { - public static void updateAppWidget(Note note, Context context, AppWidgetManager appWidgetManager, int appWidgetId) { + public static void updateAppWidget(DBNote note, Context context, AppWidgetManager appWidgetManager, int appWidgetId) { RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_single_note); if (note != null) { updateViews.setTextViewText(R.id.single_note_content, note.getSpannableContent()); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java index 646d869c2..78fddc134 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java @@ -1,18 +1,28 @@ package it.niedermann.owncloud.notes.model; +import java.io.Serializable; import java.util.Calendar; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; +import it.niedermann.owncloud.notes.util.NoteUtil; -public class DBNote extends Note { +public class DBNote extends OwnCloudNote implements Item, Serializable { + private long id; private DBStatus status; + private String excerpt = ""; - public DBNote(long id, Calendar modified, String title, String content, DBStatus status) { - super(id, modified, title, content); + public DBNote(long id, long remoteId, Calendar modified, String title, String content, DBStatus status) { + super(remoteId, modified, title, content); + this.id = id; + setExcerpt(content); this.status = status; } + public long getId() { + return id; + } + public DBStatus getStatus() { return status; } @@ -21,8 +31,31 @@ public void setStatus(DBStatus status) { this.status = status; } + public String getExcerpt() { + return excerpt; + } + + private void setExcerpt(String content) { + excerpt = NoteUtil.generateNoteExcerpt(content); + } + + public CharSequence getSpannableContent() { + // TODO Cache the generated CharSequence not possible because CharSequence does not implement Serializable + return NoteUtil.parseMarkDown(getContent()); + } + + public void setContent(String content) { + super.setContent(content); + setExcerpt(content); + } + + @Override + public boolean isSection() { + return false; + } + @Override public String toString() { - return "#" + getId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); + return "#" + getId() + "/R"+getRemoteId()+" " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java index ea3393b31..46e1b195d 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java @@ -6,7 +6,29 @@ */ public enum DBStatus { - VOID(""), LOCAL_CREATED("LOCAL_CREATED"), LOCAL_EDITED("LOCAL_EDITED"), LOCAL_DELETED("LOCAL_DELETED"); + /** + * VOID means, that the Note was not modified locally + */ + VOID(""), + + /** + * LOCAL_CREATED is not used anymore, since a newly created note has REMOTE_ID=0 + */ + @Deprecated + LOCAL_CREATED("LOCAL_CREATED"), + + /** + * LOCAL_EDITED means that a Note was created and/or changed since the last successful synchronization. + * If it was newly created, then REMOTE_ID is 0 + */ + LOCAL_EDITED("LOCAL_EDITED"), + + /** + * LOCAL_DELETED means that the Note was deleted locally, but this information was not yet synchronized. + * Therefore, the Note have to be kept locally until the synchronization has succeeded. + * However, Notes with this status should not be displayed in the UI. + */ + LOCAL_DELETED("LOCAL_DELETED"); private final String title; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/Note.java b/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java similarity index 59% rename from app/src/main/java/it/niedermann/owncloud/notes/model/Note.java rename to app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java index 4faf03a17..3021fd52b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/Note.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java @@ -8,16 +8,14 @@ import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.NoteUtil; -@SuppressWarnings("serial") -public class Note implements Item, Serializable { - private long id = 0; +public class OwnCloudNote implements Serializable { + private long remoteId = 0; private String title = ""; private Calendar modified = null; private String content = ""; - private String excerpt = ""; - public Note(long id, Calendar modified, String title, String content) { - this.id = id; + public OwnCloudNote(long remoteId, Calendar modified, String title, String content) { + this.remoteId = remoteId; if (title != null) setTitle(title); setTitle(title); @@ -25,8 +23,12 @@ public Note(long id, Calendar modified, String title, String content) { this.modified = modified; } - public long getId() { - return id; + public long getRemoteId() { + return remoteId; + } + + public void setRemoteId(long remoteId) { + this.remoteId = remoteId; } public String getTitle() { @@ -56,30 +58,11 @@ public String getContent() { } public void setContent(String content) { - setExcerpt(content); this.content = content; } - public String getExcerpt() { - return excerpt; - } - - private void setExcerpt(String content) { - excerpt = NoteUtil.generateNoteExcerpt(content); - } - - public CharSequence getSpannableContent() { - // TODO Cache the generated CharSequence not possible because CharSequence does not implement Serializable - return NoteUtil.parseMarkDown(getContent()); - } - @Override public String toString() { - return "#" + getId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ")"; - } - - @Override - public boolean isSection() { - return false; + return "#" + getRemoteId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ")"; } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 409e152ef..6a7ae6e7b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -5,6 +5,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -15,7 +16,7 @@ import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.OwnCloudNote; import it.niedermann.owncloud.notes.util.NoteUtil; /** @@ -26,25 +27,28 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - private static final int database_version = 4; + private static final int database_version = 5; private static final String database_name = "OWNCLOUD_NOTES"; private static final String table_notes = "NOTES"; private static final String key_id = "ID"; + private static final String key_remote_id = "REMOTEID"; private static final String key_status = "STATUS"; private static final String key_title = "TITLE"; private static final String key_modified = "MODIFIED"; private static final String key_content = "CONTENT"; - private static final String[] columns = {key_id, key_status, key_title, key_modified, key_content}; + private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_content}; + @Deprecated private NoteServerSyncHelper serverSyncHelper = null; private Context context = null; public NoteSQLiteOpenHelper(Context context) { super(context, database_name, null, database_version); - this.context = context; - serverSyncHelper = new NoteServerSyncHelper(this); + this.context = context.getApplicationContext(); + serverSyncHelper = NoteServerSyncHelper.getInstance(this); } + @Deprecated public NoteServerSyncHelper getNoteServerSyncHelper() { return serverSyncHelper; } @@ -58,10 +62,32 @@ public NoteServerSyncHelper getNoteServerSyncHelper() { public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE '" + table_notes + "' ( '" + key_id + "' INTEGER PRIMARY KEY AUTOINCREMENT, '" + + key_remote_id + "' INTEGER, '" + key_status + "' VARCHAR(50), '" + key_title + "' TEXT, '" + key_modified + "' TEXT, '" + key_content + "' TEXT)"); + // FIXME create index for status and remote_id + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if(oldVersion<4) { + clearDatabase(db); + } + if(oldVersion<5) { + db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_remote_id+" INTEGER"); + db.execSQL("UPDATE "+table_notes+" SET "+key_remote_id+"="+key_id+" WHERE ("+key_remote_id+" IS NULL OR "+key_remote_id+"=0) AND "+key_status+"!=?", new String[]{DBStatus.LOCAL_CREATED.getTitle()}); + db.execSQL("UPDATE "+table_notes+" SET "+key_remote_id+"=0, "+key_status+"=? WHERE "+key_status+"=?", new String[]{DBStatus.LOCAL_EDITED.getTitle(), DBStatus.LOCAL_CREATED.getTitle()}); + } + } + + private void clearDatabase(SQLiteDatabase db) { + db.delete(table_notes, null, null); + } + + public Context getContext() { + return context; } /** @@ -71,18 +97,9 @@ public void onCreate(SQLiteDatabase db) { */ @SuppressWarnings("UnusedReturnValue") public long addNoteAndSync(String content) { - SQLiteDatabase db = this.getWritableDatabase(); - - ContentValues values = new ContentValues(); - values.put(key_status, DBStatus.LOCAL_CREATED.getTitle()); - values.put(key_title, NoteUtil.generateNoteTitle(content)); - values.put(key_content, content); - - long id = db.insert(table_notes, - null, - values); - db.close(); - serverSyncHelper.uploadNewNotes(); + DBNote note = new DBNote(0, 0, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, DBStatus.LOCAL_EDITED); + long id = addNote(note); + getNoteServerSyncHelper().scheduleSync(true); return id; } @@ -90,20 +107,29 @@ public long addNoteAndSync(String content) { * Inserts a note directly into the Database. * No Synchronisation will be triggered! Use addNoteAndSync()! * - * @param note Note to be added + * @param note Note to be added. Remotely created Notes must be of type OwnCloudNote and locally created Notes must be of Type DBNote (with DBStatus.LOCAL_EDITED)! */ - public void addNote(Note note) { + long addNote(OwnCloudNote note) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(NoteSQLiteOpenHelper.key_id, note.getId()); - values.put(NoteSQLiteOpenHelper.key_status, DBStatus.VOID.getTitle()); - values.put(NoteSQLiteOpenHelper.key_title, note.getTitle()); - values.put(NoteSQLiteOpenHelper.key_modified, note.getModified(NoteSQLiteOpenHelper.DATE_FORMAT)); - values.put(NoteSQLiteOpenHelper.key_content, note.getContent()); - db.insert(NoteSQLiteOpenHelper.table_notes, - null, - values); + if(note instanceof DBNote) { + DBNote dbNote = (DBNote)note; + if (dbNote.getId() > 0) { + values.put(key_id, dbNote.getId()); + } + values.put(key_status, dbNote.getStatus().getTitle()); + } else { + values.put(key_status, DBStatus.VOID.getTitle()); + } + if(note.getRemoteId()>0) { + values.put(key_remote_id, note.getRemoteId()); + } + values.put(key_title, note.getTitle()); + values.put(key_modified, note.getModified(NoteSQLiteOpenHelper.DATE_FORMAT)); + values.put(key_content, note.getContent()); + long id = db.insert(table_notes, null, values); db.close(); + return id; } /** @@ -124,56 +150,67 @@ public DBNote getNote(long id) { null, null, null); - if (cursor != null) { - cursor.moveToFirst(); + if (cursor == null) { + return null; } - Calendar modified = Calendar.getInstance(); - try { - String modifiedStr = cursor != null ? cursor.getString(3) : null; - if (modifiedStr != null) - modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); - } catch (ParseException e) { - e.printStackTrace(); - } - DBNote note = new DBNote(Long.valueOf(cursor != null ? cursor.getString(0) : null), modified, cursor != null ? cursor.getString(2) : null, cursor.getString(4), DBStatus.parse(cursor.getString(1))); + cursor.moveToFirst(); + DBNote note = getNoteFromCursor(cursor); cursor.close(); return note; } /** * Query the database with a custom raw query. - * @param sql SQL query - * @param selectionArgs Arguments for the SQL query + * @param selection A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). + * @param selectionArgs You may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered. * @return List of Notes */ - private List getNotesRawQuery(String sql, String[] selectionArgs) { + private List getNotesCustom(String selection, String[] selectionArgs, String orderBy) { SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = db.rawQuery(sql, selectionArgs); + Cursor cursor = db.query(table_notes, columns, selection, selectionArgs, null, null, orderBy); List notes = new ArrayList<>(); if (cursor.moveToFirst()) { do { - Calendar modified = Calendar.getInstance(); - try { - String modifiedStr = cursor.getString(3); - if (modifiedStr != null) - modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); - } catch (ParseException e) { - e.printStackTrace(); - } - notes.add(new DBNote(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4), DBStatus.parse(cursor.getString(1)))); + notes.add(getNoteFromCursor(cursor)); } while (cursor.moveToNext()); } cursor.close(); return notes; } + /** + * Creates a DBNote object from the current row of a Cursor. + * @param cursor database cursor + * @return DBNote + */ + private DBNote getNoteFromCursor(Cursor cursor) { + Calendar modified = Calendar.getInstance(); + try { + String modifiedStr = cursor.getString(4); + if (modifiedStr != null) + modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); + } catch (ParseException e) { + e.printStackTrace(); + } + return new DBNote(cursor.getLong(0), cursor.getLong(1), modified, cursor.getString(3), cursor.getString(5), DBStatus.parse(cursor.getString(2))); + } + + public void debugPrintFullDB() { + List notes = getNotesCustom("", new String[]{}, key_modified + " DESC"); + Log.d(getClass().getSimpleName(), "Full Database:"); + for (DBNote note : notes) { + Log.d(getClass().getSimpleName(), " "+note); + } + } + /** * Returns a list of all Notes in the Database * * @return List<Note> */ public List getNotes() { - return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle()}); + return getNotesCustom(key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, key_modified + " DESC"); } /** @@ -182,7 +219,7 @@ public List getNotes() { * @return List<Note> */ public List searchNotes(String query) { - return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? AND " + key_content + " LIKE ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}); + return getNotesCustom(key_status + " != ? AND " + key_content + " LIKE ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}, key_modified + " DESC"); } /** @@ -191,7 +228,7 @@ public List searchNotes(String query) { * @return List<Note> */ public List getNotesByStatus(DBStatus status) { - return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " = ?", new String[]{status.getTitle()}); + return getNotesCustom(key_status + " = ?", new String[]{status.getTitle()}, null); } /** * Returns a list of all Notes in the Database with were modified locally @@ -199,7 +236,7 @@ public List getNotesByStatus(DBStatus status) { * @return List<Note> */ public List getLocalModifiedNotes() { - return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ?", new String[]{DBStatus.VOID.getTitle()}); + return getNotesCustom(key_status + " != ?", new String[]{DBStatus.VOID.getTitle()}, null); } /** @@ -208,63 +245,60 @@ public List getLocalModifiedNotes() { * @param note Note - Note with the updated Information * @return The number of the Rows affected. */ - @SuppressWarnings("UnusedReturnValue") - public int updateNoteAndSync(DBNote note) { + public void updateNoteAndSync(DBNote note) { + note.setStatus(DBStatus.LOCAL_EDITED); SQLiteDatabase db = this.getWritableDatabase(); - DBStatus newStatus = DBStatus.LOCAL_EDITED; - Cursor cursor = - db.query(table_notes, - columns, - key_id + " = ? AND " + key_status + " != ?", - new String[]{String.valueOf(note.getId()), DBStatus.LOCAL_DELETED.getTitle()}, - null, - null, - null, - null); - if (cursor != null) { - cursor.moveToFirst(); - String status = cursor.getString(1); - if (DBStatus.parse(status) == DBStatus.LOCAL_CREATED) { - newStatus = DBStatus.LOCAL_CREATED; - } - cursor.close(); - } - note.setStatus(newStatus); ContentValues values = new ContentValues(); - values.put(key_id, note.getId()); - values.put(key_status, newStatus.getTitle()); + values.put(key_status, note.getStatus().getTitle()); values.put(key_title, note.getTitle()); values.put(key_modified, note.getModified(DATE_FORMAT)); values.put(key_content, note.getContent()); - int i = db.update(table_notes, - values, - key_id + " = ?", - new String[]{String.valueOf(note.getId())}); + db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(note.getId())}); db.close(); - serverSyncHelper.uploadEditedNotes(); - return i; + getNoteServerSyncHelper().scheduleSync(true); } /** - * Updates a single Note. No Synchronization will be triggered. Use updateNoteAndSync()! + * Updates a single Note with data from the server, (if it was not modified locally). This is used by the synchronization task, hence no Synchronization will be triggered. Use updateNoteAndSync() instead! + * + * @param id local ID of Note + * @param remoteNote Note from the server. + * @param forceUnchangedDBNoteState is not null, then the local note is updated only if it was not modified meanwhile * - * @param note Note - Note with the updated Information * @return The number of the Rows affected. */ - @SuppressWarnings("UnusedReturnValue") - public int updateNote(Note note) { + int updateNote(long id, OwnCloudNote remoteNote, DBNote forceUnchangedDBNoteState) { SQLiteDatabase db = this.getWritableDatabase(); + + // First, update the remote ID, since this field cannot be changed in parallel, but have to be updated always. ContentValues values = new ContentValues(); - values.put(key_id, note.getId()); + values.put(key_remote_id, remoteNote.getRemoteId()); + db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(id)}); + + // The other columns have to be updated in dependency of forceUnchangedDBNoteState, + // since the Synchronization-Task must not overwrite locales changes! + values.clear(); values.put(key_status, DBStatus.VOID.getTitle()); - values.put(key_title, note.getTitle()); - values.put(key_modified, note.getModified(DATE_FORMAT)); - values.put(key_content, note.getContent()); - int i = db.update(table_notes, - values, - key_id + " = ?", - new String[]{String.valueOf(note.getId())}); + values.put(key_title, remoteNote.getTitle()); + values.put(key_modified, remoteNote.getModified(DATE_FORMAT)); + values.put(key_content, remoteNote.getContent()); + String whereClause; + String[] whereArgs; + if(forceUnchangedDBNoteState!=null) { + // used by: NoteServerSyncHelper.SyncTask.pushLocalChanges() + // update only, if not modified locally during the synchronization, + // uses reference value gathered at start of synchronization + whereClause = key_id + " = ? AND " + key_content + " = ?"; + whereArgs = new String[]{String.valueOf(id), forceUnchangedDBNoteState.getContent()}; + } else { + // used by: NoteServerSyncHelper.SyncTask.pullRemoteChanges() + // update only, if not modified locally + whereClause = key_id + " = ? AND " + key_status + " = ?"; + whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle()}; + } + int i = db.update(table_notes, values, whereClause, whereArgs); db.close(); + Log.d(getClass().getSimpleName(), "updateNote: "+remoteNote+" || forceUnchangedDBNoteState: "+forceUnchangedDBNoteState+" => "+i+" rows updated"); return i; } @@ -285,39 +319,21 @@ public int deleteNoteAndSync(long id) { key_id + " = ?", new String[]{String.valueOf(id)}); db.close(); - serverSyncHelper.uploadDeletedNotes(); + getNoteServerSyncHelper().scheduleSync(true); return i; } /** - * Delete a single Note from the Database + * Delete a single Note from the Database, if it has a specific DBStatus. * * @param id long - ID of the Note that should be deleted. + * @param forceDBStatus DBStatus, e.g., if Note was marked as LOCAL_DELETED (for NoteSQLiteOpenHelper.SyncTask.pushLocalChanges()) or is unchanged VOID (for NoteSQLiteOpenHelper.SyncTask.pullRemoteChanges()) */ - public void deleteNote(long id) { + void deleteNote(long id, DBStatus forceDBStatus) { SQLiteDatabase db = this.getWritableDatabase(); db.delete(table_notes, - key_id + " = ?", - new String[]{String.valueOf(id)}); - db.close(); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - clearDatabase(); - } - - public void clearDatabase() { - SQLiteDatabase db = this.getWritableDatabase(); - db.delete(table_notes, null, null); + key_id + " = ? AND " + key_status + " = ?", + new String[]{String.valueOf(id), forceDBStatus.getTitle()}); db.close(); } - - public Context getContext() { - return context; - } - - public void synchronizeWithServer() { - serverSyncHelper.synchronize(); - } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 8014fc584..ff54cbe48 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -1,27 +1,36 @@ package it.niedermann.owncloud.notes.persistence; -import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; -import android.view.View; +import android.util.Log; +import android.util.LongSparseArray; import android.widget.Toast; import org.json.JSONException; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.SettingsActivity; import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.OwnCloudNote; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NotesClient; import it.niedermann.owncloud.notes.util.NotesClientUtil.LoginStatus; @@ -33,23 +42,55 @@ */ public class NoteServerSyncHelper { - private NotesClient client = null; - private NoteSQLiteOpenHelper db = null; - private final View.OnClickListener goToSettingsListener = new View.OnClickListener() { + private static NoteServerSyncHelper instance; + + /** + * Get (or create) instance from NoteServerSyncHelper. + * This has to be a singleton in order to realize correct registering and unregistering of + * the BroadcastReceiver, which listens on changes of network connectivity. + * @param dbHelper NoteSQLiteOpenHelper + * @return NoteServerSyncHelper + */ + public static synchronized NoteServerSyncHelper getInstance(NoteSQLiteOpenHelper dbHelper) { + if(instance==null) { + instance = new NoteServerSyncHelper(dbHelper); + } + return instance; + } + + private NoteSQLiteOpenHelper dbHelper; + private Context appContext; + + // Track network connection changes using a BroadcastReceiver + private boolean networkConnected = false; + private BroadcastReceiver networkReceiver = new BroadcastReceiver() { @Override - public void onClick(View v) { - Activity parent = (Activity) db.getContext(); - Intent intent = new Intent(parent, SettingsActivity.class); - parent.startActivity(intent); + public void onReceive(Context context, Intent intent) { + ConnectivityManager connMgr = (ConnectivityManager)appContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeInfo = connMgr.getActiveNetworkInfo(); + if(activeInfo != null && activeInfo.isConnected()) { + Log.d(NoteServerSyncHelper.class.getSimpleName(), "Network connection established."); + networkConnected = true; + scheduleSync(false); + } else { + networkConnected = false; + Log.d(NoteServerSyncHelper.class.getSimpleName(), "No network connection."); + } } }; - private int operationsCount = 0; - private int operationsFinished = 0; + + // current state of the synchronization + private boolean syncActive = false; + private boolean syncScheduled = false; + + private Handler handler = null; private List callbacks = new ArrayList<>(); - public NoteServerSyncHelper(NoteSQLiteOpenHelper db) { - this.db = db; + private NoteServerSyncHelper(NoteSQLiteOpenHelper db) { + this.dbHelper = db; + this.appContext = db.getContext().getApplicationContext(); + handler = new Handler() { @Override public void handleMessage(Message msg) { @@ -59,15 +100,14 @@ public void handleMessage(Message msg) { callbacks.clear(); } }; - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(db.getContext().getApplicationContext()); - String url = preferences.getString(SettingsActivity.SETTINGS_URL, - SettingsActivity.DEFAULT_SETTINGS); - String username = preferences.getString(SettingsActivity.SETTINGS_USERNAME, - SettingsActivity.DEFAULT_SETTINGS); - String password = preferences.getString(SettingsActivity.SETTINGS_PASSWORD, - SettingsActivity.DEFAULT_SETTINGS); - client = new NotesClient(url, username, password); + // Registers BroadcastReceiver to track network connection changes. + appContext.registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + @Override + protected void finalize() throws Throwable { + appContext.unregisterReceiver(networkReceiver); + super.finalize(); } /** @@ -78,146 +118,143 @@ public void handleMessage(Message msg) { * * @param callback Implementation of ICallback, contains one method that shall be executed. */ + @Deprecated public void addCallback(ICallback callback) { callbacks.add(callback); } - /** - * Synchronizes Edited, New and Deleted Notes. After all changed content has been sent to the - * server, it downloads all changes that happened on the server. - */ - public void synchronize() { - uploadEditedNotes(); - uploadNewNotes(); - uploadDeletedNotes(); - downloadNotes(); - } /** - * Helper method to check if all synchronize operations are done yet. + * Schedules a synchronization and start it directly, if the network is connected and no + * synchronization is currently running. + * @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server. */ - private void asyncTaskFinished() { - operationsFinished++; - if (operationsFinished == operationsCount) { - handler.obtainMessage(1).sendToTarget(); + public void scheduleSync(boolean onlyLocalChanges) { + Log.d(getClass().getSimpleName(), "Sync requested ("+(onlyLocalChanges?"onlyLocalChanges":"full")+"; "+(networkConnected?"network connected":"network NOT connected")+", "+(syncActive?"sync active":"sync NOT active")+") ..."); + if(networkConnected && (!syncActive || onlyLocalChanges)) { + Log.d(getClass().getSimpleName(), "... starting now"); + new SyncTask(onlyLocalChanges).execute(); + } else if(!onlyLocalChanges) { + Log.d(getClass().getSimpleName(), "... scheduled"); + syncScheduled = true; + } else { + Log.d(getClass().getSimpleName(), "... do nothing"); } } - public void uploadEditedNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_EDITED); - for (Note note : notes) { - UploadEditedNotesTask editedNotesTask = new UploadEditedNotesTask(); - editedNotesTask.execute(note); - } - } - - public void uploadNewNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_CREATED); - for (Note note : notes) { - UploadNewNoteTask newNotesTask = new UploadNewNoteTask(); - newNotesTask.execute(note); - } - } + private class SyncTask extends AsyncTask { + private final boolean onlyLocalChanges; + private NotesClient client; - public void uploadDeletedNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_DELETED); - for (Note note : notes) { - UploadDeletedNoteTask deletedNotesTask = new UploadDeletedNoteTask(); - deletedNotesTask.execute(note); + public SyncTask(boolean onlyLocalChanges) { + this.onlyLocalChanges = onlyLocalChanges; } - } - public void downloadNotes() { - DownloadNotesTask downloadNotesTask = new DownloadNotesTask(); - downloadNotesTask.execute(); - } - - private class UploadNewNoteTask extends AsyncTask { @Override - protected Object[] doInBackground(Object... params) { - operationsCount++; - Note oldNote = (Note) params[0]; - try { - Note note = client.createNote(oldNote.getContent()); - return new Object[]{note, oldNote.getId()}; - } catch (IOException | JSONException e) { - e.printStackTrace(); + protected void onPreExecute() { + super.onPreExecute(); + if(!onlyLocalChanges && syncScheduled) { + syncScheduled = false; } - return null; + syncActive = true; } @Override - protected void onPostExecute(Object[] params) { - if (params != null) { - Long id = (Long) params[1]; - if (id != null) { - db.deleteNote(((Long) params[1])); - } - db.addNote((Note) params[0]); - } - asyncTaskFinished(); - } - } - - private class UploadEditedNotesTask extends AsyncTask { - private String noteContent; - - @Override - protected Note doInBackground(Object... params) { - operationsCount++; - Note oldNote = (Note) params[0]; - noteContent = oldNote.getContent(); - try { - return client.editNote(oldNote.getId(), noteContent); - } catch (IOException | JSONException e) { - e.printStackTrace(); - } + protected Void doInBackground(Void... voids) { + client = createNotesClient(); // recreate NoteClients on every sync in case the connection settings was changed + Log.d(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); + dbHelper.debugPrintFullDB(); + pushLocalChanges(); + pullRemoteChanges(); + dbHelper.debugPrintFullDB(); + Log.d(getClass().getSimpleName(), "SYNCHRONIZATION FINISHED"); return null; } - @Override - protected void onPostExecute(Note note) { - if (note == null) { - // Note has been deleted on server -> recreate - db.addNoteAndSync(noteContent); - } else { - db.updateNote(note); + /** + * Push local changes: for each locally created/edited/deleted Note, use NotesClient in order to push the changed to the server. + */ + private void pushLocalChanges() { + Log.d(getClass().getSimpleName(), "pushLocalChanges()"); + List notes = dbHelper.getLocalModifiedNotes(); + for (DBNote note : notes) { + Log.d(getClass().getSimpleName(), " Process Local Note: "+note); + try { + OwnCloudNote remoteNote=null; + switch(note.getStatus()) { + case LOCAL_EDITED: + Log.d(getClass().getSimpleName(), " ...create/edit"); + // if note is not new, try to edit it. + if (note.getRemoteId()>0) { + Log.d(getClass().getSimpleName(), " ...try to edit"); + remoteNote = client.editNote(note.getRemoteId(), note.getContent()); + } + // However, the note may be deleted on the server meanwhile; or was never synchronized -> (re)create + if (remoteNote == null) { + Log.d(getClass().getSimpleName(), " ...Note does not exist on server -> (re)create"); + remoteNote = client.createNote(note.getContent()); + dbHelper.updateNote(note.getId(), remoteNote, note); + } else { + dbHelper.updateNote(note.getId(), remoteNote, note); + } + break; + case LOCAL_DELETED: + if(note.getRemoteId()>0) { + Log.d(getClass().getSimpleName(), " ...delete (from server and local)"); + try { + client.deleteNote(note.getRemoteId()); + } catch (FileNotFoundException e) { + Log.d(getClass().getSimpleName(), " ...Note does not exist on server (anymore?) -> delete locally"); + } + } else { + Log.d(getClass().getSimpleName(), " ...delete (only local, since it was not synchronized)"); + } + dbHelper.deleteNote(note.getId(), DBStatus.LOCAL_DELETED); + break; + default: + throw new IllegalStateException("Unknown State of Note: "+note); + } + } catch (IOException | JSONException e) { + // FIXME Fehlerbehandlung sichtbar machen + e.printStackTrace(); + } } - asyncTaskFinished(); } - } - - private class UploadDeletedNoteTask extends AsyncTask { - Long id = null; - @Override - protected Void doInBackground(Object... params) { - operationsCount++; + /** + * Pull remote Changes: update or create each remote note (if local pendant has no changes) and remove remotely deleted notes. + */ + private void pullRemoteChanges() { + Log.d(getClass().getSimpleName(), "pullRemoteChanges()"); + LoginStatus status = null; try { - id = ((Note) params[0]).getId(); - client.deleteNote(id); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - db.deleteNote(id); - asyncTaskFinished(); - } - } - - private class DownloadNotesTask extends AsyncTask> { - private LoginStatus status = null; - - @Override - protected List doInBackground(Object... params) { - operationsCount++; - List notes = new ArrayList<>(); - try { - notes = client.getNotes(); + List localNotes = dbHelper.getNotes(); + Map localIDmap = new HashMap<>(); + for (DBNote note : localNotes) { + localIDmap.put(note.getRemoteId(), note.getId()); + } + List remoteNotes = client.getNotes(); + Set remoteIDs = new HashSet<>(); + // pull remote changes: update or create each remote note + for (OwnCloudNote remoteNote : remoteNotes) { + Log.d(getClass().getSimpleName(), " Process Remote Note: "+remoteNote); + remoteIDs.add(remoteNote.getRemoteId()); + if(localIDmap.containsKey(remoteNote.getRemoteId())) { + Log.d(getClass().getSimpleName(), " ... found -> Update"); + dbHelper.updateNote(localIDmap.get(remoteNote.getRemoteId()), remoteNote, null); + } else { + Log.d(getClass().getSimpleName(), " ... create"); + dbHelper.addNote(remoteNote); + } + } + Log.d(getClass().getSimpleName(), " Remove remotely deleted Notes (only those without local changes)"); + // remove remotely deleted notes (only those without local changes) + for (DBNote note : localNotes) { + if(note.getStatus()==DBStatus.VOID && !remoteIDs.contains(note.getRemoteId())) { + Log.d(getClass().getSimpleName(), " ... remove "+note); + dbHelper.deleteNote(note.getId(), DBStatus.VOID); + } + } status = LoginStatus.OK; } catch (IOException e) { e.printStackTrace(); @@ -225,22 +262,36 @@ protected List doInBackground(Object... params) { } catch (JSONException e) { status = LoginStatus.JSON_FAILED; } - return notes; + if (status!=LoginStatus.OK) { + Toast.makeText(appContext, appContext.getString(R.string.error_sync, appContext.getString(status.str)), Toast.LENGTH_LONG).show(); + } } @Override - protected void onPostExecute(List result) { - // Clear Database only if there was no Server Error - if (status==LoginStatus.OK) { - db.clearDatabase(); + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + syncActive = false; + if(syncScheduled) { + scheduleSync(false); } else { - Context c = db.getContext(); - Toast.makeText(c, c.getString(R.string.error_sync, c.getString(status.str)), Toast.LENGTH_LONG).show(); + asyncTaskFinished(); } - for (Note note : result) { - db.addNote(note); - } - asyncTaskFinished(); } } + + private NotesClient createNotesClient() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(appContext.getApplicationContext()); + String url = preferences.getString(SettingsActivity.SETTINGS_URL, SettingsActivity.DEFAULT_SETTINGS); + String username = preferences.getString(SettingsActivity.SETTINGS_USERNAME, SettingsActivity.DEFAULT_SETTINGS); + String password = preferences.getString(SettingsActivity.SETTINGS_PASSWORD, SettingsActivity.DEFAULT_SETTINGS); + return new NotesClient(url, username, password); + } + + /** + * Helper method to check if all synchronize operations are done yet. + */ + @Deprecated + private void asyncTaskFinished() { + handler.obtainMessage(1).sendToTarget(); + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java index ac6d459ca..5a2db0d47 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java @@ -18,7 +18,7 @@ import java.util.GregorianCalendar; import java.util.List; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.OwnCloudNote; public class NotesClient { @@ -41,9 +41,9 @@ public NotesClient(String url, String username, String password) { this.password = password; } - public List getNotes() throws JSONException, + public List getNotes() throws JSONException, IOException { - List notesList = new ArrayList<>(); + List notesList = new ArrayList<>(); JSONArray notes = new JSONArray(requestServer("notes", METHOD_GET, null)); long noteId = 0; String noteTitle = ""; @@ -68,7 +68,7 @@ public List getNotes() throws JSONException, .setTimeInMillis(currentItem.getLong(key_modified) * 1000); } notesList - .add(new Note(noteId, noteModified, noteTitle, noteContent)); + .add(new OwnCloudNote(noteId, noteModified, noteTitle, noteContent)); } return notesList; } @@ -83,7 +83,7 @@ public List getNotes() throws JSONException, * @throws IOException */ @SuppressWarnings("unused") - public Note getNoteById(long id) throws + public OwnCloudNote getNoteById(long id) throws JSONException, IOException { long noteId = 0; String noteTitle = ""; @@ -106,7 +106,7 @@ public Note getNoteById(long id) throws noteModified .setTimeInMillis(currentItem.getLong(key_modified) * 1000); } - return new Note(noteId, noteModified, noteTitle, noteContent); + return new OwnCloudNote(noteId, noteModified, noteTitle, noteContent); } /** @@ -117,7 +117,7 @@ public Note getNoteById(long id) throws * @throws JSONException * @throws IOException */ - public Note createNote(String content) throws + public OwnCloudNote createNote(String content) throws JSONException, IOException { long noteId = 0; String noteTitle = ""; @@ -143,10 +143,10 @@ public Note createNote(String content) throws noteModified .setTimeInMillis(currentItem.getLong(key_modified) * 1000); } - return new Note(noteId, noteModified, noteTitle, noteContent); + return new OwnCloudNote(noteId, noteModified, noteTitle, noteContent); } - public Note editNote(long noteId, String content) + public OwnCloudNote editNote(long noteId, String content) throws JSONException, IOException { String noteTitle = ""; Calendar noteModified = null; @@ -164,7 +164,7 @@ public Note editNote(long noteId, String content) noteModified .setTimeInMillis(currentItem.getLong(key_modified) * 1000); } - return new Note(noteId, noteModified, noteTitle, content); + return new OwnCloudNote(noteId, noteModified, noteTitle, content); } public void deleteNote(long noteId) throws From 594dc5ff46f08ee674fd4e9f7d20c37d22c1cc4c Mon Sep 17 00:00:00 2001 From: korelstar Date: Wed, 29 Jun 2016 22:09:46 +0200 Subject: [PATCH 07/25] Show icon if a note is not synchronized (hint to a possible error) --- .../android/activity/EditNoteActivity.java | 5 ++-- .../activity/NotesListViewActivity.java | 7 +++-- .../activity/SelectSingleNoteActivity.java | 3 +- .../notes/android/widget/AllNotesWidget.java | 8 +++-- .../owncloud/notes/model/DBNote.java | 28 ++++++++++++++++++ .../owncloud/notes/model/DBStatus.java | 8 +++++ .../owncloud/notes/model/ItemAdapter.java | 14 +++++---- .../persistence/NoteSQLiteOpenHelper.java | 22 +++++++------- .../res/drawable-hdpi/ic_sync_error_holo.png | Bin 0 -> 1154 bytes .../res/drawable-mdpi/ic_sync_error_holo.png | Bin 0 -> 790 bytes .../res/drawable-xhdpi/ic_sync_error_holo.png | Bin 0 -> 1411 bytes .../drawable-xxhdpi/ic_sync_error_holo.png | Bin 0 -> 1889 bytes .../drawable-xxxhdpi/ic_sync_error_holo.png | Bin 0 -> 2231 bytes .../layout/fragment_notes_list_note_item.xml | 9 ++++++ 14 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java create mode 100644 app/src/main/res/drawable-hdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-mdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_sync_error_holo.png diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index c1d24d966..e04959e88 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.ICallback; @@ -24,7 +25,7 @@ public class EditNoteActivity extends AppCompatActivity { private final long DELAY = 1000; // in ms private EditText content = null; - private Note note = null; + private DBNote note = null; private Timer timer = new Timer(); private ActionBar actionBar; @@ -32,7 +33,7 @@ public class EditNoteActivity extends AppCompatActivity { protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit); - note = (Note) getIntent().getSerializableExtra( + note = (DBNote) getIntent().getSerializableExtra( NoteActivity.EDIT_NOTE); content = (EditText) findViewById(R.id.editContent); content.setEnabled(false); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 4813188a3..db3af0e8e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -21,6 +21,7 @@ import java.util.List; import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; import it.niedermann.owncloud.notes.model.Note; @@ -130,7 +131,7 @@ public void onClick(View v) { * @param noteList List<Note> */ @SuppressWarnings("WeakerAccess") - public void setListView(List noteList) { + public void setListView(List noteList) { List itemList = new ArrayList<>(); // #12 Create Sections depending on Time // TODO Move to ItemAdapter? @@ -290,7 +291,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { //not need because of db.synchronisation in createActivity - Note createdNote = (Note) data.getExtras().getSerializable( + DBNote createdNote = (DBNote) data.getExtras().getSerializable( CREATED_NOTE); adapter.add(createdNote); //setListView(db.getNotes()); @@ -301,7 +302,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { SELECTED_NOTE_POSITION); adapter.remove(adapter.getItem(notePosition)); if (resultCode == RESULT_OK) { - Note editedNote = (Note) data.getExtras().getSerializable( + DBNote editedNote = (DBNote) data.getExtras().getSerializable( NoteActivity.EDIT_NOTE); adapter.add(editedNote); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java index 7bcf60cac..b9bb6d7b0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java @@ -15,6 +15,7 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.widget.SingleNoteWidget; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; import it.niedermann.owncloud.notes.model.Note; @@ -61,7 +62,7 @@ public void onCreate(Bundle savedInstanceState) { * * @param noteList List<Note> */ - private void setListView(List noteList) { + private void setListView(List noteList) { List itemList = new ArrayList<>(); itemList.addAll(noteList); adapter = new ItemAdapter(itemList); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java index e85c7e645..83a84f785 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java @@ -13,6 +13,8 @@ import java.util.List; import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.model.DBNote; +import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; @@ -51,7 +53,7 @@ public RemoteViewsFactory onGetViewFactory(Intent intent) { class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final int mCount = 10; - private List mWidgetItems = new ArrayList<>(); + private List mWidgetItems = new ArrayList<>(); private Context mContext; private int mAppWidgetId; @@ -62,11 +64,11 @@ public StackRemoteViewsFactory(Context context, Intent intent) { NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(mContext); db.synchronizeWithServer(); mWidgetItems = db.getNotes(); - mWidgetItems.add(new Note(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung")); + mWidgetItems.add(new DBNote(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); } public void onCreate() { - mWidgetItems.add(new Note(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung")); + mWidgetItems.add(new DBNote(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); } @Override diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java new file mode 100644 index 000000000..646d869c2 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java @@ -0,0 +1,28 @@ +package it.niedermann.owncloud.notes.model; + +import java.util.Calendar; + +import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; + +public class DBNote extends Note { + + private DBStatus status; + + public DBNote(long id, Calendar modified, String title, String content, DBStatus status) { + super(id, modified, title, content); + this.status = status; + } + + public DBStatus getStatus() { + return status; + } + + public void setStatus(DBStatus status) { + this.status = status; + } + + @Override + public String toString() { + return "#" + getId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java index fc27f8265..ea3393b31 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java @@ -17,4 +17,12 @@ public String getTitle() { DBStatus(String title) { this.title = title; } + + public static DBStatus parse(String str) { + if(str.isEmpty()) { + return DBStatus.VOID; + } else { + return DBStatus.valueOf(str); + } + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java index b0bc49e9f..f7a291049 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java @@ -4,6 +4,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; @@ -37,7 +38,7 @@ public static void setNoteClickListener(NoteClickListener noteClickListener) { * Adds the given note to the top of the list. * @param note Note that should be added. */ - public void add(Note note) { + public void add(DBNote note) { itemList.add(0, note); notifyItemInserted(0); notifyItemChanged(0); @@ -55,12 +56,12 @@ public void removeAll() { * Compares the given List of notes to the current internal holded notes and updates the list if necessairy * @param newNotes List of more up to date notes */ - public void checkForUpdates(List newNotes) { - for(Note newNote : newNotes) { + public void checkForUpdates(List newNotes) { + for(DBNote newNote : newNotes) { boolean foundNewNoteInOldList = false; for(Item oldItem : itemList) { if(!oldItem.isSection()) { - Note oldNote = (Note) oldItem; + DBNote oldNote = (DBNote) oldItem; if(newNote.getId() == oldNote.getId()) { // Notes have the same id, check which is newer if(newNote.getModified().after(oldNote.getModified())) { @@ -109,9 +110,10 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ((SectionViewHolder) holder).sectionTitle.setText(section.geTitle()); ((SectionViewHolder) holder).setPosition(position); } else { - Note note = (Note) item; + DBNote note = (DBNote) item; ((NoteViewHolder) holder).noteTitle.setText(note.getTitle()); ((NoteViewHolder) holder).noteExcerpt.setText(note.getExcerpt()); + ((NoteViewHolder) holder).noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.GONE : View.VISIBLE); ((NoteViewHolder) holder).setPosition(position); } } @@ -168,12 +170,14 @@ public static class NoteViewHolder extends RecyclerView.ViewHolder implements Vi // each data item is just a string in this case public TextView noteTitle; public TextView noteExcerpt; + public ImageView noteStatus; public int position = -1; private NoteViewHolder(View v) { super(v); this.noteTitle = (TextView) v.findViewById(R.id.noteTitle); this.noteExcerpt = (TextView) v.findViewById(R.id.noteExcerpt); + this.noteStatus = (ImageView) v.findViewById(R.id.noteStatus); v.setOnClickListener(this); v.setOnLongClickListener(this); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index d554bd49d..cb27390f2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Locale; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.util.NoteUtil; @@ -112,7 +113,7 @@ public void addNote(Note note) { * @return requested Note */ @SuppressWarnings("unused") - public Note getNote(long id) { + public DBNote getNote(long id) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.query(table_notes, @@ -134,7 +135,7 @@ public Note getNote(long id) { } catch (ParseException e) { e.printStackTrace(); } - Note note = new Note(Long.valueOf(cursor != null ? cursor.getString(0) : null), modified, cursor != null ? cursor.getString(2) : null, cursor.getString(4)); + DBNote note = new DBNote(Long.valueOf(cursor != null ? cursor.getString(0) : null), modified, cursor != null ? cursor.getString(2) : null, cursor.getString(4), DBStatus.parse(cursor.getString(1))); cursor.close(); return note; } @@ -144,8 +145,8 @@ public Note getNote(long id) { * * @return List<Note> */ - public List getNotes() { - List notes = new ArrayList<>(); + public List getNotes() { + List notes = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle()}); if (cursor.moveToFirst()) { @@ -158,7 +159,7 @@ public List getNotes() { } catch (ParseException e) { e.printStackTrace(); } - notes.add(new Note(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4))); + notes.add(new DBNote(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4), DBStatus.parse(cursor.getString(1)))); } while (cursor.moveToNext()); } cursor.close(); @@ -170,8 +171,8 @@ public List getNotes() { * * @return List<Note> */ - public List searchNotes(String query) { - List notes = new ArrayList<>(); + public List searchNotes(String query) { + List notes = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? AND " + key_content + " LIKE ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}); if (cursor.moveToFirst()) { @@ -184,7 +185,7 @@ public List searchNotes(String query) { } catch (ParseException e) { e.printStackTrace(); } - notes.add(new Note(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4))); + notes.add(new DBNote(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4), DBStatus.parse(cursor.getString(1)))); } while (cursor.moveToNext()); } cursor.close(); @@ -224,7 +225,7 @@ public List getNotesByStatus(DBStatus status) { * @return The number of the Rows affected. */ @SuppressWarnings("UnusedReturnValue") - public int updateNoteAndSync(Note note) { + public int updateNoteAndSync(DBNote note) { SQLiteDatabase db = this.getWritableDatabase(); DBStatus newStatus = DBStatus.LOCAL_EDITED; Cursor cursor = @@ -239,11 +240,12 @@ public int updateNoteAndSync(Note note) { if (cursor != null) { cursor.moveToFirst(); String status = cursor.getString(1); - if (!"".equals(status) && DBStatus.valueOf(status) == DBStatus.LOCAL_CREATED) { + if (DBStatus.parse(status) == DBStatus.LOCAL_CREATED) { newStatus = DBStatus.LOCAL_CREATED; } cursor.close(); } + note.setStatus(newStatus); ContentValues values = new ContentValues(); values.put(key_id, note.getId()); values.put(key_status, newStatus.getTitle()); diff --git a/app/src/main/res/drawable-hdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-hdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..cfa2d2b92daf5f2eeef58e5d86fe491eae242986 GIT binary patch literal 1154 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+eS5K)hhiu0R{01Y44~ zy9>jA5L~c#`DCC7XMsm#F_88EW4Dvpb_@*6L7py-ArXh)&all0aTGXS|6MQLV8xF{ zF}Dqz9AR}LVQO9Rt@2wPlTsI-t1mg__Q-|yS*BzR$3xJJtn zsRhhGI7Nh0>KKbHb6gH2tv;8_daQvpL28ESc^R$y8mqa$`T40$-%Ky4Gm1wX()^$7t5nG?!*)-4hjVMp^!KWjiw^A4 zdm#6L;f97;k@C_&IT2>l!d0^v71!>bd_;5Js!_|CG8+k!l&u3RLwZh?BsdgkkAs@96W&X}#RU{@7uZ0V~oA+Nv( zwSqTTW5n3G4u~e`uUjT?WJ;E14e#`8zYnM;OntGRTWdBoRqU6uNPL!m>IPp8OCMwY3BR)IypsPcHaO<6 zWqW?U>l-7yKEr0Mvhthx=H74ZrplYY{$9OpwgA(O%x7^PELsapLlyqBMvMG>p5~YP z*HNS7OTO73_4tNkhrZsQ%5S;aN=-P&RMDkt(w6*%i_UrZ_O%PlGO9_Rr10_WUls|2 zJxWVgehs{xR#M~n*(~JBV_lcGi!+{f@916hYq#xf-$M_!JxW);GFfekP=x#j{~u+N z{1SVGcPJmb)%8RuroFTn$j@`B8R;qD)=AB~Q z&am#(9qR<{g1#O5XC06Azn1oy`{UhDLOXQtg*PTX*z!T};lAVQcMW29e9r0EQ7?6@ zbZM2@(n(4e8nZ=nUK?<%`MSW*EAr-vYdp97Pu{GHcw*JrUsamLcD*KYxz5j(YrJM{ zDYbmEmRstZXRvwaZAs-Fx9jC@v}DVg99kN6!>adrpzEawRcCvVqF34lTo&GU_%&B9 zTxCm^p3Lw_e^tlYnCZak+&bGG9orb|ih)croLsa#h0)uiqB$)$YeMv@-N zXR1zbe=N0gNw#}^&%YDh0UV9{clrfs@;m)^`oHPV<)l`r7v}B{UoKlZrD8QOo2iz# zMwFx^mZVxG7o`Fz1|tJQ6I}yyT_ej7LsKh5Ln{+AAlJ&kApPe3Boqy~`6-!cmAEyC zwWQwzYLEok5S*V@Ql40p%HWuipOmWLnVXoN8kCxtQdxL16;u{5c)I$ztaD0e0svFC B;>!R4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-mdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..77a6077fd6804fe145fc57df5aa0fb3df9d53bf7 GIT binary patch literal 790 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ueoXKL{?^yL>WGgtNdSvKUBvfU(=jY&)Rw&pcfmLp+Y3opw@>B~YYo|D|VQSLa6D z_{bn8mRrGpk2$(bNO29v`y}0O+f>?EIg`57STa)!|4+7yUA0R^sc`Z+&%4Hd()aP! z*DPwvw>}wfJ*nL5*I$h$4hgon;5{rAIvtBxYPL;&DKK~X1?B?oD~-kqWkqV%9BY_N zkDvA~>^^>)DYN4qbEtZ9k*MV)hG>U;w=g5dQwJ7`2tV*UCJ=CyaV^97Nw-<6ZZ$@{ zxE-`3P=x;g!v+2rhUZ@Wa|<81SXpaE-|zAF_`oZ{JguSJHFVl`|8;727nweA-8DH< z;ab*gp<4xeSQ9v2O#ZlwxkKi}uGQ_^Z%$gz%<21j_vuQZ(-l$&egt2#{ZRTfk%wt< zi>1Z2*yi_}C+RKt`fp{4drhxZX1!3l!o=?{Z&l7T$;j(kX2d0WQD@urmqK&Tc?Uk? z+N5taS$JW8n#2rdyApHlUFuPvCeBzsc~{NColI-IlOAyGP&?x?^TN6AmT7g9-?_Mb z(G@q#P;XH@#291Mz<5Z&jKle6*@YV2Dvsp&0&LkwKPJu6jZ$1AsQqc8!{ySmEH71g zdUCIIJ)Z3|bpyxWq!>LWgJrD|%-1r$ZH>Qfm^k%1=jyv=ujlv&hcH~yC{S7UHL+r8Vb{*JddJqh7VdHKHUXu_Vl#^x7@Arc8d{l{0l8KN2I)8NC!uJ_%}>cptHiBAtR?*(P=h4MhT#0PlJdl&R0hYC h{G?O`&)mfH)S%SFl*+=Bsi5@9;OXk;vd$@?2>|L1FDL*2 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-xhdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..1394a19cdce2bd27a50632b51dcc44ca28fb0138 GIT binary patch literal 1411 zcmZ`(do&Yz9RKa%F>hHSyBJs6?qpkD^L|X5*AA0sSD4MX5!M!!$0j!v<#oCw?mEIr z(X7W3nS1jJJ>)of)Dn?GO+tyf>;8BDxaWJm-_QAczUTA({P!&kq){~0cB%mYG<~RK zx&rY(T2)yw!=PXC6#!>@1b6_@dQN>aR!PAjN9Yt!(DDfNRzXbTsPq5;j_(41p9jE( zLgLQ@z*Ag#7XyG}B>+28FWwDuRxFemeiSnJ{-cEV8p{<5B9$6(M1jPLibmlo;1-mR2D+AcCT7`uIMWP`PJ>zu=oY@d zz;^d6Ay1dUn*+NssgP~fy;{9%L{!u1D~anvYl}37_e5Ewi<3)KNKQx?k7w z5#y7VoFuQU9tpTuP41m9yFoKfT>C~6m4L3^Il+#uovE7PoZf{eP}bSuu%_27*u}|` zzx@Irn@bwY+HzZb<4ki;KH)C)xgO8BKX)wrX~}j*w%s(0j>Dj_hRDl`YjkU-%gBd-`7f} zyXfp4(-oTZZ?<57o=DXhf#V$Pi$~q}qhg(HAYixnMVtHF-l?&IwxE4)FxEA$Wu&6g zmy4m$$3?I%nF)BWBK`yRLPj653N6WM*lJmGGdsK#BvTFnt;Qp=U2pTPqPgMn$(KY1v4AZy>TzEi#>bVeZZ%X28;3; zs#1G?6Zll3*_&XLF%dZ4F->AUipGx2`SH?#yll62o+S#6%Lib6yqAYrEV)&UW_~U!(OCem44?y!ESDUDFDG*)+Qv1r+L_DlTl}!=#VoLPYEYg&9Xw{@+Y6CLHfKFswg*=Qdy4xjs$kiiq9y0da)v|; zQWWWl208w=-z1nYX=+9(Ci3ZCd)MPwQUWo*wzI{5Vn>Y#kqm4J8G->axG zdJORcsVJgEtFN*eJxd-iu{5Ngbg6cKRywda86DExd^IOxt#ec0P>%_C>n60X>l207 zCf@5&{22?bcQ}_B%VjynaaalfL_ER93U6;kB<&~IIuZzuHg*c?h{vDk7o#cylLbX&zRhQ E0P1&EMF0Q* literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-xxhdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb1ee383ed732b028a3ffeb9c3e8b135bea2d8f GIT binary patch literal 1889 zcmZ`)dpy(o8~<)tu954pxn>JR3^_Af+L&$0Vxg2vW^yQ(Tw)ekC|VcSm9SFM9GbG! zIjGj@v^6GHwsZn9x4w2fD)PH zM3o@vN611Xx95U@C;{nUoGT6hp4?E_2#}F*P&Ac@2ly>Ivl3zuNTRv|K*BBnNKOTS zHHnlw0|2p>05EqF0MNGpKr6Da#vLPhfY4lsPQccWbN9u=9ElxBCU0c+cIF=%`&;tKZwgxN!q}(%NdvNVxqm%>VWo zH{@O^m=M|q>0U>P!H;AMp~4A@4k2fA8~^cvxt1Okii`!123d}w$;Dem)H8}9G#D%LDy4McuFyu4EF(l&@iMPf}o~+ z=jIYidch&J_Tsir{-B40)ly2&%eB)o%-c+=PR7jW75b{)I1VWt`YO>^bj!^A)Lrhb zCwtalMavy$KZK3e)qarGVCGzJEEi&{T>GC$gX3j>Ba9=Hwg#rpw+tQejw;hFLgXu4 zNoY%7FTgre`PE!YnezvG3v!QKh7TUpBHN=tW~G@+zMBTD<0RF5<0m?{K9`cUvmXmm z9{6xr#!Z@J;~Do+mzoLeBw=&K4;&^VjWv1lQ~@H47E({PFCE!gbcZ-y zuku%|=sx)iQPsD4|1NCHNK{V=A}2_J&fpqj9K@iXG2FCb$8V*anDXx-ExbnTYRBf5i`s`)# za0ghkj`HPvQ*5W=U^(3(ism(jL+w?{mU;7GBQ2!1&8GxHX)g+%dBTEURnb$bRe!}c z6L`v&`1su$V0Y$pC+9df{{eRGz=ENz4n_kCFTB!HoOz0@+`|g14HFf%^hUn&?S|OO zxZP80`dK?dYbcJr_t%PEV*d(}S~Yw-ZOy%wD3@w|#%k(igdhe#uHlKdOT0syhigO3 z*#}%lkwzTEV)>5I$2hy8QYO%Ki72w%qHD|)3qR?OKF5`3f?VK;J?zUFa5t$k_Uk0& zwbsP_&3aD{_NLUQtJ6Z#IT@U*HZW@qm_YU^yTYSw!BlA!wDS9md{^`82|W|zp>a2! z(rNnQh8cJP*pg1thg{dP!M}Ir(3wvas&9>=#^B5T!6(#NnnS@ODKXzH#&Ix_ZDYzX zQoPx1k=p?eP(FSY(WbY^UMZ;mWNp_!o94RAbgR_Tt%7qKXT5I-#%*`t4{h=Lcq0w! z*)5;w20CiDtKvIcZ)bW$4t47NrLY?cvIQ7-r=* zdH)Ife!0swOV6wNvt|`Rr5}AOy2zt?@_Au#&)`ZX$pjl^*UV4W7pdg$3eDCzD zy2!3CVQ<8r)%h0;S~*F5Je8-#u#$-W)4G@l-qa)Z>}HlZwIu-)j0xv_HcnjnC>J{> zN2fa?@@%P5tP<%Q&$}4dspYjyl7)dOK7B$y+duC(s?I54EOLkGFm8Eu@6OCm@1jY6 zTRnW18Q1u(<{L%5a%a-Ows^H zv))(jak)+WYE8b3X{nq%9_}Jk<0?IP9X>gLaQGtMh#AU1(=VFxo@UCaFWoaAo%;39 zzX=ILva}EISI6Si7_+93D%q0nBT|0RE1orR`vv3qIFM+00&8W1us zMNe78&hZZAG?O>&+30MT2=1MCV0kg8;b5^Y7w`AFdN>ekvM`lqB~!&{znr2@#)c>| z@|kZl2c7W|D&TKBeb0s|+6*43>Y68clQP1$lMF7rzZJWnXf}A}p_<5{&L;t$#X>c$ezTR0H+E^7LRH1 z|A8IaiTb#zUtoXEpL?V^RI*7BF@$3=^njQkbYN7F1OO{bq>Z`d&*oNk9>{%YBob|7 zE1_sh%iz8d+P@585%iE#jQ=)hrUnm74B9^`9Ems;6BiH_1mNfqv>*d=SU_+PH7J12 TINK2PW7Pp<0>z1sKY8&VH{eea literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_sync_error_holo.png b/app/src/main/res/drawable-xxxhdpi/ic_sync_error_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..bb093d946b36ca0d76527193c85e3a1c0206c4c1 GIT binary patch literal 2231 zcmbVMc~nzZ8oyasLZJ=d0%*%KWGo=r10l~)2uWCk0EMuXfyqnqfM_<8Kv+h}K{~>4 zpcWAo%QP;uqOv)nR1^bL+&Tgyjz~uZHh+lvj3P?7VQz zpg^xw%p1ZeuSsP{lOU1ah(f{iV49N2W*VzJOI9J=0^$6)dKdNMz#K-wn7$4{YmiFc8rGTKBnnFzqX;oDSac>st6hle zmA1*8jQ)qlJGD)+Vgt%ZMoszxqY|q}uHPaU%iXU#T2RE;z==jRRun`d)+-CNsLmWK z4x?aS=qj}eW^#~RmV(QqaYB{3G%knBqamtXgr-83A$*jpj0)z8Bl)2m{vtM3XEGx?CA#Qajro3!4UPI$EG#mjh*@ux>Ghh$ zE=bDPoAsuAy#W%5Ab%;MRO=SxfQ1^p4q1#E)rF`k%Ba^uuaXR_-$6hbqC!<1o{Gj~ z^FnCc5S9`P9%a%LD3_Vb<1@KT6-s%7SN*@qV_>8h3!U;`owIm_wd2C^?Hpjj+et!o z*!UQ+IT@)ASONfXJXS1}6^%b?ii=4VdYv1J-Ee0O4-v|f#WGpG`qK@Nf(?I6R|itFGy_d1== z@OKDLd2ny(kQb1Nl3H^n(COo?lOp~~j>lKNh$%W92kI*11TcGGcGXTmd^qws1K9uEKZTy8UIBGhxu^B~jsD^` zVEwlFmI5^Ohb!v;(O~F(4~&1fi?p1?`*!QsY_zAUAo?2|ckS^douADeojoRgKCW>{ zS|iwxv$|NX_WS%{_D_Nbq!dF6ovj zuJ=0vo5$p{iWip~@gVNRotVjQuK2m`?()k5)r+JdOm3#A=$PJGcInv8JFn_rk}W7 zIFbFa^;!%9E7Q1W02ofC-a2s-)Vr5=T$?EA4NP@^Lf>AK_f^xz`EBO>Lk#>cqdPy! zP^Hv7@~t7ro$7w3&dRs`=s|nTLT=ht+LuvZ7EiS2P#&%*0AtnzWwT!P71Yy_PS&nV zUiPV#%I(fp3U$+{65NR?>3rntinbpn4Up%B2Sc4#KOIVBT&6X9yASO+lm(i%^}1AF zJ5o!3z^ludV1Xm^FPjN=3w2Y?Ij~L(n3V^1mb%J4>ps~_uzyV5boe$@QJK2lR68J} zfx^8No-4CE(^(C*=oNzw;v7=-XqO)+)|&R)cjW0m1^A&h^@7KNjGD8|2`#`iGTL zye-l{!9u$}J?`5ReE}EYIoXn*zS2%D{2M}UBR>)3;(_Me9+%nHsvQl4^NvWO)fe9V ztoO=k{5&}ORVP7m->m^^J$!Xaa3m3%<9`Ko+OK)$lMdmMZfrk)DsV4Haj#;z z@op)<*HZ%k<7Pn!4qT0uf!d0+wJsn^;^dK)88c@q+#{CAD@y_<-IHxNdy9Byp@$=R z-6=a|An56V!Aujq?@Ytocw>1FMV>V{OUpF^QDXV@ ok-4&A`TzQbcmaZ3J?x8f1nHYC?3~AC{{+~Il_ZGUBIV`(23x>AdH?_b literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_notes_list_note_item.xml b/app/src/main/res/layout/fragment_notes_list_note_item.xml index 5a2c834bb..715f5bed8 100644 --- a/app/src/main/res/layout/fragment_notes_list_note_item.xml +++ b/app/src/main/res/layout/fragment_notes_list_note_item.xml @@ -18,6 +18,7 @@ android:layout_alignParentEnd="false" android:layout_alignParentRight="true" android:layout_alignParentStart="true" + android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_alignWithParentIfMissing="true" android:ellipsize="end" @@ -38,6 +39,14 @@ android:textColor="@drawable/list_item_color_selector_low" android:textSize="14sp"/> + + Date: Fri, 1 Jul 2016 21:45:19 +0200 Subject: [PATCH 08/25] Refactoring: move common code to new private method getNotesRawQuery(String sql, String[] selectionArgs) New method getLocalModifiedNotes() is a preparation for bugfixing #117 --- .../persistence/NoteSQLiteOpenHelper.java | 70 +++++++------------ 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index cb27390f2..409e152ef 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -141,14 +141,15 @@ public DBNote getNote(long id) { } /** - * Returns a list of all Notes in the Database - * - * @return List<Note> + * Query the database with a custom raw query. + * @param sql SQL query + * @param selectionArgs Arguments for the SQL query + * @return List of Notes */ - public List getNotes() { + private List getNotesRawQuery(String sql, String[] selectionArgs) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.rawQuery(sql, selectionArgs); List notes = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle()}); if (cursor.moveToFirst()) { do { Calendar modified = Calendar.getInstance(); @@ -166,30 +167,22 @@ public List getNotes() { return notes; } + /** + * Returns a list of all Notes in the Database + * + * @return List<Note> + */ + public List getNotes() { + return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle()}); + } + /** * Returns a list of all Notes in the Database * * @return List<Note> */ public List searchNotes(String query) { - List notes = new ArrayList<>(); - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? AND " + key_content + " LIKE ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}); - if (cursor.moveToFirst()) { - do { - Calendar modified = Calendar.getInstance(); - try { - String modifiedStr = cursor.getString(3); - if (modifiedStr != null) - modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); - } catch (ParseException e) { - e.printStackTrace(); - } - notes.add(new DBNote(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4), DBStatus.parse(cursor.getString(1)))); - } while (cursor.moveToNext()); - } - cursor.close(); - return notes; + return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? AND " + key_content + " LIKE ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}); } /** @@ -197,25 +190,16 @@ public List searchNotes(String query) { * * @return List<Note> */ - public List getNotesByStatus(DBStatus status) { - List notes = new ArrayList<>(); - SQLiteDatabase db = this.getWritableDatabase(); - Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " = ?", new String[]{status.getTitle()}); - if (cursor.moveToFirst()) { - do { - Calendar modified = Calendar.getInstance(); - try { - String modifiedStr = cursor.getString(3); - if (modifiedStr != null) - modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); - } catch (ParseException e) { - e.printStackTrace(); - } - notes.add(new Note(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4))); - } while (cursor.moveToNext()); - } - cursor.close(); - return notes; + public List getNotesByStatus(DBStatus status) { + return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " = ?", new String[]{status.getTitle()}); + } + /** + * Returns a list of all Notes in the Database with were modified locally + * + * @return List<Note> + */ + public List getLocalModifiedNotes() { + return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ?", new String[]{DBStatus.VOID.getTitle()}); } /** From c66c3d690318669e692b08e1279b0cf47c11ae7c Mon Sep 17 00:00:00 2001 From: korelstar Date: Sat, 2 Jul 2016 22:07:32 +0200 Subject: [PATCH 09/25] last part from the previous refactoring --- .../owncloud/notes/persistence/NoteServerSyncHelper.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index f82db47ab..8014fc584 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -19,6 +19,7 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.SettingsActivity; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.util.ICallback; @@ -103,7 +104,7 @@ private void asyncTaskFinished() { } public void uploadEditedNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_EDITED); + List notes = db.getNotesByStatus(DBStatus.LOCAL_EDITED); for (Note note : notes) { UploadEditedNotesTask editedNotesTask = new UploadEditedNotesTask(); editedNotesTask.execute(note); @@ -111,7 +112,7 @@ public void uploadEditedNotes() { } public void uploadNewNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_CREATED); + List notes = db.getNotesByStatus(DBStatus.LOCAL_CREATED); for (Note note : notes) { UploadNewNoteTask newNotesTask = new UploadNewNoteTask(); newNotesTask.execute(note); @@ -119,7 +120,7 @@ public void uploadNewNotes() { } public void uploadDeletedNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_DELETED); + List notes = db.getNotesByStatus(DBStatus.LOCAL_DELETED); for (Note note : notes) { UploadDeletedNoteTask deletedNotesTask = new UploadDeletedNoteTask(); deletedNotesTask.execute(note); From 71867781fd35f113f9636dc6fce0cc9dcf02e221 Mon Sep 17 00:00:00 2001 From: korelstar Date: Tue, 5 Jul 2016 14:49:00 +0200 Subject: [PATCH 10/25] use material design icon and remove old holo icon --- .../drawable-hdpi/ic_sync_alert_black_18dp.png | Bin 0 -> 518 bytes .../res/drawable-hdpi/ic_sync_error_holo.png | Bin 1154 -> 0 bytes .../drawable-mdpi/ic_sync_alert_black_18dp.png | Bin 0 -> 344 bytes .../res/drawable-mdpi/ic_sync_error_holo.png | Bin 790 -> 0 bytes .../drawable-xhdpi/ic_sync_alert_black_18dp.png | Bin 0 -> 658 bytes .../res/drawable-xhdpi/ic_sync_error_holo.png | Bin 1411 -> 0 bytes .../drawable-xxhdpi/ic_sync_alert_black_18dp.png | Bin 0 -> 855 bytes .../res/drawable-xxhdpi/ic_sync_error_holo.png | Bin 1889 -> 0 bytes .../ic_sync_alert_black_18dp.png | Bin 0 -> 1003 bytes .../res/drawable-xxxhdpi/ic_sync_error_holo.png | Bin 2231 -> 0 bytes .../res/layout/fragment_notes_list_note_item.xml | 6 +++--- 11 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-mdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_sync_error_holo.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_sync_alert_black_18dp.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_sync_error_holo.png diff --git a/app/src/main/res/drawable-hdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-hdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d4f7994ef087505aa470ab188a6a8178e290a133 GIT binary patch literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i1|)m0d(}6TtKf~3pEvFe67$-)rm$RcltG)BOghZX5i4k`W zxp7BbnlRt`~km%N(uSznOAJ(U!WoM-${wX^Gnnloyh5TXLE)%%5cm! zFs?2;_lG5IhS?d-zhVvf3*p1j>SfKCo6UJ zFDEekvDp9nKyS_c6Uy2re=bw$I(Y48f6u;<^+jPtdb^nHRDVlMd-~?t-VtD3k zB=@X-{jBnx_UX3|a2JSt=skO8v&XNcXEWMf9@J0U(Qr|JdDwekOfh)6`njxgN@xNA DzE{|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+eS5K)hhiu0R{01Y44~ zy9>jA5L~c#`DCC7XMsm#F_88EW4Dvpb_@*6L7py-ArXh)&all0aTGXS|6MQLV8xF{ zF}Dqz9AR}LVQO9Rt@2wPlTsI-t1mg__Q-|yS*BzR$3xJJtn zsRhhGI7Nh0>KKbHb6gH2tv;8_daQvpL28ESc^R$y8mqa$`T40$-%Ky4Gm1wX()^$7t5nG?!*)-4hjVMp^!KWjiw^A4 zdm#6L;f97;k@C_&IT2>l!d0^v71!>bd_;5Js!_|CG8+k!l&u3RLwZh?BsdgkkAs@96W&X}#RU{@7uZ0V~oA+Nv( zwSqTTW5n3G4u~e`uUjT?WJ;E14e#`8zYnM;OntGRTWdBoRqU6uNPL!m>IPp8OCMwY3BR)IypsPcHaO<6 zWqW?U>l-7yKEr0Mvhthx=H74ZrplYY{$9OpwgA(O%x7^PELsapLlyqBMvMG>p5~YP z*HNS7OTO73_4tNkhrZsQ%5S;aN=-P&RMDkt(w6*%i_UrZ_O%PlGO9_Rr10_WUls|2 zJxWVgehs{xR#M~n*(~JBV_lcGi!+{f@916hYq#xf-$M_!JxW);GFfekP=x#j{~u+N z{1SVGcPJmb)%8RuroFTn$j@`B8R;qD)=AB~Q z&am#(9qR<{g1#O5XC06Azn1oy`{UhDLOXQtg*PTX*z!T};lAVQcMW29e9r0EQ7?6@ zbZM2@(n(4e8nZ=nUK?<%`MSW*EAr-vYdp97Pu{GHcw*JrUsamLcD*KYxz5j(YrJM{ zDYbmEmRstZXRvwaZAs-Fx9jC@v}DVg99kN6!>adrpzEawRcCvVqF34lTo&GU_%&B9 zTxCm^p3Lw_e^tlYnCZak+&bGG9orb|ih)croLsa#h0)uiqB$)$YeMv@-N zXR1zbe=N0gNw#}^&%YDh0UV9{clrfs@;m)^`oHPV<)l`r7v}B{UoKlZrD8QOo2iz# zMwFx^mZVxG7o`Fz1|tJQ6I}yyT_ej7LsKh5Ln{+AAlJ&kApPe3Boqy~`6-!cmAEyC zwWQwzYLEok5S*V@Ql40p%HWuipOmWLnVXoN8kCxtQdxL16;u{5c)I$ztaD0e0svFC B;>!R4 diff --git a/app/src/main/res/drawable-mdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-mdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b4947b7531f4ba404127680aaa435498fd9b7edd GIT binary patch literal 344 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|wj^(N7a$D;Kb?2i11Zh|kH}&m z?E%JaC$sH9f@KAc=|CE+pF!Co&kM+T?CIhdVsZNEWJA6t1BurCD-zWnvh}&KOC38TKvFd|AC`-~PYc_3f$~%GQKCtp3O# zX4lMM;ZgA?aSEU6P46v}G!KL=HeS^z9`M6Qh_kFuWa>Wesgs#`1$OrsJ>Ya!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ueoXKL{?^yL>WGgtNdSvKUBvfU(=jY&)Rw&pcfmLp+Y3opw@>B~YYo|D|VQSLa6D z_{bn8mRrGpk2$(bNO29v`y}0O+f>?EIg`57STa)!|4+7yUA0R^sc`Z+&%4Hd()aP! z*DPwvw>}wfJ*nL5*I$h$4hgon;5{rAIvtBxYPL;&DKK~X1?B?oD~-kqWkqV%9BY_N zkDvA~>^^>)DYN4qbEtZ9k*MV)hG>U;w=g5dQwJ7`2tV*UCJ=CyaV^97Nw-<6ZZ$@{ zxE-`3P=x;g!v+2rhUZ@Wa|<81SXpaE-|zAF_`oZ{JguSJHFVl`|8;727nweA-8DH< z;ab*gp<4xeSQ9v2O#ZlwxkKi}uGQ_^Z%$gz%<21j_vuQZ(-l$&egt2#{ZRTfk%wt< zi>1Z2*yi_}C+RKt`fp{4drhxZX1!3l!o=?{Z&l7T$;j(kX2d0WQD@urmqK&Tc?Uk? z+N5taS$JW8n#2rdyApHlUFuPvCeBzsc~{NColI-IlOAyGP&?x?^TN6AmT7g9-?_Mb z(G@q#P;XH@#291Mz<5Z&jKle6*@YV2Dvsp&0&LkwKPJu6jZ$1AsQqc8!{ySmEH71g zdUCIIJ)Z3|bpyxWq!>LWgJrD|%-1r$ZH>Qfm^k%1=jyv=ujlv&hcH~yC{S7UHL+r8Vb{*JddJqh7VdHKHUXu_Vl#^x7@Arc8d{l{0l8KN2I)8NC!uJ_%}>cptHiBAtR?*(P=h4MhT#0PlJdl&R0hYC h{G?O`&)mfH)S%SFl*+=Bsi5@9;OXk;vd$@?2>|L1FDL*2 diff --git a/app/src/main/res/drawable-xhdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-xhdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..031a5c40ccb183a427f898234d736d927e18c3b5 GIT binary patch literal 658 zcmV;D0&V??P) zCUFiI5^V3F-RIdxGw(E!NTh*fe8f6V51H5}oXy@n*q7kEA5U;;ND`G~QV1QyD;$mb zcJO`(LRZSjRACyMx;1ZM6*D=z6S$o{aVg8z&^o5-5PA>=`HiI#yZFcVO8UQGFZ#Y) z_S3ROeFQ7f_f0&lla)}OQ@MC6m>q=BEn$npORNn+rn7(_;dRzOEtEJ3li1ar-h~SL zvXU+PS7#O@+piL3XI`&(RiTVf5gNjJ>e$ca4T=~Zi`s8er!^66-LetZY9*lqQTyA7 z2@y)w!Qm7_?TYbX(TduQ{9ut%_syUH?*z>vsCpF+MeUuG4CeF-S|i120BlF`=TjUe zvRUdlE#}6XxK@+QLKJsbSL-UV7CFXcXl5D4+q>*6E5eH??&Co+nZ>Wj`9?&h-Dri!3kgOss;bAV`)}TSAgsVc)6(XLAGq|5U`K#?r_VZTc zyD6MywQxLvP#G@bN6znqaOo>WKjCZQS=~&8BPdq==QxS)bqF=Fq5hhQv4yL+jGtA; sMM^y?GH-ESkc~ehNNkKT#uy3y0hHSyBJs6?qpkD^L|X5*AA0sSD4MX5!M!!$0j!v<#oCw?mEIr z(X7W3nS1jJJ>)of)Dn?GO+tyf>;8BDxaWJm-_QAczUTA({P!&kq){~0cB%mYG<~RK zx&rY(T2)yw!=PXC6#!>@1b6_@dQN>aR!PAjN9Yt!(DDfNRzXbTsPq5;j_(41p9jE( zLgLQ@z*Ag#7XyG}B>+28FWwDuRxFemeiSnJ{-cEV8p{<5B9$6(M1jPLibmlo;1-mR2D+AcCT7`uIMWP`PJ>zu=oY@d zz;^d6Ay1dUn*+NssgP~fy;{9%L{!u1D~anvYl}37_e5Ewi<3)KNKQx?k7w z5#y7VoFuQU9tpTuP41m9yFoKfT>C~6m4L3^Il+#uovE7PoZf{eP}bSuu%_27*u}|` zzx@Irn@bwY+HzZb<4ki;KH)C)xgO8BKX)wrX~}j*w%s(0j>Dj_hRDl`YjkU-%gBd-`7f} zyXfp4(-oTZZ?<57o=DXhf#V$Pi$~q}qhg(HAYixnMVtHF-l?&IwxE4)FxEA$Wu&6g zmy4m$$3?I%nF)BWBK`yRLPj653N6WM*lJmGGdsK#BvTFnt;Qp=U2pTPqPgMn$(KY1v4AZy>TzEi#>bVeZZ%X28;3; zs#1G?6Zll3*_&XLF%dZ4F->AUipGx2`SH?#yll62o+S#6%Lib6yqAYrEV)&UW_~U!(OCem44?y!ESDUDFDG*)+Qv1r+L_DlTl}!=#VoLPYEYg&9Xw{@+Y6CLHfKFswg*=Qdy4xjs$kiiq9y0da)v|; zQWWWl208w=-z1nYX=+9(Ci3ZCd)MPwQUWo*wzI{5Vn>Y#kqm4J8G->axG zdJORcsVJgEtFN*eJxd-iu{5Ngbg6cKRywda86DExd^IOxt#ec0P>%_C>n60X>l207 zCf@5&{22?bcQ}_B%VjynaaalfL_ER93U6;kB<&~IIuZzuHg*c?h{vDk7o#cylLbX&zRhQ E0P1&EMF0Q* diff --git a/app/src/main/res/drawable-xxhdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-xxhdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..83cf8b06c40dc57250529a7aed921eb17cfc9170 GIT binary patch literal 855 zcmV-d1E~CoP)r zt5ogbD-F70aE)|q0jEs{JY)#=6u73@egXG@7p`ZqtGC@8QQZKZxJpn)t&D!p&l&pL ztk}^;Q5Ae!z>Ev91tIui2LG&J`tG+Z_%G#!8V8zM&@aH4CopMCKMU+@{y%}yw4}YB zvHQe#u;;*!yfY?f4Rw3BHG30yrrFM5{wRp5X*2a3n8+BzWW`FPzkH{FLoLQiTGC!C zc3V4t4}8lrQwiE5zEV-`c5_E{(-y1%T+nQwU}%gKGhA3!DHDIMnSd2B-#n&Ktvo4i1JwfbqFLhiyo*loBJR3^_Af+L&$0Vxg2vW^yQ(Tw)ekC|VcSm9SFM9GbG! zIjGj@v^6GHwsZn9x4w2fD)PH zM3o@vN611Xx95U@C;{nUoGT6hp4?E_2#}F*P&Ac@2ly>Ivl3zuNTRv|K*BBnNKOTS zHHnlw0|2p>05EqF0MNGpKr6Da#vLPhfY4lsPQccWbN9u=9ElxBCU0c+cIF=%`&;tKZwgxN!q}(%NdvNVxqm%>VWo zH{@O^m=M|q>0U>P!H;AMp~4A@4k2fA8~^cvxt1Okii`!123d}w$;Dem)H8}9G#D%LDy4McuFyu4EF(l&@iMPf}o~+ z=jIYidch&J_Tsir{-B40)ly2&%eB)o%-c+=PR7jW75b{)I1VWt`YO>^bj!^A)Lrhb zCwtalMavy$KZK3e)qarGVCGzJEEi&{T>GC$gX3j>Ba9=Hwg#rpw+tQejw;hFLgXu4 zNoY%7FTgre`PE!YnezvG3v!QKh7TUpBHN=tW~G@+zMBTD<0RF5<0m?{K9`cUvmXmm z9{6xr#!Z@J;~Do+mzoLeBw=&K4;&^VjWv1lQ~@H47E({PFCE!gbcZ-y zuku%|=sx)iQPsD4|1NCHNK{V=A}2_J&fpqj9K@iXG2FCb$8V*anDXx-ExbnTYRBf5i`s`)# za0ghkj`HPvQ*5W=U^(3(ism(jL+w?{mU;7GBQ2!1&8GxHX)g+%dBTEURnb$bRe!}c z6L`v&`1su$V0Y$pC+9df{{eRGz=ENz4n_kCFTB!HoOz0@+`|g14HFf%^hUn&?S|OO zxZP80`dK?dYbcJr_t%PEV*d(}S~Yw-ZOy%wD3@w|#%k(igdhe#uHlKdOT0syhigO3 z*#}%lkwzTEV)>5I$2hy8QYO%Ki72w%qHD|)3qR?OKF5`3f?VK;J?zUFa5t$k_Uk0& zwbsP_&3aD{_NLUQtJ6Z#IT@U*HZW@qm_YU^yTYSw!BlA!wDS9md{^`82|W|zp>a2! z(rNnQh8cJP*pg1thg{dP!M}Ir(3wvas&9>=#^B5T!6(#NnnS@ODKXzH#&Ix_ZDYzX zQoPx1k=p?eP(FSY(WbY^UMZ;mWNp_!o94RAbgR_Tt%7qKXT5I-#%*`t4{h=Lcq0w! z*)5;w20CiDtKvIcZ)bW$4t47NrLY?cvIQ7-r=* zdH)Ife!0swOV6wNvt|`Rr5}AOy2zt?@_Au#&)`ZX$pjl^*UV4W7pdg$3eDCzD zy2!3CVQ<8r)%h0;S~*F5Je8-#u#$-W)4G@l-qa)Z>}HlZwIu-)j0xv_HcnjnC>J{> zN2fa?@@%P5tP<%Q&$}4dspYjyl7)dOK7B$y+duC(s?I54EOLkGFm8Eu@6OCm@1jY6 zTRnW18Q1u(<{L%5a%a-Ows^H zv))(jak)+WYE8b3X{nq%9_}Jk<0?IP9X>gLaQGtMh#AU1(=VFxo@UCaFWoaAo%;39 zzX=ILva}EISI6Si7_+93D%q0nBT|0RE1orR`vv3qIFM+00&8W1us zMNe78&hZZAG?O>&+30MT2=1MCV0kg8;b5^Y7w`AFdN>ekvM`lqB~!&{znr2@#)c>| z@|kZl2c7W|D&TKBeb0s|+6*43>Y68clQP1$lMF7rzZJWnXf}A}p_<5{&L;t$#X>c$ezTR0H+E^7LRH1 z|A8IaiTb#zUtoXEpL?V^RI*7BF@$3=^njQkbYN7F1OO{bq>Z`d&*oNk9>{%YBob|7 zE1_sh%iz8d+P@585%iE#jQ=)hrUnm74B9^`9Ems;6BiH_1mNfqv>*d=SU_+PH7J12 TINK2PW7Pp<0>z1sKY8&VH{eea diff --git a/app/src/main/res/drawable-xxxhdpi/ic_sync_alert_black_18dp.png b/app/src/main/res/drawable-xxxhdpi/ic_sync_alert_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..768785eac639c0234b45e1511d5681b697175e57 GIT binary patch literal 1003 zcmVX5XGK^Jd9|A4Y0By{oU9_rLZsK3I4 z#|~Z06cp*yL8wDQH#IC>5Vuyh!xV_TZ}xNdo1NX5AH2)=eBaFc-Z*dGyje&j5{X12 zkw_#GiNycRHN#w0gQFN*2tYSb09JuY3kixYrw@1q9GqR5_El0a#Pf7=oh9IjvE_Q{XKy9zoCvFcC}61hsq4Ixrld z?2GiEV<6vbIRik+Q>ilW4CrXE!4`t5zz)#gEID1kM^9z;x%eFmEvO2tpub@aG3Tko z9xzl#PAfrgfL+h|LTEWRkpH#Hz-XO$Ed&++p1X@1cck9k8&c4Mr!mtFP#`zquEad!&dx6Qf3ig!s+atyj>{_^B22MTie7udhW;I4qFf6Spk|eHP*mj2aP=5h1>%xmYV` zU5G!z?oNjw2RszcZCDo6i@bE8?M3qu1dR!GJ|(~+{+dwOqEb$bceW*=&ZfQRA5+Ln z2fl^+d2AZn-k6z-6+>qIq;PW9yl84!Or4Vf>nLmY1H!%+!$6 zOhMD0oC~4kfE%p$g@TW9pIjt4%@Q<KEils$P$C>O{q$s>Q_4Y|H^mSNm1 z-&baZ$a{pO8FolP4 zpcWAo%QP;uqOv)nR1^bL+&Tgyjz~uZHh+lvj3P?7VQz zpg^xw%p1ZeuSsP{lOU1ah(f{iV49N2W*VzJOI9J=0^$6)dKdNMz#K-wn7$4{YmiFc8rGTKBnnFzqX;oDSac>st6hle zmA1*8jQ)qlJGD)+Vgt%ZMoszxqY|q}uHPaU%iXU#T2RE;z==jRRun`d)+-CNsLmWK z4x?aS=qj}eW^#~RmV(QqaYB{3G%knBqamtXgr-83A$*jpj0)z8Bl)2m{vtM3XEGx?CA#Qajro3!4UPI$EG#mjh*@ux>Ghh$ zE=bDPoAsuAy#W%5Ab%;MRO=SxfQ1^p4q1#E)rF`k%Ba^uuaXR_-$6hbqC!<1o{Gj~ z^FnCc5S9`P9%a%LD3_Vb<1@KT6-s%7SN*@qV_>8h3!U;`owIm_wd2C^?Hpjj+et!o z*!UQ+IT@)ASONfXJXS1}6^%b?ii=4VdYv1J-Ee0O4-v|f#WGpG`qK@Nf(?I6R|itFGy_d1== z@OKDLd2ny(kQb1Nl3H^n(COo?lOp~~j>lKNh$%W92kI*11TcGGcGXTmd^qws1K9uEKZTy8UIBGhxu^B~jsD^` zVEwlFmI5^Ohb!v;(O~F(4~&1fi?p1?`*!QsY_zAUAo?2|ckS^douADeojoRgKCW>{ zS|iwxv$|NX_WS%{_D_Nbq!dF6ovj zuJ=0vo5$p{iWip~@gVNRotVjQuK2m`?()k5)r+JdOm3#A=$PJGcInv8JFn_rk}W7 zIFbFa^;!%9E7Q1W02ofC-a2s-)Vr5=T$?EA4NP@^Lf>AK_f^xz`EBO>Lk#>cqdPy! zP^Hv7@~t7ro$7w3&dRs`=s|nTLT=ht+LuvZ7EiS2P#&%*0AtnzWwT!P71Yy_PS&nV zUiPV#%I(fp3U$+{65NR?>3rntinbpn4Up%B2Sc4#KOIVBT&6X9yASO+lm(i%^}1AF zJ5o!3z^ludV1Xm^FPjN=3w2Y?Ij~L(n3V^1mb%J4>ps~_uzyV5boe$@QJK2lR68J} zfx^8No-4CE(^(C*=oNzw;v7=-XqO)+)|&R)cjW0m1^A&h^@7KNjGD8|2`#`iGTL zye-l{!9u$}J?`5ReE}EYIoXn*zS2%D{2M}UBR>)3;(_Me9+%nHsvQl4^NvWO)fe9V ztoO=k{5&}ORVP7m->m^^J$!Xaa3m3%<9`Ko+OK)$lMdmMZfrk)DsV4Haj#;z z@op)<*HZ%k<7Pn!4qT0uf!d0+wJsn^;^dK)88c@q+#{CAD@y_<-IHxNdy9Byp@$=R z-6=a|An56V!Aujq?@Ytocw>1FMV>V{OUpF^QDXV@ ok-4&A`TzQbcmaZ3J?x8f1nHYC?3~AC{{+~Il_ZGUBIV`(23x>AdH?_b diff --git a/app/src/main/res/layout/fragment_notes_list_note_item.xml b/app/src/main/res/layout/fragment_notes_list_note_item.xml index 715f5bed8..2c38a8a59 100644 --- a/app/src/main/res/layout/fragment_notes_list_note_item.xml +++ b/app/src/main/res/layout/fragment_notes_list_note_item.xml @@ -41,11 +41,11 @@ + android:src="@drawable/ic_sync_alert_black_18dp"/> From 8042561c3621e491cbc5576c7af88fa196fe248f Mon Sep 17 00:00:00 2001 From: korelstar Date: Sun, 10 Jul 2016 14:37:08 +0200 Subject: [PATCH 11/25] Refactoring NoteSQLiteOpenHelper and NoteServerSyncHelper in order to fix several bugs belonging concurrency and synchronization. Outstanding: documentation, testing, cosmetic changes --- .../owncloud/notes/model/NoteTest.java | 2 +- .../android/activity/EditNoteActivity.java | 5 +- .../notes/android/activity/NoteActivity.java | 10 +- .../activity/NotesListViewActivity.java | 20 +- .../activity/SelectSingleNoteActivity.java | 5 +- .../notes/android/widget/AllNotesWidget.java | 7 +- .../android/widget/SingleNoteWidget.java | 4 +- .../owncloud/notes/model/DBNote.java | 41 +- .../owncloud/notes/model/DBStatus.java | 24 +- .../model/{Note.java => OwnCloudNote.java} | 39 +- .../persistence/NoteSQLiteOpenHelper.java | 256 +++++++------ .../persistence/NoteServerSyncHelper.java | 349 ++++++++++-------- .../owncloud/notes/util/NotesClient.java | 20 +- 13 files changed, 444 insertions(+), 338 deletions(-) rename app/src/main/java/it/niedermann/owncloud/notes/model/{Note.java => OwnCloudNote.java} (59%) diff --git a/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java b/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java index 6d6f2dece..c98281312 100644 --- a/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java +++ b/app/src/androidTest/java/it/niedermann/owncloud/notes/model/NoteTest.java @@ -11,7 +11,7 @@ public class NoteTest extends TestCase { public void testMarkDownStrip() { - Note note = new Note(0, Calendar.getInstance(), "#Title", ""); + OwnCloudNote note = new OwnCloudNote(0, Calendar.getInstance(), "#Title", ""); assertTrue("Title".equals(note.getTitle())); note.setTitle("* Aufzählung"); assertTrue("Aufzählung".equals(note.getTitle())); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index e04959e88..edfc27b09 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -17,7 +17,6 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.model.DBNote; -import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; @@ -106,7 +105,7 @@ public boolean onOptionsItemSelected(MenuItem item) { private void saveData() { ActionBar ab = getSupportActionBar(); if (ab != null) { - ab.setSubtitle(getResources().getString(R.string.action_edit_saving)); + ab.setSubtitle(getString(R.string.action_edit_saving)); } // #74 note.setModified(Calendar.getInstance()); @@ -127,7 +126,7 @@ public void run() { runOnUiThread(new Runnable() { @Override public void run() { - getSupportActionBar().setSubtitle(null); + getSupportActionBar().setSubtitle(getString(R.string.action_edit_editing)); } }); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java index c1c2f22d2..f93a98278 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java @@ -12,13 +12,13 @@ import android.widget.TextView; import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; public class NoteActivity extends AppCompatActivity implements View.OnClickListener { public final static String EDIT_NOTE = "it.niedermann.owncloud.notes.edit_note_id"; public final static int EDIT_NOTE_CMD = 1; - private Note note = null; + private DBNote note = null; private int notePosition = 0; private TextView noteContent = null; private ActionBar actionBar = null; @@ -27,10 +27,10 @@ public class NoteActivity extends AppCompatActivity implements View.OnClickListe protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_note); - note = (Note) getIntent().getSerializableExtra( + note = (DBNote) getIntent().getSerializableExtra( NotesListViewActivity.SELECTED_NOTE); if (savedInstanceState != null) { - note = (Note) savedInstanceState.getSerializable("note"); + note = (DBNote) savedInstanceState.getSerializable("note"); } notePosition = getIntent().getIntExtra( NotesListViewActivity.SELECTED_NOTE_POSITION, 0); @@ -114,7 +114,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == EDIT_NOTE_CMD) { // Make sure the request was successful if (resultCode == RESULT_OK) { - Note editedNote = (Note) data.getExtras().getSerializable( + DBNote editedNote = (DBNote) data.getExtras().getSerializable( EDIT_NOTE); if (editedNote != null) { note = editedNote; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index db3af0e8e..a708446ac 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -12,6 +12,7 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -24,7 +25,6 @@ import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; -import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.model.SectionItem; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.ICallback; @@ -82,7 +82,7 @@ public void onFinish() { setListView(db.getNotes()); } }); - db.getNoteServerSyncHelper().downloadNotes(); + db.getNoteServerSyncHelper().scheduleSync(false); } }); @@ -103,11 +103,12 @@ public void onFinish() { if (mActionMode != null) { mActionMode.finish(); } - adapter.checkForUpdates(db.getNotes()); + // adapter.checkForUpdates(db.getNotes()); // FIXME deactivated, since it doesn't remove remotely deleted notes + setListView(db.getNotes()); } }); if (!PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean(SettingsActivity.SETTINGS_FIRST_RUN, true)) { - db.getNoteServerSyncHelper().downloadNotes(); + db.getNoteServerSyncHelper().scheduleSync(false); } super.onResume(); } @@ -132,6 +133,9 @@ public void onClick(View v) { */ @SuppressWarnings("WeakerAccess") public void setListView(List noteList) { + Log.d(getClass().getSimpleName(), "setListView("+noteList.size()+" notes)"); + //db.debugPrintFullDB(); // FIXME remove + List itemList = new ArrayList<>(); // #12 Create Sections depending on Time // TODO Move to ItemAdapter? @@ -162,7 +166,7 @@ public void setListView(List noteList) { month.set(Calendar.SECOND, 0); month.set(Calendar.MILLISECOND, 0); for (int i = 0; i < noteList.size(); i++) { - Note currentNote = noteList.get(i); + DBNote currentNote = noteList.get(i); if (!todaySet && recent.getTimeInMillis() - currentNote.getModified().getTimeInMillis() >= 600000 && currentNote.getModified().getTimeInMillis() >= today.getTimeInMillis()) { // < 10 minutes but after 00:00 today //if (i > 0) { @@ -319,7 +323,7 @@ public void onFinish() { swipeRefreshLayout.setRefreshing(false); } }); - db.synchronizeWithServer(); + db.getNoteServerSyncHelper().scheduleSync(false); } } @@ -355,7 +359,7 @@ public void onNoteClick(int position, View v) { NoteActivity.class); Item item = adapter.getItem(position); - intent.putExtra(SELECTED_NOTE, (Note) item); + intent.putExtra(SELECTED_NOTE, (DBNote) item); intent.putExtra(SELECTED_NOTE_POSITION, position); startActivityForResult(intent, show_single_note_cmd); @@ -413,7 +417,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { case R.id.menu_delete: List selection = adapter.getSelected(); for (Integer i : selection) { - Note note = (Note) adapter.getItem(i); + DBNote note = (DBNote) adapter.getItem(i); db.deleteNoteAndSync(note.getId()); // Not needed because of dbsync //adapter.remove(note); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java index b9bb6d7b0..0b171f1d1 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SelectSingleNoteActivity.java @@ -18,7 +18,6 @@ import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; -import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; /** @@ -39,7 +38,7 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_select_single_note); // Display Data db = new NoteSQLiteOpenHelper(this); - db.synchronizeWithServer(); + db.getNoteServerSyncHelper().scheduleSync(false); setListView(db.getNotes()); @@ -80,7 +79,7 @@ public void onNoteClick(int position, View v) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_single_note); appWidgetManager.updateAppWidget(appWidgetId, views); - SingleNoteWidget.updateAppWidget((Note) adapter.getItem(position), context, appWidgetManager, appWidgetId); + SingleNoteWidget.updateAppWidget((DBNote) adapter.getItem(position), context, appWidgetManager, appWidgetId); Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java index 83a84f785..726c335f9 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/AllNotesWidget.java @@ -15,7 +15,6 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; -import it.niedermann.owncloud.notes.model.Note; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; /** @@ -62,13 +61,13 @@ public StackRemoteViewsFactory(Context context, Intent intent) { mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(mContext); - db.synchronizeWithServer(); + db.getNoteServerSyncHelper().scheduleSync(false); mWidgetItems = db.getNotes(); - mWidgetItems.add(new DBNote(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); + mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); } public void onCreate() { - mWidgetItems.add(new DBNote(0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); + mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", DBStatus.VOID)); } @Override diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/SingleNoteWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/SingleNoteWidget.java index d2c695629..26593ff4b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/widget/SingleNoteWidget.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/widget/SingleNoteWidget.java @@ -12,7 +12,7 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.NoteActivity; import it.niedermann.owncloud.notes.android.activity.NotesListViewActivity; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.DBNote; /** * Widget which displays a single selected note. @@ -20,7 +20,7 @@ */ public class SingleNoteWidget extends AppWidgetProvider { - public static void updateAppWidget(Note note, Context context, AppWidgetManager appWidgetManager, int appWidgetId) { + public static void updateAppWidget(DBNote note, Context context, AppWidgetManager appWidgetManager, int appWidgetId) { RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_single_note); if (note != null) { updateViews.setTextViewText(R.id.single_note_content, note.getSpannableContent()); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java index 646d869c2..78fddc134 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java @@ -1,18 +1,28 @@ package it.niedermann.owncloud.notes.model; +import java.io.Serializable; import java.util.Calendar; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; +import it.niedermann.owncloud.notes.util.NoteUtil; -public class DBNote extends Note { +public class DBNote extends OwnCloudNote implements Item, Serializable { + private long id; private DBStatus status; + private String excerpt = ""; - public DBNote(long id, Calendar modified, String title, String content, DBStatus status) { - super(id, modified, title, content); + public DBNote(long id, long remoteId, Calendar modified, String title, String content, DBStatus status) { + super(remoteId, modified, title, content); + this.id = id; + setExcerpt(content); this.status = status; } + public long getId() { + return id; + } + public DBStatus getStatus() { return status; } @@ -21,8 +31,31 @@ public void setStatus(DBStatus status) { this.status = status; } + public String getExcerpt() { + return excerpt; + } + + private void setExcerpt(String content) { + excerpt = NoteUtil.generateNoteExcerpt(content); + } + + public CharSequence getSpannableContent() { + // TODO Cache the generated CharSequence not possible because CharSequence does not implement Serializable + return NoteUtil.parseMarkDown(getContent()); + } + + public void setContent(String content) { + super.setContent(content); + setExcerpt(content); + } + + @Override + public boolean isSection() { + return false; + } + @Override public String toString() { - return "#" + getId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); + return "#" + getId() + "/R"+getRemoteId()+" " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java index ea3393b31..46e1b195d 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java @@ -6,7 +6,29 @@ */ public enum DBStatus { - VOID(""), LOCAL_CREATED("LOCAL_CREATED"), LOCAL_EDITED("LOCAL_EDITED"), LOCAL_DELETED("LOCAL_DELETED"); + /** + * VOID means, that the Note was not modified locally + */ + VOID(""), + + /** + * LOCAL_CREATED is not used anymore, since a newly created note has REMOTE_ID=0 + */ + @Deprecated + LOCAL_CREATED("LOCAL_CREATED"), + + /** + * LOCAL_EDITED means that a Note was created and/or changed since the last successful synchronization. + * If it was newly created, then REMOTE_ID is 0 + */ + LOCAL_EDITED("LOCAL_EDITED"), + + /** + * LOCAL_DELETED means that the Note was deleted locally, but this information was not yet synchronized. + * Therefore, the Note have to be kept locally until the synchronization has succeeded. + * However, Notes with this status should not be displayed in the UI. + */ + LOCAL_DELETED("LOCAL_DELETED"); private final String title; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/Note.java b/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java similarity index 59% rename from app/src/main/java/it/niedermann/owncloud/notes/model/Note.java rename to app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java index 4faf03a17..3021fd52b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/Note.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java @@ -8,16 +8,14 @@ import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.NoteUtil; -@SuppressWarnings("serial") -public class Note implements Item, Serializable { - private long id = 0; +public class OwnCloudNote implements Serializable { + private long remoteId = 0; private String title = ""; private Calendar modified = null; private String content = ""; - private String excerpt = ""; - public Note(long id, Calendar modified, String title, String content) { - this.id = id; + public OwnCloudNote(long remoteId, Calendar modified, String title, String content) { + this.remoteId = remoteId; if (title != null) setTitle(title); setTitle(title); @@ -25,8 +23,12 @@ public Note(long id, Calendar modified, String title, String content) { this.modified = modified; } - public long getId() { - return id; + public long getRemoteId() { + return remoteId; + } + + public void setRemoteId(long remoteId) { + this.remoteId = remoteId; } public String getTitle() { @@ -56,30 +58,11 @@ public String getContent() { } public void setContent(String content) { - setExcerpt(content); this.content = content; } - public String getExcerpt() { - return excerpt; - } - - private void setExcerpt(String content) { - excerpt = NoteUtil.generateNoteExcerpt(content); - } - - public CharSequence getSpannableContent() { - // TODO Cache the generated CharSequence not possible because CharSequence does not implement Serializable - return NoteUtil.parseMarkDown(getContent()); - } - @Override public String toString() { - return "#" + getId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ")"; - } - - @Override - public boolean isSection() { - return false; + return "#" + getRemoteId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ")"; } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 409e152ef..6a7ae6e7b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -5,6 +5,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -15,7 +16,7 @@ import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.OwnCloudNote; import it.niedermann.owncloud.notes.util.NoteUtil; /** @@ -26,25 +27,28 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - private static final int database_version = 4; + private static final int database_version = 5; private static final String database_name = "OWNCLOUD_NOTES"; private static final String table_notes = "NOTES"; private static final String key_id = "ID"; + private static final String key_remote_id = "REMOTEID"; private static final String key_status = "STATUS"; private static final String key_title = "TITLE"; private static final String key_modified = "MODIFIED"; private static final String key_content = "CONTENT"; - private static final String[] columns = {key_id, key_status, key_title, key_modified, key_content}; + private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_content}; + @Deprecated private NoteServerSyncHelper serverSyncHelper = null; private Context context = null; public NoteSQLiteOpenHelper(Context context) { super(context, database_name, null, database_version); - this.context = context; - serverSyncHelper = new NoteServerSyncHelper(this); + this.context = context.getApplicationContext(); + serverSyncHelper = NoteServerSyncHelper.getInstance(this); } + @Deprecated public NoteServerSyncHelper getNoteServerSyncHelper() { return serverSyncHelper; } @@ -58,10 +62,32 @@ public NoteServerSyncHelper getNoteServerSyncHelper() { public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE '" + table_notes + "' ( '" + key_id + "' INTEGER PRIMARY KEY AUTOINCREMENT, '" + + key_remote_id + "' INTEGER, '" + key_status + "' VARCHAR(50), '" + key_title + "' TEXT, '" + key_modified + "' TEXT, '" + key_content + "' TEXT)"); + // FIXME create index for status and remote_id + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if(oldVersion<4) { + clearDatabase(db); + } + if(oldVersion<5) { + db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_remote_id+" INTEGER"); + db.execSQL("UPDATE "+table_notes+" SET "+key_remote_id+"="+key_id+" WHERE ("+key_remote_id+" IS NULL OR "+key_remote_id+"=0) AND "+key_status+"!=?", new String[]{DBStatus.LOCAL_CREATED.getTitle()}); + db.execSQL("UPDATE "+table_notes+" SET "+key_remote_id+"=0, "+key_status+"=? WHERE "+key_status+"=?", new String[]{DBStatus.LOCAL_EDITED.getTitle(), DBStatus.LOCAL_CREATED.getTitle()}); + } + } + + private void clearDatabase(SQLiteDatabase db) { + db.delete(table_notes, null, null); + } + + public Context getContext() { + return context; } /** @@ -71,18 +97,9 @@ public void onCreate(SQLiteDatabase db) { */ @SuppressWarnings("UnusedReturnValue") public long addNoteAndSync(String content) { - SQLiteDatabase db = this.getWritableDatabase(); - - ContentValues values = new ContentValues(); - values.put(key_status, DBStatus.LOCAL_CREATED.getTitle()); - values.put(key_title, NoteUtil.generateNoteTitle(content)); - values.put(key_content, content); - - long id = db.insert(table_notes, - null, - values); - db.close(); - serverSyncHelper.uploadNewNotes(); + DBNote note = new DBNote(0, 0, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, DBStatus.LOCAL_EDITED); + long id = addNote(note); + getNoteServerSyncHelper().scheduleSync(true); return id; } @@ -90,20 +107,29 @@ public long addNoteAndSync(String content) { * Inserts a note directly into the Database. * No Synchronisation will be triggered! Use addNoteAndSync()! * - * @param note Note to be added + * @param note Note to be added. Remotely created Notes must be of type OwnCloudNote and locally created Notes must be of Type DBNote (with DBStatus.LOCAL_EDITED)! */ - public void addNote(Note note) { + long addNote(OwnCloudNote note) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(NoteSQLiteOpenHelper.key_id, note.getId()); - values.put(NoteSQLiteOpenHelper.key_status, DBStatus.VOID.getTitle()); - values.put(NoteSQLiteOpenHelper.key_title, note.getTitle()); - values.put(NoteSQLiteOpenHelper.key_modified, note.getModified(NoteSQLiteOpenHelper.DATE_FORMAT)); - values.put(NoteSQLiteOpenHelper.key_content, note.getContent()); - db.insert(NoteSQLiteOpenHelper.table_notes, - null, - values); + if(note instanceof DBNote) { + DBNote dbNote = (DBNote)note; + if (dbNote.getId() > 0) { + values.put(key_id, dbNote.getId()); + } + values.put(key_status, dbNote.getStatus().getTitle()); + } else { + values.put(key_status, DBStatus.VOID.getTitle()); + } + if(note.getRemoteId()>0) { + values.put(key_remote_id, note.getRemoteId()); + } + values.put(key_title, note.getTitle()); + values.put(key_modified, note.getModified(NoteSQLiteOpenHelper.DATE_FORMAT)); + values.put(key_content, note.getContent()); + long id = db.insert(table_notes, null, values); db.close(); + return id; } /** @@ -124,56 +150,67 @@ public DBNote getNote(long id) { null, null, null); - if (cursor != null) { - cursor.moveToFirst(); + if (cursor == null) { + return null; } - Calendar modified = Calendar.getInstance(); - try { - String modifiedStr = cursor != null ? cursor.getString(3) : null; - if (modifiedStr != null) - modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); - } catch (ParseException e) { - e.printStackTrace(); - } - DBNote note = new DBNote(Long.valueOf(cursor != null ? cursor.getString(0) : null), modified, cursor != null ? cursor.getString(2) : null, cursor.getString(4), DBStatus.parse(cursor.getString(1))); + cursor.moveToFirst(); + DBNote note = getNoteFromCursor(cursor); cursor.close(); return note; } /** * Query the database with a custom raw query. - * @param sql SQL query - * @param selectionArgs Arguments for the SQL query + * @param selection A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). + * @param selectionArgs You may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings. + * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered. * @return List of Notes */ - private List getNotesRawQuery(String sql, String[] selectionArgs) { + private List getNotesCustom(String selection, String[] selectionArgs, String orderBy) { SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = db.rawQuery(sql, selectionArgs); + Cursor cursor = db.query(table_notes, columns, selection, selectionArgs, null, null, orderBy); List notes = new ArrayList<>(); if (cursor.moveToFirst()) { do { - Calendar modified = Calendar.getInstance(); - try { - String modifiedStr = cursor.getString(3); - if (modifiedStr != null) - modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); - } catch (ParseException e) { - e.printStackTrace(); - } - notes.add(new DBNote(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4), DBStatus.parse(cursor.getString(1)))); + notes.add(getNoteFromCursor(cursor)); } while (cursor.moveToNext()); } cursor.close(); return notes; } + /** + * Creates a DBNote object from the current row of a Cursor. + * @param cursor database cursor + * @return DBNote + */ + private DBNote getNoteFromCursor(Cursor cursor) { + Calendar modified = Calendar.getInstance(); + try { + String modifiedStr = cursor.getString(4); + if (modifiedStr != null) + modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr)); + } catch (ParseException e) { + e.printStackTrace(); + } + return new DBNote(cursor.getLong(0), cursor.getLong(1), modified, cursor.getString(3), cursor.getString(5), DBStatus.parse(cursor.getString(2))); + } + + public void debugPrintFullDB() { + List notes = getNotesCustom("", new String[]{}, key_modified + " DESC"); + Log.d(getClass().getSimpleName(), "Full Database:"); + for (DBNote note : notes) { + Log.d(getClass().getSimpleName(), " "+note); + } + } + /** * Returns a list of all Notes in the Database * * @return List<Note> */ public List getNotes() { - return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle()}); + return getNotesCustom(key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, key_modified + " DESC"); } /** @@ -182,7 +219,7 @@ public List getNotes() { * @return List<Note> */ public List searchNotes(String query) { - return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ? AND " + key_content + " LIKE ? ORDER BY " + key_modified + " DESC", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}); + return getNotesCustom(key_status + " != ? AND " + key_content + " LIKE ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "%" + query + "%"}, key_modified + " DESC"); } /** @@ -191,7 +228,7 @@ public List searchNotes(String query) { * @return List<Note> */ public List getNotesByStatus(DBStatus status) { - return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " = ?", new String[]{status.getTitle()}); + return getNotesCustom(key_status + " = ?", new String[]{status.getTitle()}, null); } /** * Returns a list of all Notes in the Database with were modified locally @@ -199,7 +236,7 @@ public List getNotesByStatus(DBStatus status) { * @return List<Note> */ public List getLocalModifiedNotes() { - return getNotesRawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ?", new String[]{DBStatus.VOID.getTitle()}); + return getNotesCustom(key_status + " != ?", new String[]{DBStatus.VOID.getTitle()}, null); } /** @@ -208,63 +245,60 @@ public List getLocalModifiedNotes() { * @param note Note - Note with the updated Information * @return The number of the Rows affected. */ - @SuppressWarnings("UnusedReturnValue") - public int updateNoteAndSync(DBNote note) { + public void updateNoteAndSync(DBNote note) { + note.setStatus(DBStatus.LOCAL_EDITED); SQLiteDatabase db = this.getWritableDatabase(); - DBStatus newStatus = DBStatus.LOCAL_EDITED; - Cursor cursor = - db.query(table_notes, - columns, - key_id + " = ? AND " + key_status + " != ?", - new String[]{String.valueOf(note.getId()), DBStatus.LOCAL_DELETED.getTitle()}, - null, - null, - null, - null); - if (cursor != null) { - cursor.moveToFirst(); - String status = cursor.getString(1); - if (DBStatus.parse(status) == DBStatus.LOCAL_CREATED) { - newStatus = DBStatus.LOCAL_CREATED; - } - cursor.close(); - } - note.setStatus(newStatus); ContentValues values = new ContentValues(); - values.put(key_id, note.getId()); - values.put(key_status, newStatus.getTitle()); + values.put(key_status, note.getStatus().getTitle()); values.put(key_title, note.getTitle()); values.put(key_modified, note.getModified(DATE_FORMAT)); values.put(key_content, note.getContent()); - int i = db.update(table_notes, - values, - key_id + " = ?", - new String[]{String.valueOf(note.getId())}); + db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(note.getId())}); db.close(); - serverSyncHelper.uploadEditedNotes(); - return i; + getNoteServerSyncHelper().scheduleSync(true); } /** - * Updates a single Note. No Synchronization will be triggered. Use updateNoteAndSync()! + * Updates a single Note with data from the server, (if it was not modified locally). This is used by the synchronization task, hence no Synchronization will be triggered. Use updateNoteAndSync() instead! + * + * @param id local ID of Note + * @param remoteNote Note from the server. + * @param forceUnchangedDBNoteState is not null, then the local note is updated only if it was not modified meanwhile * - * @param note Note - Note with the updated Information * @return The number of the Rows affected. */ - @SuppressWarnings("UnusedReturnValue") - public int updateNote(Note note) { + int updateNote(long id, OwnCloudNote remoteNote, DBNote forceUnchangedDBNoteState) { SQLiteDatabase db = this.getWritableDatabase(); + + // First, update the remote ID, since this field cannot be changed in parallel, but have to be updated always. ContentValues values = new ContentValues(); - values.put(key_id, note.getId()); + values.put(key_remote_id, remoteNote.getRemoteId()); + db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(id)}); + + // The other columns have to be updated in dependency of forceUnchangedDBNoteState, + // since the Synchronization-Task must not overwrite locales changes! + values.clear(); values.put(key_status, DBStatus.VOID.getTitle()); - values.put(key_title, note.getTitle()); - values.put(key_modified, note.getModified(DATE_FORMAT)); - values.put(key_content, note.getContent()); - int i = db.update(table_notes, - values, - key_id + " = ?", - new String[]{String.valueOf(note.getId())}); + values.put(key_title, remoteNote.getTitle()); + values.put(key_modified, remoteNote.getModified(DATE_FORMAT)); + values.put(key_content, remoteNote.getContent()); + String whereClause; + String[] whereArgs; + if(forceUnchangedDBNoteState!=null) { + // used by: NoteServerSyncHelper.SyncTask.pushLocalChanges() + // update only, if not modified locally during the synchronization, + // uses reference value gathered at start of synchronization + whereClause = key_id + " = ? AND " + key_content + " = ?"; + whereArgs = new String[]{String.valueOf(id), forceUnchangedDBNoteState.getContent()}; + } else { + // used by: NoteServerSyncHelper.SyncTask.pullRemoteChanges() + // update only, if not modified locally + whereClause = key_id + " = ? AND " + key_status + " = ?"; + whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle()}; + } + int i = db.update(table_notes, values, whereClause, whereArgs); db.close(); + Log.d(getClass().getSimpleName(), "updateNote: "+remoteNote+" || forceUnchangedDBNoteState: "+forceUnchangedDBNoteState+" => "+i+" rows updated"); return i; } @@ -285,39 +319,21 @@ public int deleteNoteAndSync(long id) { key_id + " = ?", new String[]{String.valueOf(id)}); db.close(); - serverSyncHelper.uploadDeletedNotes(); + getNoteServerSyncHelper().scheduleSync(true); return i; } /** - * Delete a single Note from the Database + * Delete a single Note from the Database, if it has a specific DBStatus. * * @param id long - ID of the Note that should be deleted. + * @param forceDBStatus DBStatus, e.g., if Note was marked as LOCAL_DELETED (for NoteSQLiteOpenHelper.SyncTask.pushLocalChanges()) or is unchanged VOID (for NoteSQLiteOpenHelper.SyncTask.pullRemoteChanges()) */ - public void deleteNote(long id) { + void deleteNote(long id, DBStatus forceDBStatus) { SQLiteDatabase db = this.getWritableDatabase(); db.delete(table_notes, - key_id + " = ?", - new String[]{String.valueOf(id)}); - db.close(); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - clearDatabase(); - } - - public void clearDatabase() { - SQLiteDatabase db = this.getWritableDatabase(); - db.delete(table_notes, null, null); + key_id + " = ? AND " + key_status + " = ?", + new String[]{String.valueOf(id), forceDBStatus.getTitle()}); db.close(); } - - public Context getContext() { - return context; - } - - public void synchronizeWithServer() { - serverSyncHelper.synchronize(); - } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 8014fc584..ff54cbe48 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -1,27 +1,36 @@ package it.niedermann.owncloud.notes.persistence; -import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; -import android.view.View; +import android.util.Log; +import android.util.LongSparseArray; import android.widget.Toast; import org.json.JSONException; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.SettingsActivity; import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.OwnCloudNote; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NotesClient; import it.niedermann.owncloud.notes.util.NotesClientUtil.LoginStatus; @@ -33,23 +42,55 @@ */ public class NoteServerSyncHelper { - private NotesClient client = null; - private NoteSQLiteOpenHelper db = null; - private final View.OnClickListener goToSettingsListener = new View.OnClickListener() { + private static NoteServerSyncHelper instance; + + /** + * Get (or create) instance from NoteServerSyncHelper. + * This has to be a singleton in order to realize correct registering and unregistering of + * the BroadcastReceiver, which listens on changes of network connectivity. + * @param dbHelper NoteSQLiteOpenHelper + * @return NoteServerSyncHelper + */ + public static synchronized NoteServerSyncHelper getInstance(NoteSQLiteOpenHelper dbHelper) { + if(instance==null) { + instance = new NoteServerSyncHelper(dbHelper); + } + return instance; + } + + private NoteSQLiteOpenHelper dbHelper; + private Context appContext; + + // Track network connection changes using a BroadcastReceiver + private boolean networkConnected = false; + private BroadcastReceiver networkReceiver = new BroadcastReceiver() { @Override - public void onClick(View v) { - Activity parent = (Activity) db.getContext(); - Intent intent = new Intent(parent, SettingsActivity.class); - parent.startActivity(intent); + public void onReceive(Context context, Intent intent) { + ConnectivityManager connMgr = (ConnectivityManager)appContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeInfo = connMgr.getActiveNetworkInfo(); + if(activeInfo != null && activeInfo.isConnected()) { + Log.d(NoteServerSyncHelper.class.getSimpleName(), "Network connection established."); + networkConnected = true; + scheduleSync(false); + } else { + networkConnected = false; + Log.d(NoteServerSyncHelper.class.getSimpleName(), "No network connection."); + } } }; - private int operationsCount = 0; - private int operationsFinished = 0; + + // current state of the synchronization + private boolean syncActive = false; + private boolean syncScheduled = false; + + private Handler handler = null; private List callbacks = new ArrayList<>(); - public NoteServerSyncHelper(NoteSQLiteOpenHelper db) { - this.db = db; + private NoteServerSyncHelper(NoteSQLiteOpenHelper db) { + this.dbHelper = db; + this.appContext = db.getContext().getApplicationContext(); + handler = new Handler() { @Override public void handleMessage(Message msg) { @@ -59,15 +100,14 @@ public void handleMessage(Message msg) { callbacks.clear(); } }; - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(db.getContext().getApplicationContext()); - String url = preferences.getString(SettingsActivity.SETTINGS_URL, - SettingsActivity.DEFAULT_SETTINGS); - String username = preferences.getString(SettingsActivity.SETTINGS_USERNAME, - SettingsActivity.DEFAULT_SETTINGS); - String password = preferences.getString(SettingsActivity.SETTINGS_PASSWORD, - SettingsActivity.DEFAULT_SETTINGS); - client = new NotesClient(url, username, password); + // Registers BroadcastReceiver to track network connection changes. + appContext.registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + @Override + protected void finalize() throws Throwable { + appContext.unregisterReceiver(networkReceiver); + super.finalize(); } /** @@ -78,146 +118,143 @@ public void handleMessage(Message msg) { * * @param callback Implementation of ICallback, contains one method that shall be executed. */ + @Deprecated public void addCallback(ICallback callback) { callbacks.add(callback); } - /** - * Synchronizes Edited, New and Deleted Notes. After all changed content has been sent to the - * server, it downloads all changes that happened on the server. - */ - public void synchronize() { - uploadEditedNotes(); - uploadNewNotes(); - uploadDeletedNotes(); - downloadNotes(); - } /** - * Helper method to check if all synchronize operations are done yet. + * Schedules a synchronization and start it directly, if the network is connected and no + * synchronization is currently running. + * @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server. */ - private void asyncTaskFinished() { - operationsFinished++; - if (operationsFinished == operationsCount) { - handler.obtainMessage(1).sendToTarget(); + public void scheduleSync(boolean onlyLocalChanges) { + Log.d(getClass().getSimpleName(), "Sync requested ("+(onlyLocalChanges?"onlyLocalChanges":"full")+"; "+(networkConnected?"network connected":"network NOT connected")+", "+(syncActive?"sync active":"sync NOT active")+") ..."); + if(networkConnected && (!syncActive || onlyLocalChanges)) { + Log.d(getClass().getSimpleName(), "... starting now"); + new SyncTask(onlyLocalChanges).execute(); + } else if(!onlyLocalChanges) { + Log.d(getClass().getSimpleName(), "... scheduled"); + syncScheduled = true; + } else { + Log.d(getClass().getSimpleName(), "... do nothing"); } } - public void uploadEditedNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_EDITED); - for (Note note : notes) { - UploadEditedNotesTask editedNotesTask = new UploadEditedNotesTask(); - editedNotesTask.execute(note); - } - } - - public void uploadNewNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_CREATED); - for (Note note : notes) { - UploadNewNoteTask newNotesTask = new UploadNewNoteTask(); - newNotesTask.execute(note); - } - } + private class SyncTask extends AsyncTask { + private final boolean onlyLocalChanges; + private NotesClient client; - public void uploadDeletedNotes() { - List notes = db.getNotesByStatus(DBStatus.LOCAL_DELETED); - for (Note note : notes) { - UploadDeletedNoteTask deletedNotesTask = new UploadDeletedNoteTask(); - deletedNotesTask.execute(note); + public SyncTask(boolean onlyLocalChanges) { + this.onlyLocalChanges = onlyLocalChanges; } - } - public void downloadNotes() { - DownloadNotesTask downloadNotesTask = new DownloadNotesTask(); - downloadNotesTask.execute(); - } - - private class UploadNewNoteTask extends AsyncTask { @Override - protected Object[] doInBackground(Object... params) { - operationsCount++; - Note oldNote = (Note) params[0]; - try { - Note note = client.createNote(oldNote.getContent()); - return new Object[]{note, oldNote.getId()}; - } catch (IOException | JSONException e) { - e.printStackTrace(); + protected void onPreExecute() { + super.onPreExecute(); + if(!onlyLocalChanges && syncScheduled) { + syncScheduled = false; } - return null; + syncActive = true; } @Override - protected void onPostExecute(Object[] params) { - if (params != null) { - Long id = (Long) params[1]; - if (id != null) { - db.deleteNote(((Long) params[1])); - } - db.addNote((Note) params[0]); - } - asyncTaskFinished(); - } - } - - private class UploadEditedNotesTask extends AsyncTask { - private String noteContent; - - @Override - protected Note doInBackground(Object... params) { - operationsCount++; - Note oldNote = (Note) params[0]; - noteContent = oldNote.getContent(); - try { - return client.editNote(oldNote.getId(), noteContent); - } catch (IOException | JSONException e) { - e.printStackTrace(); - } + protected Void doInBackground(Void... voids) { + client = createNotesClient(); // recreate NoteClients on every sync in case the connection settings was changed + Log.d(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); + dbHelper.debugPrintFullDB(); + pushLocalChanges(); + pullRemoteChanges(); + dbHelper.debugPrintFullDB(); + Log.d(getClass().getSimpleName(), "SYNCHRONIZATION FINISHED"); return null; } - @Override - protected void onPostExecute(Note note) { - if (note == null) { - // Note has been deleted on server -> recreate - db.addNoteAndSync(noteContent); - } else { - db.updateNote(note); + /** + * Push local changes: for each locally created/edited/deleted Note, use NotesClient in order to push the changed to the server. + */ + private void pushLocalChanges() { + Log.d(getClass().getSimpleName(), "pushLocalChanges()"); + List notes = dbHelper.getLocalModifiedNotes(); + for (DBNote note : notes) { + Log.d(getClass().getSimpleName(), " Process Local Note: "+note); + try { + OwnCloudNote remoteNote=null; + switch(note.getStatus()) { + case LOCAL_EDITED: + Log.d(getClass().getSimpleName(), " ...create/edit"); + // if note is not new, try to edit it. + if (note.getRemoteId()>0) { + Log.d(getClass().getSimpleName(), " ...try to edit"); + remoteNote = client.editNote(note.getRemoteId(), note.getContent()); + } + // However, the note may be deleted on the server meanwhile; or was never synchronized -> (re)create + if (remoteNote == null) { + Log.d(getClass().getSimpleName(), " ...Note does not exist on server -> (re)create"); + remoteNote = client.createNote(note.getContent()); + dbHelper.updateNote(note.getId(), remoteNote, note); + } else { + dbHelper.updateNote(note.getId(), remoteNote, note); + } + break; + case LOCAL_DELETED: + if(note.getRemoteId()>0) { + Log.d(getClass().getSimpleName(), " ...delete (from server and local)"); + try { + client.deleteNote(note.getRemoteId()); + } catch (FileNotFoundException e) { + Log.d(getClass().getSimpleName(), " ...Note does not exist on server (anymore?) -> delete locally"); + } + } else { + Log.d(getClass().getSimpleName(), " ...delete (only local, since it was not synchronized)"); + } + dbHelper.deleteNote(note.getId(), DBStatus.LOCAL_DELETED); + break; + default: + throw new IllegalStateException("Unknown State of Note: "+note); + } + } catch (IOException | JSONException e) { + // FIXME Fehlerbehandlung sichtbar machen + e.printStackTrace(); + } } - asyncTaskFinished(); } - } - - private class UploadDeletedNoteTask extends AsyncTask { - Long id = null; - @Override - protected Void doInBackground(Object... params) { - operationsCount++; + /** + * Pull remote Changes: update or create each remote note (if local pendant has no changes) and remove remotely deleted notes. + */ + private void pullRemoteChanges() { + Log.d(getClass().getSimpleName(), "pullRemoteChanges()"); + LoginStatus status = null; try { - id = ((Note) params[0]).getId(); - client.deleteNote(id); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - db.deleteNote(id); - asyncTaskFinished(); - } - } - - private class DownloadNotesTask extends AsyncTask> { - private LoginStatus status = null; - - @Override - protected List doInBackground(Object... params) { - operationsCount++; - List notes = new ArrayList<>(); - try { - notes = client.getNotes(); + List localNotes = dbHelper.getNotes(); + Map localIDmap = new HashMap<>(); + for (DBNote note : localNotes) { + localIDmap.put(note.getRemoteId(), note.getId()); + } + List remoteNotes = client.getNotes(); + Set remoteIDs = new HashSet<>(); + // pull remote changes: update or create each remote note + for (OwnCloudNote remoteNote : remoteNotes) { + Log.d(getClass().getSimpleName(), " Process Remote Note: "+remoteNote); + remoteIDs.add(remoteNote.getRemoteId()); + if(localIDmap.containsKey(remoteNote.getRemoteId())) { + Log.d(getClass().getSimpleName(), " ... found -> Update"); + dbHelper.updateNote(localIDmap.get(remoteNote.getRemoteId()), remoteNote, null); + } else { + Log.d(getClass().getSimpleName(), " ... create"); + dbHelper.addNote(remoteNote); + } + } + Log.d(getClass().getSimpleName(), " Remove remotely deleted Notes (only those without local changes)"); + // remove remotely deleted notes (only those without local changes) + for (DBNote note : localNotes) { + if(note.getStatus()==DBStatus.VOID && !remoteIDs.contains(note.getRemoteId())) { + Log.d(getClass().getSimpleName(), " ... remove "+note); + dbHelper.deleteNote(note.getId(), DBStatus.VOID); + } + } status = LoginStatus.OK; } catch (IOException e) { e.printStackTrace(); @@ -225,22 +262,36 @@ protected List doInBackground(Object... params) { } catch (JSONException e) { status = LoginStatus.JSON_FAILED; } - return notes; + if (status!=LoginStatus.OK) { + Toast.makeText(appContext, appContext.getString(R.string.error_sync, appContext.getString(status.str)), Toast.LENGTH_LONG).show(); + } } @Override - protected void onPostExecute(List result) { - // Clear Database only if there was no Server Error - if (status==LoginStatus.OK) { - db.clearDatabase(); + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + syncActive = false; + if(syncScheduled) { + scheduleSync(false); } else { - Context c = db.getContext(); - Toast.makeText(c, c.getString(R.string.error_sync, c.getString(status.str)), Toast.LENGTH_LONG).show(); + asyncTaskFinished(); } - for (Note note : result) { - db.addNote(note); - } - asyncTaskFinished(); } } + + private NotesClient createNotesClient() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(appContext.getApplicationContext()); + String url = preferences.getString(SettingsActivity.SETTINGS_URL, SettingsActivity.DEFAULT_SETTINGS); + String username = preferences.getString(SettingsActivity.SETTINGS_USERNAME, SettingsActivity.DEFAULT_SETTINGS); + String password = preferences.getString(SettingsActivity.SETTINGS_PASSWORD, SettingsActivity.DEFAULT_SETTINGS); + return new NotesClient(url, username, password); + } + + /** + * Helper method to check if all synchronize operations are done yet. + */ + @Deprecated + private void asyncTaskFinished() { + handler.obtainMessage(1).sendToTarget(); + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java index ac6d459ca..5a2db0d47 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java @@ -18,7 +18,7 @@ import java.util.GregorianCalendar; import java.util.List; -import it.niedermann.owncloud.notes.model.Note; +import it.niedermann.owncloud.notes.model.OwnCloudNote; public class NotesClient { @@ -41,9 +41,9 @@ public NotesClient(String url, String username, String password) { this.password = password; } - public List getNotes() throws JSONException, + public List getNotes() throws JSONException, IOException { - List notesList = new ArrayList<>(); + List notesList = new ArrayList<>(); JSONArray notes = new JSONArray(requestServer("notes", METHOD_GET, null)); long noteId = 0; String noteTitle = ""; @@ -68,7 +68,7 @@ public List getNotes() throws JSONException, .setTimeInMillis(currentItem.getLong(key_modified) * 1000); } notesList - .add(new Note(noteId, noteModified, noteTitle, noteContent)); + .add(new OwnCloudNote(noteId, noteModified, noteTitle, noteContent)); } return notesList; } @@ -83,7 +83,7 @@ public List getNotes() throws JSONException, * @throws IOException */ @SuppressWarnings("unused") - public Note getNoteById(long id) throws + public OwnCloudNote getNoteById(long id) throws JSONException, IOException { long noteId = 0; String noteTitle = ""; @@ -106,7 +106,7 @@ public Note getNoteById(long id) throws noteModified .setTimeInMillis(currentItem.getLong(key_modified) * 1000); } - return new Note(noteId, noteModified, noteTitle, noteContent); + return new OwnCloudNote(noteId, noteModified, noteTitle, noteContent); } /** @@ -117,7 +117,7 @@ public Note getNoteById(long id) throws * @throws JSONException * @throws IOException */ - public Note createNote(String content) throws + public OwnCloudNote createNote(String content) throws JSONException, IOException { long noteId = 0; String noteTitle = ""; @@ -143,10 +143,10 @@ public Note createNote(String content) throws noteModified .setTimeInMillis(currentItem.getLong(key_modified) * 1000); } - return new Note(noteId, noteModified, noteTitle, noteContent); + return new OwnCloudNote(noteId, noteModified, noteTitle, noteContent); } - public Note editNote(long noteId, String content) + public OwnCloudNote editNote(long noteId, String content) throws JSONException, IOException { String noteTitle = ""; Calendar noteModified = null; @@ -164,7 +164,7 @@ public Note editNote(long noteId, String content) noteModified .setTimeInMillis(currentItem.getLong(key_modified) * 1000); } - return new Note(noteId, noteModified, noteTitle, content); + return new OwnCloudNote(noteId, noteModified, noteTitle, content); } public void deleteNote(long noteId) throws From 93eb7e9e7682f75cc8aa8422a10a6e2d47920991 Mon Sep 17 00:00:00 2001 From: korelstar Date: Sun, 10 Jul 2016 21:02:04 +0200 Subject: [PATCH 12/25] only pull remote changes if this was demanded by the caller --- .../owncloud/notes/persistence/NoteServerSyncHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index ff54cbe48..342fdf350 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -165,7 +165,9 @@ protected Void doInBackground(Void... voids) { Log.d(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); dbHelper.debugPrintFullDB(); pushLocalChanges(); - pullRemoteChanges(); + if(!onlyLocalChanges) { + pullRemoteChanges(); + } dbHelper.debugPrintFullDB(); Log.d(getClass().getSimpleName(), "SYNCHRONIZATION FINISHED"); return null; From 7fe474d80d045f043d819bf8f007106f945aab2c Mon Sep 17 00:00:00 2001 From: korelstar Date: Mon, 11 Jul 2016 22:25:22 +0200 Subject: [PATCH 13/25] restructure the communication between synchronization task and user interface using callbacks, only try to sync if not offline, otherwise show an error message. --- .../android/activity/EditNoteActivity.java | 36 +++++---- .../activity/NotesListViewActivity.java | 73 ++++++++++------- .../persistence/NoteSQLiteOpenHelper.java | 4 +- .../persistence/NoteServerSyncHelper.java | 81 +++++++++++-------- .../owncloud/notes/util/NotesClientUtil.java | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 114 insertions(+), 83 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index edfc27b09..9be0a5cd4 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -27,6 +27,7 @@ public class EditNoteActivity extends AppCompatActivity { private DBNote note = null; private Timer timer = new Timer(); private ActionBar actionBar; + private NoteSQLiteOpenHelper db; @Override protected void onCreate(final Bundle savedInstanceState) { @@ -38,6 +39,7 @@ protected void onCreate(final Bundle savedInstanceState) { content.setEnabled(false); content.setText(note.getContent()); content.setEnabled(true); + db = new NoteSQLiteOpenHelper(this); actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setTitle(note.getTitle()); @@ -59,18 +61,20 @@ public void onTextChanged(final CharSequence s, int start, int before, @Override public void afterTextChanged(final Editable s) { - timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - saveData(); - } - }); - } - }, DELAY); + if(db.getNoteServerSyncHelper().isSyncPossible()) { + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { + saveDataWithUI(); + } + }); + } + }, DELAY); + } } }); } @@ -102,7 +106,7 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } - private void saveData() { + private void saveDataWithUI() { ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setSubtitle(getString(R.string.action_edit_saving)); @@ -112,8 +116,7 @@ private void saveData() { note.setContent(((EditText) findViewById(R.id.editContent)).getText().toString()); // #80 note.setTitle(NoteUtil.generateNoteTitle(note.getContent())); - NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(this); - db.getNoteServerSyncHelper().addCallback(new ICallback() { + db.getNoteServerSyncHelper().addCallbackPush(new ICallback() { @Override public void onFinish() { runOnUiThread(new Runnable() { @@ -143,6 +146,9 @@ public void run() { }*/ } }); + saveData(); + } + private void saveData() { db.updateNoteAndSync(note); } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index a708446ac..949eb9df8 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -16,6 +16,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.Toast; import java.util.ArrayList; import java.util.Calendar; @@ -28,6 +29,7 @@ import it.niedermann.owncloud.notes.model.SectionItem; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.ICallback; +import it.niedermann.owncloud.notes.util.NotesClientUtil; public class NotesListViewActivity extends AppCompatActivity implements ItemAdapter.NoteClickListener, View.OnClickListener { @@ -71,17 +73,22 @@ protected void onCreate(Bundle savedInstanceState) { swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { - db.getNoteServerSyncHelper().addCallback(new ICallback() { - @Override - public void onFinish() { - swipeRefreshLayout.setRefreshing(false); - adapter.clearSelection(); - if (mActionMode != null) { - mActionMode.finish(); + if(db.getNoteServerSyncHelper().isSyncPossible()) { + db.getNoteServerSyncHelper().addCallbackPull(new ICallback() { + @Override + public void onFinish() { + swipeRefreshLayout.setRefreshing(false); + adapter.clearSelection(); + if (mActionMode != null) { + mActionMode.finish(); + } + setListView(db.getNotes()); } - setListView(db.getNotes()); - } - }); + }); + } else { + swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getApplicationContext(), getString(R.string.error_sync, getString(NotesClientUtil.LoginStatus.NO_NETWORK.str)), Toast.LENGTH_LONG).show(); + } db.getNoteServerSyncHelper().scheduleSync(false); } }); @@ -95,19 +102,19 @@ public void onFinish() { */ @Override protected void onResume() { - db.getNoteServerSyncHelper().addCallback(new ICallback() { - @Override - public void onFinish() { - swipeRefreshLayout.setRefreshing(false); - adapter.clearSelection(); - if (mActionMode != null) { - mActionMode.finish(); + if (db.getNoteServerSyncHelper().isSyncPossible() && !PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean(SettingsActivity.SETTINGS_FIRST_RUN, true)) { + db.getNoteServerSyncHelper().addCallbackPull(new ICallback() { + @Override + public void onFinish() { + swipeRefreshLayout.setRefreshing(false); + adapter.clearSelection(); + if (mActionMode != null) { + mActionMode.finish(); + } + // adapter.checkForUpdates(db.getNotes()); // FIXME deactivated, since it doesn't remove remotely deleted notes + setListView(db.getNotes()); } - // adapter.checkForUpdates(db.getNotes()); // FIXME deactivated, since it doesn't remove remotely deleted notes - setListView(db.getNotes()); - } - }); - if (!PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean(SettingsActivity.SETTINGS_FIRST_RUN, true)) { + }); db.getNoteServerSyncHelper().scheduleSync(false); } super.onResume(); @@ -314,15 +321,19 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } else if (requestCode == server_settings) { // Create new Instance with new URL and credentials db = new NoteSQLiteOpenHelper(this); - adapter.removeAll(); - swipeRefreshLayout.setRefreshing(true); - db.getNoteServerSyncHelper().addCallback(new ICallback() { - @Override - public void onFinish() { - setListView(db.getNotes()); - swipeRefreshLayout.setRefreshing(false); - } - }); + if(db.getNoteServerSyncHelper().isSyncPossible()) { + adapter.removeAll(); + swipeRefreshLayout.setRefreshing(true); + db.getNoteServerSyncHelper().addCallbackPull(new ICallback() { + @Override + public void onFinish() { + setListView(db.getNotes()); + swipeRefreshLayout.setRefreshing(false); + } + }); + } else { + Toast.makeText(getApplicationContext(), getString(R.string.error_sync, getString(NotesClientUtil.LoginStatus.NO_NETWORK.str)), Toast.LENGTH_LONG).show(); + } db.getNoteServerSyncHelper().scheduleSync(false); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 6a7ae6e7b..2c68ea9f6 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -38,7 +38,6 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { private static final String key_content = "CONTENT"; private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_content}; - @Deprecated private NoteServerSyncHelper serverSyncHelper = null; private Context context = null; @@ -48,7 +47,6 @@ public NoteSQLiteOpenHelper(Context context) { serverSyncHelper = NoteServerSyncHelper.getInstance(this); } - @Deprecated public NoteServerSyncHelper getNoteServerSyncHelper() { return serverSyncHelper; } @@ -243,9 +241,9 @@ public List getLocalModifiedNotes() { * Updates a single Note and sets a synchronization Flag. * * @param note Note - Note with the updated Information - * @return The number of the Rows affected. */ public void updateNoteAndSync(DBNote note) { + // TODO check if the content has really changed note.setStatus(DBStatus.LOCAL_EDITED); SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 342fdf350..b4895c4d9 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -8,11 +8,8 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; -import android.os.Handler; -import android.os.Message; import android.preference.PreferenceManager; import android.util.Log; -import android.util.LongSparseArray; import android.widget.Toast; import org.json.JSONException; @@ -58,12 +55,12 @@ public static synchronized NoteServerSyncHelper getInstance(NoteSQLiteOpenHelper return instance; } - private NoteSQLiteOpenHelper dbHelper; - private Context appContext; + private final NoteSQLiteOpenHelper dbHelper; + private final Context appContext; // Track network connection changes using a BroadcastReceiver private boolean networkConnected = false; - private BroadcastReceiver networkReceiver = new BroadcastReceiver() { + private final BroadcastReceiver networkReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { ConnectivityManager connMgr = (ConnectivityManager)appContext.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -83,23 +80,16 @@ public void onReceive(Context context, Intent intent) { private boolean syncActive = false; private boolean syncScheduled = false; + // list of callbacks for both parts of synchronziation + private List callbacksPush = new ArrayList<>(); + private List callbacksPull = new ArrayList<>(); + - private Handler handler = null; - private List callbacks = new ArrayList<>(); private NoteServerSyncHelper(NoteSQLiteOpenHelper db) { this.dbHelper = db; this.appContext = db.getContext().getApplicationContext(); - handler = new Handler() { - @Override - public void handleMessage(Message msg) { - for (ICallback callback : callbacks) { - callback.onFinish(); - } - callbacks.clear(); - } - }; // Registers BroadcastReceiver to track network connection changes. appContext.registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); } @@ -110,17 +100,33 @@ protected void finalize() throws Throwable { super.finalize(); } + + public boolean isSyncPossible() { + return networkConnected; + } + + + /** + * Adds a callback method to the NoteServerSyncHelper for the synchronization part push local changes to the server. + * All callbacks will be executed once the synchronization operations are done. + * After execution the callback will be deleted, so it has to be added again if it shall be + * executed the next time all synchronize operations are finished. + * + * @param callback Implementation of ICallback, contains one method that shall be executed. + */ + public void addCallbackPush(ICallback callback) { + callbacksPush.add(callback); + } /** - * Adds a callback method to the NoteServerSyncHelper. - * All callbacks will be executed once all synchronize operations are done. + * Adds a callback method to the NoteServerSyncHelper for the synchronization part pull remote changes from the server. + * All callbacks will be executed once the synchronization operations are done. * After execution the callback will be deleted, so it has to be added again if it shall be * executed the next time all synchronize operations are finished. * * @param callback Implementation of ICallback, contains one method that shall be executed. */ - @Deprecated - public void addCallback(ICallback callback) { - callbacks.add(callback); + public void addCallbackPull(ICallback callback) { + callbacksPull.add(callback); } @@ -131,9 +137,16 @@ public void addCallback(ICallback callback) { */ public void scheduleSync(boolean onlyLocalChanges) { Log.d(getClass().getSimpleName(), "Sync requested ("+(onlyLocalChanges?"onlyLocalChanges":"full")+"; "+(networkConnected?"network connected":"network NOT connected")+", "+(syncActive?"sync active":"sync NOT active")+") ..."); - if(networkConnected && (!syncActive || onlyLocalChanges)) { + if(isSyncPossible() && (!syncActive || onlyLocalChanges)) { Log.d(getClass().getSimpleName(), "... starting now"); - new SyncTask(onlyLocalChanges).execute(); + SyncTask syncTask = new SyncTask(onlyLocalChanges); + syncTask.addCallbacks(callbacksPush); + callbacksPush = new ArrayList<>(); + if(!onlyLocalChanges) { + syncTask.addCallbacks(callbacksPull); + callbacksPull = new ArrayList<>(); + } + syncTask.execute(); } else if(!onlyLocalChanges) { Log.d(getClass().getSimpleName(), "... scheduled"); syncScheduled = true; @@ -144,12 +157,17 @@ public void scheduleSync(boolean onlyLocalChanges) { private class SyncTask extends AsyncTask { private final boolean onlyLocalChanges; + private final List callbacks = new ArrayList<>(); private NotesClient client; public SyncTask(boolean onlyLocalChanges) { this.onlyLocalChanges = onlyLocalChanges; } + public void addCallbacks(List callbacks) { + this.callbacks.addAll(callbacks); + } + @Override protected void onPreExecute() { super.onPreExecute(); @@ -273,10 +291,13 @@ private void pullRemoteChanges() { protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); syncActive = false; + // notify callbacks + for (ICallback callback : callbacks) { + callback.onFinish(); + } + // start next sync if scheduled meanwhile if(syncScheduled) { scheduleSync(false); - } else { - asyncTaskFinished(); } } } @@ -288,12 +309,4 @@ private NotesClient createNotesClient() { String password = preferences.getString(SettingsActivity.SETTINGS_PASSWORD, SettingsActivity.DEFAULT_SETTINGS); return new NotesClient(url, username, password); } - - /** - * Helper method to check if all synchronize operations are done yet. - */ - @Deprecated - private void asyncTaskFinished() { - handler.obtainMessage(1).sendToTarget(); - } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java index 16801f7c2..3bf521713 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java @@ -25,6 +25,7 @@ public enum LoginStatus { OK(0), AUTH_FAILED(R.string.error_username_password_invalid), CONNECTION_FAILED(R.string.error_io), + NO_NETWORK(R.string.error_no_network), JSON_FAILED(R.string.error_json), SERVER_FAILED(R.string.error_server); diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6bc4b8815..d4ebc7f44 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -49,6 +49,7 @@ Login fehlgeschlagen: %1$s ist die Server-App ownCloud Notes aktiviert? Server-Verbindung nicht möglich + keine Netzwerk-Verbindung URL/Server fehlerhaft URL nicht korrekt Benutzername / Passwort nicht korrekt diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 156d58905..59323823f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,6 +49,7 @@ Invalid login: %1$s is the owncloud Notes app activated on the server? server connection is broken + no network connection URL/Server has errors Wrong server address Wrong username or password From 6b22030a5cfaebaf782f5222677add396068a68e Mon Sep 17 00:00:00 2001 From: korelstar Date: Tue, 12 Jul 2016 21:00:26 +0200 Subject: [PATCH 14/25] updateNoteAndSync(): only make database changes, if the content really changed (see #104) --- .../owncloud/notes/persistence/NoteSQLiteOpenHelper.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 2c68ea9f6..6f8d6afe5 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -243,7 +243,6 @@ public List getLocalModifiedNotes() { * @param note Note - Note with the updated Information */ public void updateNoteAndSync(DBNote note) { - // TODO check if the content has really changed note.setStatus(DBStatus.LOCAL_EDITED); SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -251,9 +250,11 @@ public void updateNoteAndSync(DBNote note) { values.put(key_title, note.getTitle()); values.put(key_modified, note.getModified(DATE_FORMAT)); values.put(key_content, note.getContent()); - db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(note.getId())}); + int rows = db.update(table_notes, values, key_id + " = ? AND " + key_content + " != ?", new String[]{String.valueOf(note.getId()), note.getContent()}); db.close(); - getNoteServerSyncHelper().scheduleSync(true); + if(rows > 0) { + getNoteServerSyncHelper().scheduleSync(true); + } } /** From 5d4384bb3a926f81dbe5c3f944789094c17d359b Mon Sep 17 00:00:00 2001 From: korelstar Date: Tue, 12 Jul 2016 21:30:28 +0200 Subject: [PATCH 15/25] javadoc and cleanup --- .../android/activity/NotesListViewActivity.java | 3 --- .../it/niedermann/owncloud/notes/model/DBNote.java | 4 ++++ .../niedermann/owncloud/notes/model/DBStatus.java | 5 +++++ .../owncloud/notes/model/OwnCloudNote.java | 4 ++++ .../notes/persistence/NoteSQLiteOpenHelper.java | 5 ++++- .../notes/persistence/NoteServerSyncHelper.java | 14 +++++++++++++- 6 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 949eb9df8..7b1de93e3 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -140,9 +140,6 @@ public void onClick(View v) { */ @SuppressWarnings("WeakerAccess") public void setListView(List noteList) { - Log.d(getClass().getSimpleName(), "setListView("+noteList.size()+" notes)"); - //db.debugPrintFullDB(); // FIXME remove - List itemList = new ArrayList<>(); // #12 Create Sections depending on Time // TODO Move to ItemAdapter? diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java index 78fddc134..f25105b86 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java @@ -6,6 +6,10 @@ import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.NoteUtil; +/** + * DBNote represents a single note from the local SQLite database with all attributes. + * It extends OwnCloudNote with attributes required for local data management. + */ public class DBNote extends OwnCloudNote implements Item, Serializable { private long id; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java index 46e1b195d..6ca063188 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java @@ -40,6 +40,11 @@ public String getTitle() { this.title = title; } + /** + * Parse a String an get the appropriate DBStatus enum element. + * @param str The String containing the DBStatus identifier. Must not null. + * @return The DBStatus fitting to the String. + */ public static DBStatus parse(String str) { if(str.isEmpty()) { return DBStatus.VOID; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java index 3021fd52b..b9a31865c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/OwnCloudNote.java @@ -8,6 +8,10 @@ import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.NoteUtil; +/** + * OwnCloudNote represents a remote note from an OwnCloud server. + * It can be directly generated from the JSON answer from the server. + */ public class OwnCloudNote implements Serializable { private long remoteId = 0; private String title = ""; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 6f8d6afe5..64038f596 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -258,7 +258,9 @@ public void updateNoteAndSync(DBNote note) { } /** - * Updates a single Note with data from the server, (if it was not modified locally). This is used by the synchronization task, hence no Synchronization will be triggered. Use updateNoteAndSync() instead! + * Updates a single Note with data from the server, (if it was not modified locally). + * Thereby, an optimistic concurrency control is realized in order to prevent conflicts arising due to parallel changes from the UI and synchronization. + * This is used by the synchronization task, hence no Synchronization will be triggered. Use updateNoteAndSync() instead! * * @param id local ID of Note * @param remoteNote Note from the server. @@ -324,6 +326,7 @@ public int deleteNoteAndSync(long id) { /** * Delete a single Note from the Database, if it has a specific DBStatus. + * Thereby, an optimistic concurrency control is realized in order to prevent conflicts arising due to parallel changes from the UI and synchronization. * * @param id long - ID of the Note that should be deleted. * @param forceDBStatus DBStatus, e.g., if Note was marked as LOCAL_DELETED (for NoteSQLiteOpenHelper.SyncTask.pushLocalChanges()) or is unchanged VOID (for NoteSQLiteOpenHelper.SyncTask.pullRemoteChanges()) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index b4895c4d9..488c34a05 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -101,6 +101,12 @@ protected void finalize() throws Throwable { } + /** + * Synchronization is only possible, if there is an active network connection. + * NoteServerSyncHelper observes changes in the network connection. + * The current state can be retrieved with this method. + * @return true if sync is possible, otherwise false. + */ public boolean isSyncPossible() { return networkConnected; } @@ -155,6 +161,10 @@ public void scheduleSync(boolean onlyLocalChanges) { } } + /** + * SyncTask is an AsyncTask which performs the synchronization in a background thread. + * Synchronization consists of two parts: pushLocalChanges and pullRemoteChanges. + */ private class SyncTask extends AsyncTask { private final boolean onlyLocalChanges; private final List callbacks = new ArrayList<>(); @@ -210,6 +220,7 @@ private void pushLocalChanges() { remoteNote = client.editNote(note.getRemoteId(), note.getContent()); } // However, the note may be deleted on the server meanwhile; or was never synchronized -> (re)create + // Please note, thas dbHelper.updateNote() realizes an optimistic conflict resolution, which is required for parallel changes of this Note from the UI. if (remoteNote == null) { Log.d(getClass().getSimpleName(), " ...Note does not exist on server -> (re)create"); remoteNote = client.createNote(note.getContent()); @@ -229,13 +240,14 @@ private void pushLocalChanges() { } else { Log.d(getClass().getSimpleName(), " ...delete (only local, since it was not synchronized)"); } + // Please note, thas dbHelper.deleteNote() realizes an optimistic conflict resolution, which is required for parallel changes of this Note from the UI. dbHelper.deleteNote(note.getId(), DBStatus.LOCAL_DELETED); break; default: throw new IllegalStateException("Unknown State of Note: "+note); } } catch (IOException | JSONException e) { - // FIXME Fehlerbehandlung sichtbar machen + // FIXME make some errors visible in the UI e.printStackTrace(); } } From defbd220325325204c2f90b99e8ff3c4f9e22d8b Mon Sep 17 00:00:00 2001 From: korelstar Date: Wed, 13 Jul 2016 20:07:55 +0200 Subject: [PATCH 16/25] Bugfix for EditNoteActivity: invoke callbacks directly if note wasn't changed in edit (before, callback was never invoked in this case) --- .../android/activity/EditNoteActivity.java | 11 +++++------ .../persistence/NoteSQLiteOpenHelper.java | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 9be0a5cd4..83cec2602 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -82,7 +82,7 @@ public void run() { @Override public void onBackPressed() { content.setEnabled(false); - saveData(); + saveData(null); Intent data = new Intent(); data.setAction(Intent.ACTION_VIEW); data.putExtra(NoteActivity.EDIT_NOTE, note); @@ -95,7 +95,7 @@ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: content.setEnabled(false); - saveData(); + saveData(null); Intent data = new Intent(); data.setAction(Intent.ACTION_VIEW); data.putExtra(NoteActivity.EDIT_NOTE, note); @@ -116,7 +116,7 @@ private void saveDataWithUI() { note.setContent(((EditText) findViewById(R.id.editContent)).getText().toString()); // #80 note.setTitle(NoteUtil.generateNoteTitle(note.getContent())); - db.getNoteServerSyncHelper().addCallbackPush(new ICallback() { + saveData(new ICallback() { @Override public void onFinish() { runOnUiThread(new Runnable() { @@ -146,9 +146,8 @@ public void run() { }*/ } }); - saveData(); } - private void saveData() { - db.updateNoteAndSync(note); + private void saveData(ICallback callback) { + db.updateNoteAndSync(note, callback); } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 64038f596..9a23a480e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -17,6 +17,7 @@ import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.OwnCloudNote; +import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; /** @@ -241,19 +242,28 @@ public List getLocalModifiedNotes() { * Updates a single Note and sets a synchronization Flag. * * @param note Note - Note with the updated Information + * @param callback When the synchronization is finished, this callback will be invoked (optional). */ - public void updateNoteAndSync(DBNote note) { - note.setStatus(DBStatus.LOCAL_EDITED); + public void updateNoteAndSync(DBNote note, ICallback callback) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(key_status, note.getStatus().getTitle()); + values.put(key_status, DBStatus.LOCAL_EDITED.getTitle()); values.put(key_title, note.getTitle()); values.put(key_modified, note.getModified(DATE_FORMAT)); values.put(key_content, note.getContent()); int rows = db.update(table_notes, values, key_id + " = ? AND " + key_content + " != ?", new String[]{String.valueOf(note.getId()), note.getContent()}); db.close(); + // if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly. if(rows > 0) { - getNoteServerSyncHelper().scheduleSync(true); + note.setStatus(DBStatus.LOCAL_EDITED); + if(callback!=null) { + serverSyncHelper.addCallbackPush(callback); + } + serverSyncHelper.scheduleSync(true); + } else { + if(callback!=null) { + callback.onFinish(); + } } } From dd6d6ebb1b24e829f8bde783acda113738e20725 Mon Sep 17 00:00:00 2001 From: korelstar Date: Wed, 13 Jul 2016 21:50:10 +0200 Subject: [PATCH 17/25] Make sure, that saveDataWithUI is not called, when the previous saveAndSync is still running. In addition, start next saveAndSync not before a small delay has passed. --- .../android/activity/EditNoteActivity.java | 137 +++++++++++++----- 1 file changed, 97 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 83cec2602..2a657d95c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.MenuItem; import android.widget.EditText; @@ -22,10 +23,13 @@ import it.niedermann.owncloud.notes.util.NoteUtil; public class EditNoteActivity extends AppCompatActivity { + private static final String LOG_TAG = "EditNote/SAVE"; private final long DELAY = 1000; // in ms + private final long DELAY_AFTER_SYNC = 4000; // in ms private EditText content = null; private DBNote note = null; - private Timer timer = new Timer(); + private Timer timer, timerNextSync; + private boolean saveActive = false; private ActionBar actionBar; private NoteSQLiteOpenHelper db; @@ -55,25 +59,32 @@ public void beforeTextChanged(CharSequence s, int start, int count, @Override public void onTextChanged(final CharSequence s, int start, int before, int count) { - if (timer != null) + if (timer != null) { timer.cancel(); + timer = null; + } } @Override public void afterTextChanged(final Editable s) { if(db.getNoteServerSyncHelper().isSyncPossible()) { - timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - saveDataWithUI(); - } - }); - } - }, DELAY); + if(timer != null) { + timer.cancel(); + } + if(!saveActive) { + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { + saveDataWithUI(); + } + }); + } + }, DELAY); + } } } }); @@ -81,61 +92,102 @@ public void run() { @Override public void onBackPressed() { - content.setEnabled(false); - saveData(null); - Intent data = new Intent(); - data.setAction(Intent.ACTION_VIEW); - data.putExtra(NoteActivity.EDIT_NOTE, note); - setResult(RESULT_OK, data); - finish(); + saveAndClose(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - content.setEnabled(false); - saveData(null); - Intent data = new Intent(); - data.setAction(Intent.ACTION_VIEW); - data.putExtra(NoteActivity.EDIT_NOTE, note); - setResult(RESULT_OK, data); - finish(); + saveAndClose(); return true; } return super.onOptionsItemSelected(item); } + /** + * Saves all changes and closes the Activity + */ + private void saveAndClose() { + content.setEnabled(false); + if(timer!=null) { + timer.cancel(); + timer = null; + } + if(timerNextSync!=null) { + timerNextSync.cancel(); + timerNextSync = null; + } + saveData(null); + Intent data = new Intent(); + data.setAction(Intent.ACTION_VIEW); + data.putExtra(NoteActivity.EDIT_NOTE, note); + setResult(RESULT_OK, data); + finish(); + } + + /** + * Gets the current content of the EditText field in the UI. + * @return String of the current content. + */ + private String getContent() { + return ((EditText) findViewById(R.id.editContent)).getText().toString(); + } + + /** + * Saves the current changes and show the status in the ActionBar + */ private void saveDataWithUI() { + Log.d(LOG_TAG, "START save+sync"); + saveActive = true; ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setSubtitle(getString(R.string.action_edit_saving)); } // #74 note.setModified(Calendar.getInstance()); - note.setContent(((EditText) findViewById(R.id.editContent)).getText().toString()); + note.setContent(getContent()); // #80 note.setTitle(NoteUtil.generateNoteTitle(note.getContent())); + final String content = note.getContent(); saveData(new ICallback() { @Override public void onFinish() { - runOnUiThread(new Runnable() { + // AFTER SYNCHRONIZATION + Log.d(LOG_TAG, "...sync finished"); + getSupportActionBar().setSubtitle(getResources().getString(R.string.action_edit_saved)); + Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() { @Override public void run() { - getSupportActionBar().setSubtitle(getResources().getString(R.string.action_edit_saved)); - Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() { + runOnUiThread(new Runnable() { @Override public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - getSupportActionBar().setSubtitle(getString(R.string.action_edit_editing)); - } - }); + // AFTER 1 SECOND: set ActionBar to default title + getSupportActionBar().setSubtitle(getString(R.string.action_edit_editing)); + } + }); + } + }, 1, TimeUnit.SECONDS); + + timerNextSync = new Timer(); + timerNextSync.schedule(new TimerTask() { + @Override + public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { + // AFTER "DELAY_AFTER_SYNC" SECONDS: allow next auto-save or start it directly + if(getContent().equals(content)) { + saveActive = false; + Log.d(LOG_TAG, "FINISH, no new changes"); + } else { + Log.d(LOG_TAG, "content has changed meanwhile -> restart save"); + saveDataWithUI(); + } } - }, 1, TimeUnit.SECONDS); + }); } - }); + }, DELAY_AFTER_SYNC); /* TODO Notify widgets @@ -147,6 +199,11 @@ public void run() { } }); } + + /** + * Save the current state in the database and schedule synchronization if needed. + * @param callback + */ private void saveData(ICallback callback) { db.updateNoteAndSync(note, callback); } From 3d5348aab1546ea79941f989e5106b6f0e5266ed Mon Sep 17 00:00:00 2001 From: korelstar Date: Thu, 14 Jul 2016 07:57:10 +0200 Subject: [PATCH 18/25] rename auto-sync method; adjust DELAYs --- .../notes/android/activity/EditNoteActivity.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 2a657d95c..9cddfa0f4 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -24,8 +24,8 @@ public class EditNoteActivity extends AppCompatActivity { private static final String LOG_TAG = "EditNote/SAVE"; - private final long DELAY = 1000; // in ms - private final long DELAY_AFTER_SYNC = 4000; // in ms + private final long DELAY = 2000; // in ms + private final long DELAY_AFTER_SYNC = 5000; // in ms private EditText content = null; private DBNote note = null; private Timer timer, timerNextSync; @@ -79,7 +79,7 @@ public void run() { runOnUiThread(new Runnable() { @Override public void run() { - saveDataWithUI(); + autoSave(); } }); } @@ -137,7 +137,7 @@ private String getContent() { /** * Saves the current changes and show the status in the ActionBar */ - private void saveDataWithUI() { + private void autoSave() { Log.d(LOG_TAG, "START save+sync"); saveActive = true; ActionBar ab = getSupportActionBar(); @@ -182,7 +182,7 @@ public void run() { Log.d(LOG_TAG, "FINISH, no new changes"); } else { Log.d(LOG_TAG, "content has changed meanwhile -> restart save"); - saveDataWithUI(); + autoSave(); } } }); From 6458f3042537b5ca91af32d58c5d628b1aeb0e1d Mon Sep 17 00:00:00 2001 From: korelstar Date: Mon, 18 Jul 2016 07:47:50 +0200 Subject: [PATCH 19/25] Quick Bugfix: Toast in AsyncTask have to be in onPostExecute --- .../owncloud/notes/persistence/NoteServerSyncHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index 488c34a05..ec716f684 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -295,7 +295,7 @@ private void pullRemoteChanges() { status = LoginStatus.JSON_FAILED; } if (status!=LoginStatus.OK) { - Toast.makeText(appContext, appContext.getString(R.string.error_sync, appContext.getString(status.str)), Toast.LENGTH_LONG).show(); + //Toast.makeText(appContext, appContext.getString(R.string.error_sync, appContext.getString(status.str)), Toast.LENGTH_LONG).show(); // FIXME move to onPostExecute! } } From 3b8648d3c7d3513418ca0f27a5787334af13c4b6 Mon Sep 17 00:00:00 2001 From: korelstar Date: Mon, 1 Aug 2016 19:42:19 +0200 Subject: [PATCH 20/25] Bugfix: save edited note in offline mode, too --- .../notes/android/activity/EditNoteActivity.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 9cddfa0f4..0d90a69a2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -144,12 +144,7 @@ private void autoSave() { if (ab != null) { ab.setSubtitle(getString(R.string.action_edit_saving)); } - // #74 - note.setModified(Calendar.getInstance()); - note.setContent(getContent()); - // #80 - note.setTitle(NoteUtil.generateNoteTitle(note.getContent())); - final String content = note.getContent(); + final String content = getContent(); saveData(new ICallback() { @Override public void onFinish() { @@ -205,6 +200,9 @@ public void run() { * @param callback */ private void saveData(ICallback callback) { + note.setModified(Calendar.getInstance()); + note.setContent(getContent()); + note.setTitle(NoteUtil.generateNoteTitle(note.getContent())); db.updateNoteAndSync(note, callback); } } \ No newline at end of file From 559f5b0c9dce822b4aa495c23d05feac31f3dee5 Mon Sep 17 00:00:00 2001 From: korelstar Date: Mon, 1 Aug 2016 19:53:29 +0200 Subject: [PATCH 21/25] Bugfix: Show error message in UI thread --- .../persistence/NoteServerSyncHelper.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index ec716f684..b5114a052 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -165,7 +165,7 @@ public void scheduleSync(boolean onlyLocalChanges) { * SyncTask is an AsyncTask which performs the synchronization in a background thread. * Synchronization consists of two parts: pushLocalChanges and pullRemoteChanges. */ - private class SyncTask extends AsyncTask { + private class SyncTask extends AsyncTask { private final boolean onlyLocalChanges; private final List callbacks = new ArrayList<>(); private NotesClient client; @@ -188,17 +188,18 @@ protected void onPreExecute() { } @Override - protected Void doInBackground(Void... voids) { + protected LoginStatus doInBackground(Void... voids) { client = createNotesClient(); // recreate NoteClients on every sync in case the connection settings was changed Log.d(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); dbHelper.debugPrintFullDB(); + LoginStatus status = LoginStatus.OK; pushLocalChanges(); if(!onlyLocalChanges) { - pullRemoteChanges(); + status = pullRemoteChanges(); } dbHelper.debugPrintFullDB(); Log.d(getClass().getSimpleName(), "SYNCHRONIZATION FINISHED"); - return null; + return status; } /** @@ -256,7 +257,7 @@ private void pushLocalChanges() { /** * Pull remote Changes: update or create each remote note (if local pendant has no changes) and remove remotely deleted notes. */ - private void pullRemoteChanges() { + private LoginStatus pullRemoteChanges() { Log.d(getClass().getSimpleName(), "pullRemoteChanges()"); LoginStatus status = null; try { @@ -289,19 +290,19 @@ private void pullRemoteChanges() { } status = LoginStatus.OK; } catch (IOException e) { - e.printStackTrace(); status = LoginStatus.CONNECTION_FAILED; } catch (JSONException e) { status = LoginStatus.JSON_FAILED; } - if (status!=LoginStatus.OK) { - //Toast.makeText(appContext, appContext.getString(R.string.error_sync, appContext.getString(status.str)), Toast.LENGTH_LONG).show(); // FIXME move to onPostExecute! - } + return status; } @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); + protected void onPostExecute(LoginStatus status) { + super.onPostExecute(status); + if (status!=LoginStatus.OK) { + Toast.makeText(appContext, appContext.getString(R.string.error_sync, appContext.getString(status.str)), Toast.LENGTH_LONG).show(); + } syncActive = false; // notify callbacks for (ICallback callback : callbacks) { From df744dd91f46869d4de6e86869d24094462bc64d Mon Sep 17 00:00:00 2001 From: korelstar Date: Mon, 1 Aug 2016 20:13:23 +0200 Subject: [PATCH 22/25] reduce writes to local storage --- .../owncloud/notes/persistence/NoteSQLiteOpenHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 9a23a480e..93416a220 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -304,8 +304,8 @@ int updateNote(long id, OwnCloudNote remoteNote, DBNote forceUnchangedDBNoteStat } else { // used by: NoteServerSyncHelper.SyncTask.pullRemoteChanges() // update only, if not modified locally - whereClause = key_id + " = ? AND " + key_status + " = ?"; - whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle()}; + whereClause = key_id + " = ? AND " + key_status + " = ? AND ("+key_modified+"!=? OR "+key_content+"!=?)"; + whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle(), remoteNote.getModified(DATE_FORMAT), remoteNote.getContent()}; } int i = db.update(table_notes, values, whereClause, whereArgs); db.close(); From fb3074662e777f1d37eefa2f9df458b7895d13f1 Mon Sep 17 00:00:00 2001 From: korelstar Date: Mon, 1 Aug 2016 20:13:48 +0200 Subject: [PATCH 23/25] reduce logging --- .../owncloud/notes/persistence/NoteServerSyncHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java index b5114a052..2690a639a 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java @@ -191,13 +191,13 @@ protected void onPreExecute() { protected LoginStatus doInBackground(Void... voids) { client = createNotesClient(); // recreate NoteClients on every sync in case the connection settings was changed Log.d(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); - dbHelper.debugPrintFullDB(); + //dbHelper.debugPrintFullDB(); LoginStatus status = LoginStatus.OK; pushLocalChanges(); if(!onlyLocalChanges) { status = pullRemoteChanges(); } - dbHelper.debugPrintFullDB(); + //dbHelper.debugPrintFullDB(); Log.d(getClass().getSimpleName(), "SYNCHRONIZATION FINISHED"); return status; } From 63bbdc4de0c8199754b2cb35a2b079a838a3ae84 Mon Sep 17 00:00:00 2001 From: korelstar Date: Wed, 3 Aug 2016 21:29:38 +0200 Subject: [PATCH 24/25] Refactor: remove recurrent code --- .../activity/NotesListViewActivity.java | 55 ++++++++----------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 7b1de93e3..ed1892642 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -51,6 +51,19 @@ public class NotesListViewActivity extends AppCompatActivity implements private NoteSQLiteOpenHelper db = null; private SearchView searchView = null; + private ICallback syncCallBack = new ICallback() { + @Override + public void onFinish() { + adapter.clearSelection(); + if (mActionMode != null) { + mActionMode.finish(); + } + // adapter.checkForUpdates(db.getNotes()); // FIXME deactivated, since it doesn't remove remotely deleted notes + setListView(db.getNotes()); + swipeRefreshLayout.setRefreshing(false); + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -74,24 +87,14 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onRefresh() { if(db.getNoteServerSyncHelper().isSyncPossible()) { - db.getNoteServerSyncHelper().addCallbackPull(new ICallback() { - @Override - public void onFinish() { - swipeRefreshLayout.setRefreshing(false); - adapter.clearSelection(); - if (mActionMode != null) { - mActionMode.finish(); - } - setListView(db.getNotes()); - } - }); + synchronize(); } else { swipeRefreshLayout.setRefreshing(false); Toast.makeText(getApplicationContext(), getString(R.string.error_sync, getString(NotesClientUtil.LoginStatus.NO_NETWORK.str)), Toast.LENGTH_LONG).show(); } - db.getNoteServerSyncHelper().scheduleSync(false); } }); + db.getNoteServerSyncHelper().addCallbackPull(syncCallBack); // Floating Action Button findViewById(R.id.fab_create).setOnClickListener(this); @@ -103,19 +106,7 @@ public void onFinish() { @Override protected void onResume() { if (db.getNoteServerSyncHelper().isSyncPossible() && !PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean(SettingsActivity.SETTINGS_FIRST_RUN, true)) { - db.getNoteServerSyncHelper().addCallbackPull(new ICallback() { - @Override - public void onFinish() { - swipeRefreshLayout.setRefreshing(false); - adapter.clearSelection(); - if (mActionMode != null) { - mActionMode.finish(); - } - // adapter.checkForUpdates(db.getNotes()); // FIXME deactivated, since it doesn't remove remotely deleted notes - setListView(db.getNotes()); - } - }); - db.getNoteServerSyncHelper().scheduleSync(false); + synchronize(); } super.onResume(); } @@ -321,17 +312,10 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(db.getNoteServerSyncHelper().isSyncPossible()) { adapter.removeAll(); swipeRefreshLayout.setRefreshing(true); - db.getNoteServerSyncHelper().addCallbackPull(new ICallback() { - @Override - public void onFinish() { - setListView(db.getNotes()); - swipeRefreshLayout.setRefreshing(false); - } - }); + synchronize(); } else { Toast.makeText(getApplicationContext(), getString(R.string.error_sync, getString(NotesClientUtil.LoginStatus.NO_NETWORK.str)), Toast.LENGTH_LONG).show(); } - db.getNoteServerSyncHelper().scheduleSync(false); } } @@ -395,6 +379,11 @@ public void onBackPressed() { } } + private void synchronize() { + db.getNoteServerSyncHelper().addCallbackPull(syncCallBack); + db.getNoteServerSyncHelper().scheduleSync(false); + } + /** * Handler for the MultiSelect Actions */ From 26a051fa9204358359705e4bf5f47c7f6bc7897c Mon Sep 17 00:00:00 2001 From: korelstar Date: Wed, 3 Aug 2016 21:30:38 +0200 Subject: [PATCH 25/25] UI enhancement when editing notes (with no changes) --- .../android/activity/EditNoteActivity.java | 16 +++++-------- .../persistence/NoteSQLiteOpenHelper.java | 24 ++++++++++++------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java index 0d90a69a2..dae99314d 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java @@ -40,7 +40,6 @@ protected void onCreate(final Bundle savedInstanceState) { note = (DBNote) getIntent().getSerializableExtra( NoteActivity.EDIT_NOTE); content = (EditText) findViewById(R.id.editContent); - content.setEnabled(false); content.setText(note.getContent()); content.setEnabled(true); db = new NoteSQLiteOpenHelper(this); @@ -140,9 +139,8 @@ private String getContent() { private void autoSave() { Log.d(LOG_TAG, "START save+sync"); saveActive = true; - ActionBar ab = getSupportActionBar(); - if (ab != null) { - ab.setSubtitle(getString(R.string.action_edit_saving)); + if (actionBar != null) { + actionBar.setSubtitle(getString(R.string.action_edit_saving)); } final String content = getContent(); saveData(new ICallback() { @@ -150,7 +148,8 @@ private void autoSave() { public void onFinish() { // AFTER SYNCHRONIZATION Log.d(LOG_TAG, "...sync finished"); - getSupportActionBar().setSubtitle(getResources().getString(R.string.action_edit_saved)); + actionBar.setTitle(note.getTitle()); + actionBar.setSubtitle(getResources().getString(R.string.action_edit_saved)); Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() { @Override public void run() { @@ -158,7 +157,7 @@ public void run() { @Override public void run() { // AFTER 1 SECOND: set ActionBar to default title - getSupportActionBar().setSubtitle(getString(R.string.action_edit_editing)); + actionBar.setSubtitle(getString(R.string.action_edit_editing)); } }); } @@ -200,9 +199,6 @@ public void run() { * @param callback */ private void saveData(ICallback callback) { - note.setModified(Calendar.getInstance()); - note.setContent(getContent()); - note.setTitle(NoteUtil.generateNoteTitle(note.getContent())); - db.updateNoteAndSync(note, callback); + note = db.updateNoteAndSync(note, getContent(), callback); } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 93416a220..478910d93 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -239,31 +239,37 @@ public List getLocalModifiedNotes() { } /** - * Updates a single Note and sets a synchronization Flag. + * Updates a single Note with a new content. + * The title is derived from the new content automatically, and modified date as well as DBStatus are updated, too -- if the content differs to the state in the database. * - * @param note Note - Note with the updated Information + * @param oldNote Note to be changed + * @param newContent New content * @param callback When the synchronization is finished, this callback will be invoked (optional). + * @return changed note if differs from database, otherwise the old note. */ - public void updateNoteAndSync(DBNote note, ICallback callback) { + public DBNote updateNoteAndSync(DBNote oldNote, String newContent, ICallback callback) { + debugPrintFullDB(); + DBNote newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNoteTitle(newContent), newContent, DBStatus.LOCAL_EDITED); SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(key_status, DBStatus.LOCAL_EDITED.getTitle()); - values.put(key_title, note.getTitle()); - values.put(key_modified, note.getModified(DATE_FORMAT)); - values.put(key_content, note.getContent()); - int rows = db.update(table_notes, values, key_id + " = ? AND " + key_content + " != ?", new String[]{String.valueOf(note.getId()), note.getContent()}); + values.put(key_status, newNote.getStatus().getTitle()); + values.put(key_title, newNote.getTitle()); + values.put(key_modified, newNote.getModified(DATE_FORMAT)); + values.put(key_content, newNote.getContent()); + int rows = db.update(table_notes, values, key_id + " = ? AND " + key_content + " != ?", new String[]{String.valueOf(newNote.getId()), newNote.getContent()}); db.close(); // if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly. if(rows > 0) { - note.setStatus(DBStatus.LOCAL_EDITED); if(callback!=null) { serverSyncHelper.addCallbackPush(callback); } serverSyncHelper.scheduleSync(true); + return newNote; } else { if(callback!=null) { callback.onFinish(); } + return oldNote; } }