From a9f51a6a8891c57d57a92ce1299bb1dd1f9ef6ce Mon Sep 17 00:00:00 2001 From: korelstar Date: Fri, 20 Jan 2017 11:46:20 +0100 Subject: [PATCH 1/4] speed-up synchronization and introduce categories --- .../android/activity/CreateNoteActivity.java | 2 +- .../android/activity/EditNoteActivity.java | 3 +- .../activity/NotesListViewActivity.java | 6 +- .../activity/SelectSingleNoteActivity.java | 2 +- .../notes/android/widget/AllNotesWidget.java | 6 +- .../owncloud/notes/model/CloudNote.java | 29 +++- .../owncloud/notes/model/DBNote.java | 7 +- .../owncloud/notes/model/ItemAdapter.java | 4 + .../owncloud/notes/model/SyncDataEntry.java | 30 ++++ .../persistence/NoteSQLiteOpenHelper.java | 138 ++++++++++++------ .../persistence/NoteServerSyncHelper.java | 39 +++-- .../owncloud/notes/util/NotesClient.java | 81 +++++++--- app/src/main/res/layout/activity_edit.xml | 35 ++++- .../layout/fragment_notes_list_note_item.xml | 20 ++- 14 files changed, 298 insertions(+), 104 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/model/SyncDataEntry.java diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/CreateNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/CreateNoteActivity.java index 78a3a2952..937313e9b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/CreateNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/CreateNoteActivity.java @@ -72,7 +72,7 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.action_create_save: editTextField.setEnabled(false); String content = editTextField.getText().toString(); - NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(this); + NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(this); Intent data = new Intent(); data.putExtra(NotesListViewActivity.CREATED_NOTE, db.getNote(db.addNoteAndSync(content))); setResult(RESULT_OK, data); 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 0ba718e94..124ef851c 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 @@ -83,7 +83,7 @@ public void onNext(CharSequence charSequence) { } }); - db = new NoteSQLiteOpenHelper(this); + db = NoteSQLiteOpenHelper.getInstance(this); actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setTitle(note.getTitle()); @@ -126,6 +126,7 @@ public void run() { } } }); + // Snackbar.make(findViewById(R.id.editCoordinator), "Note may be outdated. Please connect to the network to update.", Snackbar.LENGTH_INDEFINITE).show(); } @Override 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 50b6e7cb1..bf9030b71 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 @@ -82,7 +82,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_notes_list_view); // Display Data - db = new NoteSQLiteOpenHelper(this); + db = NoteSQLiteOpenHelper.getInstance(this); initList(); refreshList(); @@ -417,7 +417,7 @@ 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); + db = NoteSQLiteOpenHelper.getInstance(this); if(db.getNoteServerSyncHelper().isSyncPossible()) { adapter.removeAll(); swipeRefreshLayout.setRefreshing(true); @@ -468,7 +468,7 @@ public void onNoteClick(int position, View v) { @Override public void onNoteFavoriteClick(int position, View view) { DBNote note = (DBNote) adapter.getItem(position); - NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(view.getContext()); + NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(view.getContext()); db.toggleFavorite(note, syncCallBack); adapter.notifyItemChanged(position); refreshList(); 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 412c98078..88b120f8c 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 @@ -37,7 +37,7 @@ public void onCreate(Bundle savedInstanceState) { setResult(RESULT_CANCELED); setContentView(R.layout.activity_select_single_note); // Display Data - db = new NoteSQLiteOpenHelper(this); + db = NoteSQLiteOpenHelper.getInstance(this); db.getNoteServerSyncHelper().scheduleSync(false); setListView(db.getNotes()); 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 4aa320b19..f0f2e10af 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 @@ -60,14 +60,14 @@ public StackRemoteViewsFactory(Context context, Intent intent) { mContext = context; mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); - NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(mContext); + NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(mContext); db.getNoteServerSyncHelper().scheduleSync(false); mWidgetItems = db.getNotes(); - mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", false, DBStatus.VOID)); + mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", false, null, null, DBStatus.VOID)); } public void onCreate() { - mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", false, DBStatus.VOID)); + mWidgetItems.add(new DBNote(0, 0, Calendar.getInstance(), "Test-Titel", "Test-Beschreibung", false, null, null, DBStatus.VOID)); } @Override diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/CloudNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/CloudNote.java index da1e74b4b..e423a4244 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/CloudNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/CloudNote.java @@ -18,14 +18,18 @@ public class CloudNote implements Serializable { private Calendar modified = null; private String content = ""; private boolean favorite = false; + private String category = ""; + private String etag = ""; - public CloudNote(long remoteId, Calendar modified, String title, String content, boolean favorite) { + public CloudNote(long remoteId, Calendar modified, String title, String content, boolean favorite, String category, String etag) { this.remoteId = remoteId; if (title != null) setTitle(title); setTitle(title); setContent(content); setFavorite(favorite); + setCategory(category); + setEtag(etag); this.modified = modified; } @@ -51,8 +55,9 @@ public Calendar getModified() { } public String getModified(String format) { - return new SimpleDateFormat(format, Locale.GERMANY) - .format(this.getModified().getTimeInMillis()); + if(modified==null) + return null; + return new SimpleDateFormat(format, Locale.GERMANY) .format(this.getModified().getTimeInMillis()); } public void setModified(Calendar modified) { @@ -75,8 +80,24 @@ public void setFavorite(boolean favorite) { this.favorite = favorite; } + public String getEtag() { + return etag; + } + + public void setEtag(String etag) { + this.etag = etag; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category==null ? "" : category; + } + @Override public String toString() { - return "#" + getRemoteId() + " " + (isFavorite() ? " (*) " : " ") + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ")"; + return "R" + getRemoteId() + " " + (isFavorite() ? " (*) " : " ") + getCategory() + " / " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + " / " + getEtag() + ")"; } } \ No newline at end of file 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 887e33184..d0f6d7ff0 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 @@ -3,7 +3,6 @@ import java.io.Serializable; import java.util.Calendar; -import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.NoteUtil; /** @@ -16,8 +15,8 @@ public class DBNote extends CloudNote implements Item, Serializable { private DBStatus status; private String excerpt = ""; - public DBNote(long id, long remoteId, Calendar modified, String title, String content, boolean favorite, DBStatus status) { - super(remoteId, modified, title, content, favorite); + public DBNote(long id, long remoteId, Calendar modified, String title, String content, boolean favorite, String category, String etag, DBStatus status) { + super(remoteId, modified, title, content, favorite, category, etag); this.id = id; setExcerpt(content); this.status = status; @@ -55,6 +54,6 @@ public boolean isSection() { @Override public String toString() { - return "#" + getId() + "/R"+getRemoteId()+" " + (isFavorite() ? " (*) " : " ") + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ") " + getStatus(); + return "#"+getId()+"/" + super.toString() + " " + getStatus(); } } 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 3242d7a32..af67882a2 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 @@ -91,6 +91,8 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) final DBNote note = (DBNote) item; final NoteViewHolder nvHolder = ((NoteViewHolder) holder); nvHolder.noteTitle.setText(note.getTitle()); + nvHolder.noteCategory.setVisibility(note.getCategory().isEmpty() ? View.GONE : View.VISIBLE); + nvHolder.noteCategory.setText(note.getCategory()); nvHolder.noteExcerpt.setText(note.getExcerpt()); nvHolder.noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.GONE : View.VISIBLE); nvHolder.noteFavorite.setImageResource(note.isFavorite() ? R.drawable.ic_star_grey600_24dp : R.drawable.ic_star_outline_grey600_24dp); @@ -156,6 +158,7 @@ public interface NoteClickListener { public class NoteViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener, View.OnClickListener { // each data item is just a string in this case public TextView noteTitle; + public TextView noteCategory; public TextView noteExcerpt; public ImageView noteStatus; public ImageView noteFavorite; @@ -164,6 +167,7 @@ public class NoteViewHolder extends RecyclerView.ViewHolder implements View.OnLo private NoteViewHolder(View v) { super(v); this.noteTitle = (TextView) v.findViewById(R.id.noteTitle); + this.noteCategory = (TextView) v.findViewById(R.id.noteCategory); this.noteExcerpt = (TextView) v.findViewById(R.id.noteExcerpt); this.noteStatus = (ImageView) v.findViewById(R.id.noteStatus); this.noteFavorite = (ImageView) v.findViewById(R.id.noteFavorite); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/SyncDataEntry.java b/app/src/main/java/it/niedermann/owncloud/notes/model/SyncDataEntry.java new file mode 100644 index 000000000..ca11e5c32 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/SyncDataEntry.java @@ -0,0 +1,30 @@ +package it.niedermann.owncloud.notes.model; + + +/** + * This bean class is a reduced version from {@link DBNote}. + * It contains only those attributes that are required for preparing the synchronization. + */ +public class SyncDataEntry { + private long id; + private long remoteId; + private String etag; + + public SyncDataEntry(long id, long remoteId, String etag) { + this.id = id; + this.remoteId = remoteId; + this.etag = etag; + } + + public long getId() { + return id; + } + + public long getRemoteId() { + return remoteId; + } + + public String getEtag() { + return etag; + } +} 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 df9a05a26..d2afee62a 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 @@ -11,12 +11,15 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; import it.niedermann.owncloud.notes.model.CloudNote; +import it.niedermann.owncloud.notes.model.SyncDataEntry; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; @@ -26,9 +29,10 @@ * Created by stefan on 19.09.15. */ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { + public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - private static final int database_version = 6; + private static final int database_version = 7; private static final String database_name = "OWNCLOUD_NOTES"; private static final String table_notes = "NOTES"; private static final String key_id = "ID"; @@ -38,16 +42,28 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { private static final String key_modified = "MODIFIED"; private static final String key_content = "CONTENT"; private static final String key_favorite = "FAVORITE"; - private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite}; + private static final String key_category = "CATEGORY"; + private static final String key_etag = "ETAG"; + private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag }; private static final String default_order = key_favorite + " DESC, " + key_modified + " DESC"; + private static NoteSQLiteOpenHelper instance; + private NoteServerSyncHelper serverSyncHelper = null; private Context context = null; - public NoteSQLiteOpenHelper(Context context) { + private NoteSQLiteOpenHelper(Context context) { super(context, database_name, null, database_version); this.context = context.getApplicationContext(); serverSyncHelper = NoteServerSyncHelper.getInstance(this); + //recreateDatabase(getWritableDatabase()); + } + + public static NoteSQLiteOpenHelper getInstance(Context context) { + if(instance==null) + return instance = new NoteSQLiteOpenHelper(context.getApplicationContext()); + else + return instance; } public NoteServerSyncHelper getNoteServerSyncHelper() { @@ -61,19 +77,24 @@ public NoteServerSyncHelper getNoteServerSyncHelper() { */ @Override 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, '" + - key_favorite + "' INTEGER DEFAULT 0)"); - // FIXME create index for status and remote_id + 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, " + + key_favorite + " INTEGER DEFAULT 0, " + + key_category + " TEXT NOT NULL, " + + key_etag + " TEXT)"); + createIndexes(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if(oldVersion<3) { + recreateDatabase(db); + } if(oldVersion<4) { clearDatabase(db); } @@ -85,12 +106,43 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if(oldVersion<6) { db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_favorite+" INTEGER DEFAULT 0"); } + if(oldVersion<7) { + dropIndexes(db); + db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_category+" TEXT NOT NULL"); + db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_etag+" TEXT"); + createIndexes(db); + } } private void clearDatabase(SQLiteDatabase db) { db.delete(table_notes, null, null); } + private void recreateDatabase(SQLiteDatabase db) { + db.execSQL("DROP TABLE "+table_notes); + onCreate(db); + } + + private void dropIndexes(SQLiteDatabase db) { + Cursor c = db.query("sqlite_master", new String[]{"name"}, "type=?", new String[]{"index"}, null, null, null); + while (c.moveToNext()) { + db.execSQL( "DROP INDEX "+c.getString(0)); + } + c.close(); + } + + private void createIndexes(SQLiteDatabase db) { + createIndex(db, table_notes, key_remote_id); + createIndex(db, table_notes, key_status); + createIndex(db, table_notes, key_favorite); + createIndex(db, table_notes, key_category); + createIndex(db, table_notes, key_modified); + } + private void createIndex(SQLiteDatabase db, String table, String column) { + String indexName = table+"_"+column+"_idx"; + db.execSQL("CREATE INDEX IF NOT EXISTS "+indexName+" ON "+table+"("+column+")"); + } + public Context getContext() { return context; } @@ -102,7 +154,7 @@ public Context getContext() { */ @SuppressWarnings("UnusedReturnValue") public long addNoteAndSync(String content) { - CloudNote note = new CloudNote(0, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, false); + CloudNote note = new CloudNote(0, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, false, null, null); return addNoteAndSync(note); } @@ -113,7 +165,7 @@ public long addNoteAndSync(String content) { */ @SuppressWarnings("UnusedReturnValue") public long addNoteAndSync(CloudNote note) { - DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), DBStatus.LOCAL_EDITED); + DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), note.getEtag(), DBStatus.LOCAL_EDITED); long id = addNote(dbNote); getNoteServerSyncHelper().scheduleSync(true); return id; @@ -144,6 +196,8 @@ long addNote(CloudNote note) { values.put(key_modified, note.getModified(NoteSQLiteOpenHelper.DATE_FORMAT)); values.put(key_content, note.getContent()); values.put(key_favorite, note.isFavorite()); + values.put(key_category, note.getCategory()); + values.put(key_etag, note.getEtag()); long id = db.insert(table_notes, null, values); db.close(); return id; @@ -157,23 +211,8 @@ long addNote(CloudNote note) { */ @SuppressWarnings("unused") public DBNote getNote(long id) { - SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = - db.query(table_notes, - columns, - key_id + " = ? AND " + key_status + " != ?", - new String[]{String.valueOf(id), DBStatus.LOCAL_DELETED.getTitle()}, - null, - null, - null, - null); - if (cursor == null) { - return null; - } - cursor.moveToFirst(); - DBNote note = getNoteFromCursor(cursor); - cursor.close(); - return note; + List notes = getNotesCustom(key_id + " = ? AND " + key_status + " != ?", new String[]{String.valueOf(id), DBStatus.LOCAL_DELETED.getTitle()}, null); + return notes.isEmpty() ? null : notes.get(0); } /** @@ -187,10 +226,8 @@ private List getNotesCustom(String selection, String[] selectionArgs, St SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(table_notes, columns, selection, selectionArgs, null, null, orderBy); List notes = new ArrayList<>(); - if (cursor.moveToFirst()) { - do { - notes.add(getNoteFromCursor(cursor)); - } while (cursor.moveToNext()); + while (cursor.moveToNext()) { + notes.add(getNoteFromCursor(cursor)); } cursor.close(); return notes; @@ -210,17 +247,30 @@ private DBNote getNoteFromCursor(Cursor cursor) { } catch (ParseException e) { e.printStackTrace(); } - return new DBNote(cursor.getLong(0), cursor.getLong(1), modified, cursor.getString(3), cursor.getString(5), cursor.getInt(6)>0, DBStatus.parse(cursor.getString(2))); + return new DBNote(cursor.getLong(0), cursor.getLong(1), modified, cursor.getString(3), cursor.getString(5), cursor.getInt(6)>0, cursor.getString(7), cursor.getString(8), DBStatus.parse(cursor.getString(2))); } public void debugPrintFullDB() { List notes = getNotesCustom("", new String[]{}, default_order); - Log.d(getClass().getSimpleName(), "Full Database:"); + Log.d(getClass().getSimpleName(), "Full Database ("+notes.size()+" notes):"); for (DBNote note : notes) { Log.d(getClass().getSimpleName(), " "+note); } } + public Map getSyncData() { + Map result = new HashMap<>(); + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(table_notes, new String[]{ key_id, key_remote_id, key_etag }, key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, null, null, null); + if (cursor.moveToFirst()) { + while(cursor.moveToNext()) { + result.put(cursor.getLong(1), new SyncDataEntry(cursor.getLong(0), cursor.getLong(1), cursor.getString(2))); + } + } + cursor.close(); + return result; + } + /** * Returns a list of all Notes in the Database * @@ -285,9 +335,9 @@ public DBNote updateNoteAndSync(DBNote oldNote, String newContent, ICallback cal debugPrintFullDB(); DBNote newNote; if(newContent==null) { - newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), DBStatus.LOCAL_EDITED); + newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED); } else { - newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNoteTitle(newContent), newContent, oldNote.isFavorite(), DBStatus.LOCAL_EDITED); + newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNoteTitle(newContent), newContent, oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED); } SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -339,6 +389,8 @@ int updateNote(long id, CloudNote remoteNote, DBNote forceUnchangedDBNoteState) values.put(key_modified, remoteNote.getModified(DATE_FORMAT)); values.put(key_content, remoteNote.getContent()); values.put(key_favorite, remoteNote.isFavorite()); + values.put(key_category, remoteNote.getCategory()); + values.put(key_etag, remoteNote.getEtag()); String whereClause; String[] whereArgs; if(forceUnchangedDBNoteState!=null) { @@ -346,13 +398,13 @@ int updateNote(long id, CloudNote remoteNote, DBNote forceUnchangedDBNoteState) // update only, if not modified locally during the synchronization // (i.e. all (!) user changeable columns (content, favorite) should still have the same value), // uses reference value gathered at start of synchronization - whereClause = key_id + " = ? AND " + key_content + " = ? AND " + key_favorite + " = ?"; - whereArgs = new String[]{String.valueOf(id), forceUnchangedDBNoteState.getContent(), forceUnchangedDBNoteState.isFavorite()?"1":"0"}; + whereClause = key_id + " = ? AND " + key_content + " = ? AND " + key_favorite + " = ? AND " + key_category + " = ?"; + whereArgs = new String[]{String.valueOf(id), forceUnchangedDBNoteState.getContent(), forceUnchangedDBNoteState.isFavorite()?"1":"0", forceUnchangedDBNoteState.getCategory()}; } else { // used by: NoteServerSyncHelper.SyncTask.pullRemoteChanges() // update only, if not modified locally (i.e. STATUS="") and if modified remotely (i.e. any (!) column has changed) - whereClause = key_id + " = ? AND " + key_status + " = ? AND ("+key_modified+"!=? OR "+key_content+"!=? OR "+key_favorite+"!=?)"; - whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle(), remoteNote.getModified(DATE_FORMAT), remoteNote.getContent(), remoteNote.isFavorite()?"1":"0"}; + whereClause = key_id + " = ? AND " + key_status + " = ? AND ("+key_modified+"!=? OR "+key_title+"!=? OR "+key_favorite+"!=? OR "+key_category+"!=? OR "+(remoteNote.getEtag()!=null ? key_etag+" IS NULL OR " : "")+key_etag+"!=? OR "+key_content+"!=?)"; + whereArgs = new String[]{String.valueOf(id), DBStatus.VOID.getTitle(), remoteNote.getModified(DATE_FORMAT), remoteNote.getTitle(), remoteNote.isFavorite()?"1":"0", remoteNote.getCategory(), remoteNote.getEtag(), remoteNote.getContent()}; } int i = db.update(table_notes, values, whereClause, whereArgs); db.close(); 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 76203615d..455c2e002 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 @@ -17,7 +17,6 @@ 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; @@ -28,6 +27,7 @@ import it.niedermann.owncloud.notes.model.CloudNote; import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBStatus; +import it.niedermann.owncloud.notes.model.SyncDataEntry; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NotesClient; import it.niedermann.owncloud.notes.util.NotesClientUtil.LoginStatus; @@ -197,12 +197,16 @@ protected LoginStatus doInBackground(Void... voids) { Log.d(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); //dbHelper.debugPrintFullDB(); LoginStatus status = LoginStatus.OK; + long timeBeforePush = System.currentTimeMillis(); pushLocalChanges(); + long timePush = System.currentTimeMillis() - timeBeforePush; + long timeBeforePull = System.currentTimeMillis(); if(!onlyLocalChanges) { status = pullRemoteChanges(); } - //dbHelper.debugPrintFullDB(); - Log.d(getClass().getSimpleName(), "SYNCHRONIZATION FINISHED"); + long timePull = System.currentTimeMillis() - timeBeforePull; + dbHelper.debugPrintFullDB(); + Log.i(getClass().getSimpleName(), "SYNCHRONIZATION FINISHED ("+timePush+"ms/"+timePull+"ms)"); return status; } @@ -267,33 +271,38 @@ private LoginStatus pullRemoteChanges() { Log.d(getClass().getSimpleName(), "pullRemoteChanges()"); LoginStatus status = null; try { - List localNotes = dbHelper.getNotes(); - Map localIDmap = new HashMap<>(); - for (DBNote note : localNotes) { - localIDmap.put(note.getRemoteId(), note.getId()); - } - List remoteNotes = client.getNotes(); + long timeStart = System.currentTimeMillis(); + Map syncDataMap = dbHelper.getSyncData(); + long timePre = System.currentTimeMillis() - timeStart; + List remoteNotes = client.getNotes(syncDataMap.values()); + long timeBeforeProcess = System.currentTimeMillis(); Set remoteIDs = new HashSet<>(); // pull remote changes: update or create each remote note for (CloudNote remoteNote : remoteNotes) { Log.d(getClass().getSimpleName(), " Process Remote Note: "+remoteNote); remoteIDs.add(remoteNote.getRemoteId()); - if(localIDmap.containsKey(remoteNote.getRemoteId())) { + if(remoteNote.getModified()==null) { + Log.d(getClass().getSimpleName(), " ... unchanged"); + } else if(syncDataMap.containsKey(remoteNote.getRemoteId())) { Log.d(getClass().getSimpleName(), " ... found -> Update"); - dbHelper.updateNote(localIDmap.get(remoteNote.getRemoteId()), remoteNote, null); + dbHelper.updateNote(syncDataMap.get(remoteNote.getRemoteId()).getId(), remoteNote, null); } else { Log.d(getClass().getSimpleName(), " ... create"); dbHelper.addNote(remoteNote); } } + long timeProcessing = System.currentTimeMillis()-timeBeforeProcess; 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); + long timeBeforeDelete = System.currentTimeMillis(); + for (SyncDataEntry entry : syncDataMap.values()) { + if(!remoteIDs.contains(entry.getRemoteId())) { + Log.d(getClass().getSimpleName(), " ... remove "+entry.getId()); + dbHelper.deleteNote(entry.getId(), DBStatus.VOID); } } + long timeDelete = System.currentTimeMillis()-timeBeforeDelete; + Log.i(getClass().getSimpleName(), "pullRemoteChanges: pre: "+timePre+"ms; post: "+timeProcessing+"ms; delete: "+timeDelete+"ms"); status = LoginStatus.OK; } catch (IOException e) { Log.e(getClass().getSimpleName(), "Exception", e); 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 f1b161d95..d173e81d6 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 @@ -16,10 +16,12 @@ import java.net.URL; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collection; import java.util.GregorianCalendar; import java.util.List; import it.niedermann.owncloud.notes.model.CloudNote; +import it.niedermann.owncloud.notes.model.SyncDataEntry; public class NotesClient { @@ -31,6 +33,8 @@ public class NotesClient { private static final String key_title = "title"; private static final String key_content = "content"; private static final String key_favorite = "favorite"; + private static final String key_category = "category"; + private static final String key_etag = "etag"; private static final String key_modified = "modified"; private static final String application_json = "application/json"; private String url = ""; @@ -44,33 +48,41 @@ public NotesClient(String url, String username, String password) { } private CloudNote getNoteFromJSON(JSONObject json) throws JSONException { - long noteId = 0; - String noteTitle = ""; - String noteContent = ""; - Calendar noteModified = null; - boolean noteFavorite = false; + long id = 0; + String title = ""; + String content = ""; + Calendar modified = null; + boolean favorite = false; + String category = null; + String etag = null; if (!json.isNull(key_id)) { - noteId = json.getLong(key_id); + id = json.getLong(key_id); } if (!json.isNull(key_title)) { - noteTitle = json.getString(key_title); + title = json.getString(key_title); } if (!json.isNull(key_content)) { - noteContent = json.getString(key_content); + content = json.getString(key_content); } if (!json.isNull(key_modified)) { - noteModified = GregorianCalendar.getInstance(); - noteModified.setTimeInMillis(json.getLong(key_modified) * 1000); + modified = GregorianCalendar.getInstance(); + modified.setTimeInMillis(json.getLong(key_modified) * 1000); } if (!json.isNull(key_favorite)) { - noteFavorite = json.getBoolean(key_favorite); + favorite = json.getBoolean(key_favorite); } - return new CloudNote(noteId, noteModified, noteTitle, noteContent, noteFavorite); + if (!json.isNull(key_category)) { + category = json.getString(key_category); + } + if (!json.isNull(key_etag)) { + etag = json.getString(key_etag); + } + return new CloudNote(id, modified, title, content, favorite, category, etag); } - public List getNotes() throws JSONException, IOException { + public List getNotes(Collection etags) throws JSONException, IOException { List notesList = new ArrayList<>(); - JSONArray notes = new JSONArray(requestServer("notes", METHOD_GET, null)); + JSONArray notes = new JSONArray(requestServer("notes", METHOD_GET, null, etags)); for (int i = 0; i < notes.length(); i++) { JSONObject json = notes.getJSONObject(i); notesList.add(getNoteFromJSON(json)); @@ -88,7 +100,7 @@ public List getNotes() throws JSONException, IOException { */ @SuppressWarnings("unused") public CloudNote getNoteById(long id) throws JSONException, IOException { - JSONObject json = new JSONObject(requestServer("notes/" + id, METHOD_GET, null)); + JSONObject json = new JSONObject(requestServer("notes/" + id, METHOD_GET, null, null)); return getNoteFromJSON(json); } @@ -97,7 +109,7 @@ private CloudNote putNote(CloudNote note, String path, String method) throws JS paramObject.accumulate(key_content, note.getContent()); paramObject.accumulate(key_modified, note.getModified().getTimeInMillis()/1000); paramObject.accumulate(key_favorite, note.isFavorite()); - JSONObject json = new JSONObject(requestServer(path, method, paramObject)); + JSONObject json = new JSONObject(requestServer(path, method, paramObject, null)); return getNoteFromJSON(json); } @@ -119,7 +131,7 @@ public CloudNote editNote(CloudNote note) throws JSONException, IOException { public void deleteNote(long noteId) throws IOException { - this.requestServer("notes/" + noteId, METHOD_DELETE, null); + this.requestServer("notes/" + noteId, METHOD_DELETE, null, null); } /** @@ -132,35 +144,56 @@ public void deleteNote(long noteId) throws * @throws MalformedURLException * @throws IOException */ - private String requestServer(String target, String method, JSONObject params) + private String requestServer(String target, String method, JSONObject params, Collection etags) throws IOException { + StringBuilder etagsBuilder = new StringBuilder(); + if(etags!=null) { + for (SyncDataEntry entry : etags) { + if (entry.getEtag() != null) { + if (etagsBuilder.length() > 0) + etagsBuilder.append(","); + etagsBuilder.append(entry.getRemoteId()).append("-").append(entry.getEtag()); + } + } + } + StringBuffer result = new StringBuffer(); String targetURL = url + "index.php/apps/notes/api/v0.2/" + target; + long timeStart = System.currentTimeMillis(); HttpURLConnection con = (HttpURLConnection) new URL(targetURL) .openConnection(); con.setRequestMethod(method); con.setRequestProperty( "Authorization", - "Basic " - + new String(Base64.encode((username + ":" - + password).getBytes(), Base64.NO_WRAP))); + "Basic " + Base64.encodeToString((username + ":" + password).getBytes(), Base64.NO_WRAP)); + if(etagsBuilder.length()>0) { + con.setRequestProperty("X-Notes-ETags", etagsBuilder.toString()); + } con.setConnectTimeout(10 * 1000); // 10 seconds - Log.d(getClass().getSimpleName(), method + " " + targetURL); + Log.i(getClass().getSimpleName(), method + " " + targetURL); + byte[] paramData=null; if (params != null) { + paramData = params.toString().getBytes(); Log.d(getClass().getSimpleName(), "Params: "+params); - con.setFixedLengthStreamingMode(params.toString().getBytes().length); + con.setFixedLengthStreamingMode(paramData.length); con.setRequestProperty("Content-Type", application_json); con.setDoOutput(true); OutputStream os = con.getOutputStream(); - os.write(params.toString().getBytes()); + os.write(paramData); os.flush(); os.close(); } + long timePre = System.currentTimeMillis() - timeStart; + long timeBeforeStream = System.currentTimeMillis(); BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream())); + Log.i(getClass().getSimpleName(), "Content-Length: "+con.getContentLength()+"; Etag: " + con.getHeaderField("Etag") + (paramData==null ? "" : "; Request length: "+paramData.length)); String line; while ((line = rd.readLine()) != null) { result.append(line); } + long timeStream = System.currentTimeMillis() - timeBeforeStream; + Log.i(getClass().getSimpleName(), "Result length: " + result.length() + (paramData==null ? "" : "; Request length: "+paramData.length)); + Log.i(getClass().getSimpleName(), "timePre: "+timePre+"ms; timeStream: "+timeStream+"ms"); return result.toString(); } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_edit.xml b/app/src/main/res/layout/activity_edit.xml index 8f736edfe..47a802b9a 100644 --- a/app/src/main/res/layout/activity_edit.xml +++ b/app/src/main/res/layout/activity_edit.xml @@ -1,12 +1,42 @@ - + + - \ No newline at end of file + + \ No newline at end of file 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 735f65bc2..d86c31057 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 @@ -23,16 +23,30 @@ android:textSize="16sp" /> + + Date: Fri, 20 Jan 2017 16:37:43 +0100 Subject: [PATCH 2/4] fix doubled notes --- .../owncloud/notes/persistence/NoteSQLiteOpenHelper.java | 6 ++---- 1 file changed, 2 insertions(+), 4 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 d2afee62a..3eb282090 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 @@ -262,10 +262,8 @@ public Map getSyncData() { Map result = new HashMap<>(); SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(table_notes, new String[]{ key_id, key_remote_id, key_etag }, key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, null, null, null); - if (cursor.moveToFirst()) { - while(cursor.moveToNext()) { - result.put(cursor.getLong(1), new SyncDataEntry(cursor.getLong(0), cursor.getLong(1), cursor.getString(2))); - } + while(cursor.moveToNext()) { + result.put(cursor.getLong(1), new SyncDataEntry(cursor.getLong(0), cursor.getLong(1), cursor.getString(2))); } cursor.close(); return result; From 79a5b2d74fe0297874464faeb001fc233c3afc87 Mon Sep 17 00:00:00 2001 From: korelstar Date: Tue, 24 Jan 2017 16:37:50 +0100 Subject: [PATCH 3/4] fix database upgrade --- .../owncloud/notes/persistence/NoteSQLiteOpenHelper.java | 5 +++-- 1 file changed, 3 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 3eb282090..c4d25857b 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 @@ -85,7 +85,7 @@ public void onCreate(SQLiteDatabase db) { key_modified + " TEXT, " + key_content + " TEXT, " + key_favorite + " INTEGER DEFAULT 0, " + - key_category + " TEXT NOT NULL, " + + key_category + " TEXT NOT NULL DEFAULT '', " + key_etag + " TEXT)"); createIndexes(db); } @@ -108,7 +108,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } if(oldVersion<7) { dropIndexes(db); - db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_category+" TEXT NOT NULL"); + db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_category+" TEXT NOT NULL DEFAULT ''"); db.execSQL("ALTER TABLE "+table_notes+" ADD COLUMN "+key_etag+" TEXT"); createIndexes(db); } @@ -119,6 +119,7 @@ private void clearDatabase(SQLiteDatabase db) { } private void recreateDatabase(SQLiteDatabase db) { + dropIndexes(db); db.execSQL("DROP TABLE "+table_notes); onCreate(db); } From c56b886c6dbba7288556e0a5989abf72b78df3c3 Mon Sep 17 00:00:00 2001 From: korelstar Date: Tue, 24 Jan 2017 16:38:39 +0100 Subject: [PATCH 4/4] use new server-api-syntax --- .../java/it/niedermann/owncloud/notes/util/NotesClient.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 d173e81d6..d7ac1c3a0 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 @@ -150,9 +150,7 @@ private String requestServer(String target, String method, JSONObject params, Co if(etags!=null) { for (SyncDataEntry entry : etags) { if (entry.getEtag() != null) { - if (etagsBuilder.length() > 0) - etagsBuilder.append(","); - etagsBuilder.append(entry.getRemoteId()).append("-").append(entry.getEtag()); + etagsBuilder.append(entry.getEtag()); } } }