Fix #750 Split up NoteSQLiteOpenHelper

Signed-off-by: stefan-niedermann <info@niedermann.it>
This commit is contained in:
stefan-niedermann 2020-02-28 07:57:03 +01:00
parent 28a6288bcc
commit 3187ca6eab
17 changed files with 328 additions and 302 deletions

View file

@ -25,14 +25,14 @@ import it.niedermann.owncloud.notes.android.activity.NotesListViewActivity;
import it.niedermann.owncloud.notes.android.fragment.AccountChooserDialogFragment;
import it.niedermann.owncloud.notes.model.DBNote;
import it.niedermann.owncloud.notes.model.ItemAdapter;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper.ViewProvider;
public class MultiSelectedActionModeCallback implements Callback {
private final Context context;
private final ViewProvider viewProvider;
private final NoteSQLiteOpenHelper db;
private final NotesDatabase db;
private final ItemAdapter adapter;
private final RecyclerView recyclerView;
private final Runnable refreshLists;
@ -40,7 +40,7 @@ public class MultiSelectedActionModeCallback implements Callback {
private final SearchView searchView;
public MultiSelectedActionModeCallback(
Context context, ViewProvider viewProvider, NoteSQLiteOpenHelper db, ActionMode actionMode, ItemAdapter adapter, RecyclerView recyclerView, Runnable refreshLists, FragmentManager fragmentManager, SearchView searchView) {
Context context, ViewProvider viewProvider, NotesDatabase db, ActionMode actionMode, ItemAdapter adapter, RecyclerView recyclerView, Runnable refreshLists, FragmentManager fragmentManager, SearchView searchView) {
this.context = context;
this.viewProvider = viewProvider;
this.db = db;

View file

@ -16,7 +16,7 @@ import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.model.DBNote;
import it.niedermann.owncloud.notes.model.ISyncCallback;
import it.niedermann.owncloud.notes.model.ItemAdapter;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper.ViewProvider;
public class NotesListViewItemTouchHelper extends ItemTouchHelper {
@ -27,7 +27,7 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper {
SingleSignOnAccount ssoAccount,
Context context,
ViewProvider viewProvider,
NoteSQLiteOpenHelper db,
NotesDatabase db,
ItemAdapter adapter,
ISyncCallback syncCallBack,
Runnable refreshLists

View file

@ -67,7 +67,7 @@ import it.niedermann.owncloud.notes.model.NavigationAdapter;
import it.niedermann.owncloud.notes.model.NavigationAdapter.NavigationItem;
import it.niedermann.owncloud.notes.persistence.LoadNotesListTask;
import it.niedermann.owncloud.notes.persistence.LoadNotesListTask.NotesLoadedListener;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper;
import it.niedermann.owncloud.notes.util.NoteUtil;
@ -140,7 +140,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap
private Category navigationSelection = new Category(null, null);
private String navigationOpen = "";
private ActionMode mActionMode;
private NoteSQLiteOpenHelper db = null;
private NotesDatabase db = null;
private SearchView searchView = null;
private final ISyncCallback syncCallBack = () -> {
adapter.clearSelection(listView);
@ -174,7 +174,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap
categoryAdapterSelectedItem = savedInstanceState.getString(SAVED_STATE_NAVIGATION_ADAPTER_SLECTION);
}
db = NoteSQLiteOpenHelper.getInstance(this);
db = NotesDatabase.getInstance(this);
setupHeader();
setupActionBar();
@ -770,7 +770,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap
@Override
public void onNoteFavoriteClick(int position, View view) {
DBNote note = (DBNote) adapter.getItem(position);
NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(view.getContext());
NotesDatabase db = NotesDatabase.getInstance(view.getContext());
db.toggleFavorite(ssoAccount, note, syncCallBack);
adapter.notifyItemChanged(position);
refreshLists();

View file

@ -27,7 +27,7 @@ import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.android.activity.NotesListViewActivity;
import it.niedermann.owncloud.notes.model.LocalAccount;
import it.niedermann.owncloud.notes.model.NavigationAdapter;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.util.Notes;
public class NoteListWidgetConfiguration extends AppCompatActivity {
@ -40,7 +40,7 @@ public class NoteListWidgetConfiguration extends AppCompatActivity {
private NavigationAdapter adapterCategories;
private NavigationAdapter.NavigationItem itemRecent, itemFavorites;
private NoteSQLiteOpenHelper db = null;
private NotesDatabase db = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -48,7 +48,7 @@ public class NoteListWidgetConfiguration extends AppCompatActivity {
setResult(RESULT_CANCELED);
setContentView(R.layout.activity_note_list_configuration);
db = NoteSQLiteOpenHelper.getInstance(this);
db = NotesDatabase.getInstance(this);
try {
this.localAccount = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(this).name);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {

View file

@ -15,7 +15,7 @@ import java.util.List;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.android.activity.EditNoteActivity;
import it.niedermann.owncloud.notes.model.DBNote;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
private final Context context;
@ -23,7 +23,7 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact
private final boolean darkTheme;
private final String category;
private final long accountId;
private NoteSQLiteOpenHelper db;
private NotesDatabase db;
private List<DBNote> dbNotes;
NoteListWidgetFactory(Context context, Intent intent) {
@ -39,7 +39,7 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact
@Override
public void onCreate() {
db = NoteSQLiteOpenHelper.getInstance(context);
db = NotesDatabase.getInstance(context);
}
@Override

View file

@ -16,7 +16,7 @@ import com.yydcdut.markdown.syntax.text.TextFactory;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.android.activity.EditNoteActivity;
import it.niedermann.owncloud.notes.model.DBNote;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.util.MarkDownUtil;
public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
@ -25,7 +25,7 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa
private final Context context;
private final int appWidgetId;
private NoteSQLiteOpenHelper db;
private NotesDatabase db;
private DBNote note;
private final SharedPreferences sp;
private static Boolean darkTheme;
@ -45,7 +45,7 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa
@Override
public void onCreate() {
db = NoteSQLiteOpenHelper.getInstance(context);
db = NotesDatabase.getInstance(context);
}

View file

@ -22,7 +22,7 @@ import butterknife.ButterKnife;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.android.fragment.AccountChooserAdapter.AccountChooserListener;
import it.niedermann.owncloud.notes.model.LocalAccount;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
public class AccountChooserDialogFragment extends AppCompatDialogFragment implements AccountChooserListener {
private AccountChooserListener accountChooserListener;
@ -51,7 +51,7 @@ public class AccountChooserDialogFragment extends AppCompatDialogFragment implem
View view = View.inflate(getContext(), R.layout.dialog_choose_account, null);
ButterKnife.bind(this, view);
NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(getActivity());
NotesDatabase db = NotesDatabase.getInstance(getActivity());
List<LocalAccount> accountsList = db.getAccounts();
RecyclerView.Adapter adapter = new AccountChooserAdapter(accountsList, this, requireActivity());

View file

@ -34,7 +34,7 @@ import it.niedermann.owncloud.notes.model.DBNote;
import it.niedermann.owncloud.notes.model.DBStatus;
import it.niedermann.owncloud.notes.model.ISyncCallback;
import it.niedermann.owncloud.notes.model.LocalAccount;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.util.NoteUtil;
import static androidx.core.content.pm.ShortcutManagerCompat.isRequestPinShortcutSupported;
@ -58,7 +58,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
protected DBNote note;
@Nullable
private DBNote originalNote;
protected NoteSQLiteOpenHelper db;
protected NotesDatabase db;
private NoteFragmentListener listener;
boolean isNew = true;
@ -113,7 +113,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
} catch (ClassCastException e) {
throw new ClassCastException(context.getClass() + " must implement " + NoteFragmentListener.class);
}
db = NoteSQLiteOpenHelper.getInstance(context);
db = NotesDatabase.getInstance(context);
}
@Override

View file

@ -24,7 +24,7 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.model.NavigationAdapter;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
/**
* This {@link DialogFragment} allows for the selection of a category.
@ -36,7 +36,7 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
private static final String TAG = CategoryDialogFragment.class.getSimpleName();
private static final String STATE_CATEGORY = "category";
private NoteSQLiteOpenHelper db;
private NotesDatabase db;
private CategoryDialogListener listener;
/**
@ -80,7 +80,7 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
} else {
throw new IllegalArgumentException("Calling activity or target fragment must implement " + CategoryDialogListener.class.getCanonicalName());
}
db = NoteSQLiteOpenHelper.getInstance(getActivity());
db = NotesDatabase.getInstance(getActivity());
}
@NonNull

View file

@ -39,7 +39,7 @@ import butterknife.ButterKnife;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.android.activity.EditNoteActivity;
import it.niedermann.owncloud.notes.model.LoginStatus;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.util.DisplayUtils;
import it.niedermann.owncloud.notes.util.MarkDownUtil;
import it.niedermann.owncloud.notes.util.NoteLinksUtils;
@ -188,7 +188,7 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
changedText = note.getContent();
noteContent.setMovementMethod(LinkMovementMethod.getInstance());
db = NoteSQLiteOpenHelper.getInstance(getContext());
db = NotesDatabase.getInstance(getContext());
swipeRefreshLayout.setOnRefreshListener(this);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(requireActivity().getApplicationContext());

View file

@ -33,7 +33,7 @@ import butterknife.ButterKnife;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.android.activity.EditNoteActivity;
import it.niedermann.owncloud.notes.model.ISyncCallback;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.util.DisplayUtils;
import it.niedermann.owncloud.notes.util.MarkDownUtil;
import it.niedermann.owncloud.notes.util.NoteLinksUtils;
@ -139,7 +139,7 @@ public class NoteReadonlyFragment extends SearchableBaseNoteFragment {
}
noteContent.setMovementMethod(LinkMovementMethod.getInstance());
db = NoteSQLiteOpenHelper.getInstance(getActivity());
db = NotesDatabase.getInstance(getActivity());
swipeRefreshLayout.setEnabled(false);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(requireActivity().getApplicationContext());

View file

@ -0,0 +1,271 @@
package it.niedermann.owncloud.notes.persistence;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.net.MalformedURLException;
import java.net.URL;
import it.niedermann.owncloud.notes.android.appwidget.NoteListWidget;
import it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget;
import it.niedermann.owncloud.notes.model.DBStatus;
import it.niedermann.owncloud.notes.util.DatabaseIndexUtil;
import it.niedermann.owncloud.notes.util.NoteUtil;
// Protected APIs
@SuppressWarnings("WeakerAccess")
abstract class AbstractNotesDatabase extends SQLiteOpenHelper {
private static final String TAG = AbstractNotesDatabase.class.getSimpleName();
private static final int database_version = 10;
private final Context context;
protected static final String database_name = "OWNCLOUD_NOTES";
protected static final String table_notes = "NOTES";
protected static final String table_accounts = "ACCOUNTS";
protected static final String key_id = "ID";
protected static final String key_url = "URL";
protected static final String key_account_name = "ACCOUNT_NAME";
protected static final String key_username = "USERNAME";
protected static final String key_account_id = "ACCOUNT_ID";
protected static final String key_remote_id = "REMOTEID";
protected static final String key_status = "STATUS";
protected static final String key_title = "TITLE";
protected static final String key_modified = "MODIFIED";
protected static final String key_content = "CONTENT";
protected static final String key_excerpt = "EXCERPT";
protected static final String key_favorite = "FAVORITE";
protected static final String key_category = "CATEGORY";
protected static final String key_etag = "ETAG";
protected AbstractNotesDatabase(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory) {
super(context, name, factory, database_version);
this.context = context;
}
public Context getContext() {
return context;
}
/**
* Creates initial the Database
*
* @param db Database
*/
@Override
public void onCreate(SQLiteDatabase db) {
createAccountTable(db);
createNotesTable(db);
}
private void createNotesTable(@NonNull SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + table_notes + " ( " +
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
key_remote_id + " INTEGER, " +
key_account_id + " INTEGER, " +
key_status + " VARCHAR(50), " +
key_title + " TEXT, " +
key_modified + " INTEGER DEFAULT 0, " +
key_content + " TEXT, " +
key_favorite + " INTEGER DEFAULT 0, " +
key_category + " TEXT NOT NULL DEFAULT '', " +
key_etag + " TEXT," +
key_excerpt + " TEXT NOT NULL DEFAULT '', " +
"FOREIGN KEY(" + key_account_id + ") REFERENCES " + table_accounts + "(" + key_id + "))");
createNotesIndexes(db);
}
private void createAccountTable(@NonNull SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + table_accounts + " ( " +
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
key_url + " TEXT, " +
key_username + " TEXT, " +
key_account_name + " TEXT UNIQUE, " +
key_etag + " TEXT, " +
key_modified + " INTEGER)");
createAccountIndexes(db);
}
@SuppressWarnings("deprecation")
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 3) {
recreateDatabase(db);
return;
}
if (oldVersion < 4) {
db.delete(table_notes, null, null);
db.delete(table_accounts, null, null);
}
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()});
}
if (oldVersion < 6) {
db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_favorite + " INTEGER DEFAULT 0");
}
if (oldVersion < 7) {
DatabaseIndexUtil.dropIndexes(db);
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");
DatabaseIndexUtil.createIndex(db, table_notes, key_remote_id, key_status, key_favorite, key_category, key_modified);
}
if (oldVersion < 8) {
final String table_temp = "NOTES_TEMP";
db.execSQL("CREATE TABLE " + table_temp + " ( " +
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
key_remote_id + " INTEGER, " +
key_status + " VARCHAR(50), " +
key_title + " TEXT, " +
key_modified + " INTEGER DEFAULT 0, " +
key_content + " TEXT, " +
key_favorite + " INTEGER DEFAULT 0, " +
key_category + " TEXT NOT NULL DEFAULT '', " +
key_etag + " TEXT)");
DatabaseIndexUtil.createIndex(db, table_temp, key_remote_id, key_status, key_favorite, key_category, key_modified);
db.execSQL(String.format("INSERT INTO %s(%s,%s,%s,%s,%s,%s,%s,%s,%s) ", table_temp, key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag)
+ String.format("SELECT %s,%s,%s,%s,strftime('%%s',%s),%s,%s,%s,%s FROM %s", key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag, table_notes));
db.execSQL(String.format("DROP TABLE %s", table_notes));
db.execSQL(String.format("ALTER TABLE %s RENAME TO %s", table_temp, table_notes));
}
if (oldVersion < 9) {
// Create accounts table
createAccountTable(db);
// Add accountId to notes table
db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_account_id + " INTEGER NOT NULL DEFAULT 0");
DatabaseIndexUtil.createIndex(db, table_notes, key_account_id);
// Migrate existing account from SharedPreferences
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String username = sharedPreferences.getString("settingsUsername", "");
String url = sharedPreferences.getString("settingsUrl", "");
if (!url.isEmpty() && url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
try {
String accountName = username + "@" + new URL(url).getHost();
ContentValues migratedAccountValues = new ContentValues();
migratedAccountValues.put(key_url, url);
migratedAccountValues.put(key_username, username);
migratedAccountValues.put(key_account_name, accountName);
db.insert(table_accounts, null, migratedAccountValues);
// After successful insertion of migrated account, set accountId to 1 in each note
ContentValues values = new ContentValues();
values.put(key_account_id, 1);
db.update(table_notes, values, key_account_id + " = ?", new String[]{"NULL"});
// Add FOREIGN_KEY constraint
final String table_temp = "NOTES_TEMP";
db.execSQL(String.format("ALTER TABLE %s RENAME TO %s", table_notes, table_temp));
db.execSQL("CREATE TABLE " + table_notes + " ( " +
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
key_remote_id + " INTEGER, " +
key_account_id + " INTEGER, " +
key_status + " VARCHAR(50), " +
key_title + " TEXT, " +
key_modified + " INTEGER DEFAULT 0, " +
key_content + " TEXT, " +
key_favorite + " INTEGER DEFAULT 0, " +
key_category + " TEXT NOT NULL DEFAULT '', " +
key_etag + " TEXT," +
"FOREIGN KEY(" + key_account_id + ") REFERENCES " + table_accounts + "(" + key_id + "))");
DatabaseIndexUtil.createIndex(db, table_notes, key_remote_id, key_account_id, key_status, key_favorite, key_category, key_modified);
db.execSQL(String.format("INSERT INTO %s(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) ", table_notes, key_id, key_account_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag)
+ String.format("SELECT %s,%s,%s,%s,%s,%s,%s,%s,%s,%s FROM %s", key_id, values.get(key_account_id), key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag, table_temp));
db.execSQL(String.format("DROP TABLE %s;", table_temp));
AppWidgetManager awm = AppWidgetManager.getInstance(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
// Add accountId '1' to any existing (and configured) appwidgets
int[] appWidgetIdsNLW = awm.getAppWidgetIds(new ComponentName(context, NoteListWidget.class));
int[] appWidgetIdsSNW = awm.getAppWidgetIds(new ComponentName(context, SingleNoteWidget.class));
for (int appWidgetId : appWidgetIdsNLW) {
if (sharedPreferences.getInt(NoteListWidget.WIDGET_MODE_KEY + appWidgetId, -1) >= 0) {
editor.putLong(NoteListWidget.ACCOUNT_ID_KEY + appWidgetId, 1);
}
}
for (int appWidgetId : appWidgetIdsSNW) {
if (sharedPreferences.getLong(SingleNoteWidget.WIDGET_KEY + appWidgetId, -1) >= 0) {
editor.putLong(SingleNoteWidget.ACCOUNT_ID_KEY + appWidgetId, 1);
}
}
notifyNotesChanged();
// Clean up no longer needed SharedPreferences
editor.remove("notes_last_etag");
editor.remove("notes_last_modified");
editor.remove("settingsUrl");
editor.remove("settingsUsername");
editor.remove("settingsPassword");
editor.apply();
} catch (MalformedURLException e) {
Log.e(TAG, "Previous URL could not be parsed. Recreating database...");
e.printStackTrace();
recreateDatabase(db);
return;
}
} else {
Log.e(TAG, "Previous URL is empty or does not end with a '/' character. Recreating database...");
recreateDatabase(db);
return;
}
}
if (oldVersion < 10) {
db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_excerpt + " INTEGER NOT NULL DEFAULT ''");
Cursor cursor = db.query(table_notes, new String[]{key_id, key_content}, null, null, null, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
values.put(key_excerpt, NoteUtil.generateNoteExcerpt(cursor.getString(1)));
db.update(table_notes, values, key_id + " = ? ", new String[]{cursor.getString(0)});
}
cursor.close();
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
recreateDatabase(db);
}
private void recreateDatabase(SQLiteDatabase db) {
DatabaseIndexUtil.dropIndexes(db);
db.execSQL("DROP TABLE IF EXISTS " + table_notes);
db.execSQL("DROP TABLE IF EXISTS " + table_accounts);
onCreate(db);
}
private static void createNotesIndexes(@NonNull SQLiteDatabase db) {
DatabaseIndexUtil.createIndex(db, table_notes, key_remote_id, key_account_id, key_status, key_favorite, key_category, key_modified);
}
private static void createAccountIndexes(@NonNull SQLiteDatabase db) {
DatabaseIndexUtil.createIndex(db, table_accounts, key_url, key_username, key_account_name, key_etag, key_modified);
}
protected abstract void notifyNotesChanged();
}

View file

@ -44,7 +44,7 @@ public class LoadNotesListTask extends AsyncTask<Void, Void, List<Item>> {
@Override
protected List<Item> doInBackground(Void... voids) {
List<DBNote> noteList;
NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(context);
NotesDatabase db = NotesDatabase.getInstance(context);
noteList = db.searchNotes(accountId, searchQuery, category.category, category.favorite);
if (category.category == null) {

View file

@ -67,7 +67,7 @@ public class NoteServerSyncHelper {
private static NoteServerSyncHelper instance;
private final NoteSQLiteOpenHelper db;
private final NotesDatabase db;
private final Context context;
// Track network connection changes using a BroadcastReceiver
@ -110,7 +110,7 @@ public class NoteServerSyncHelper {
private final Map<String, List<ISyncCallback>> callbacksPush = new HashMap<>();
private final Map<String, List<ISyncCallback>> callbacksPull = new HashMap<>();
private NoteServerSyncHelper(NoteSQLiteOpenHelper db) {
private NoteServerSyncHelper(NotesDatabase db) {
this.db = db;
this.context = db.getContext();
notesClient = new NotesClient(context.getApplicationContext());
@ -134,7 +134,7 @@ public class NoteServerSyncHelper {
* @param dbHelper NoteSQLiteOpenHelper
* @return NoteServerSyncHelper
*/
public static synchronized NoteServerSyncHelper getInstance(NoteSQLiteOpenHelper dbHelper) {
public static synchronized NoteServerSyncHelper getInstance(NotesDatabase dbHelper) {
if (instance == null) {
instance = new NoteServerSyncHelper(dbHelper);
}

View file

@ -1,11 +1,8 @@
package it.niedermann.owncloud.notes.persistence;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
@ -13,10 +10,8 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
@ -26,8 +21,6 @@ import androidx.annotation.WorkerThread;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
@ -44,10 +37,9 @@ import it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget;
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.ISyncCallback;
import it.niedermann.owncloud.notes.model.LocalAccount;
import it.niedermann.owncloud.notes.model.NavigationAdapter;
import it.niedermann.owncloud.notes.util.DatabaseIndexUtil;
import it.niedermann.owncloud.notes.model.ISyncCallback;
import it.niedermann.owncloud.notes.util.NoteUtil;
import static it.niedermann.owncloud.notes.android.activity.EditNoteActivity.ACTION_SHORTCUT;
@ -55,51 +47,26 @@ import static it.niedermann.owncloud.notes.android.activity.EditNoteActivity.ACT
/**
* Helps to add, get, update and delete Notes with the option to trigger a Resync with the Server.
*/
public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
public class NotesDatabase extends AbstractNotesDatabase {
private static final String TAG = NoteSQLiteOpenHelper.class.getSimpleName();
private static final int database_version = 10;
private static final String database_name = "OWNCLOUD_NOTES";
private static final String table_notes = "NOTES";
private static final String table_accounts = "ACCOUNTS";
private static final String key_id = "ID";
private static final String key_url = "URL";
private static final String key_account_name = "ACCOUNT_NAME";
private static final String key_username = "USERNAME";
private static final String key_account_id = "ACCOUNT_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 key_excerpt = "EXCERPT";
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 TAG = NotesDatabase.class.getSimpleName();
private static final String[] columnsWithoutContent = {key_id, key_remote_id, key_status, key_title, key_modified, key_favorite, key_category, key_etag, key_excerpt};
private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_favorite, key_category, key_etag, key_excerpt, key_content};
private static final String default_order = key_favorite + " DESC, " + key_modified + " DESC";
private static NoteSQLiteOpenHelper instance;
private static NotesDatabase instance;
private final NoteServerSyncHelper serverSyncHelper;
private final Context context;
private NoteSQLiteOpenHelper(Context context) {
super(context, database_name, null, database_version);
this.context = context;
private NotesDatabase(Context context) {
super(context, database_name, null);
serverSyncHelper = NoteServerSyncHelper.getInstance(this);
}
public static NoteSQLiteOpenHelper getInstance(Context context) {
public static NotesDatabase getInstance(Context context) {
if (instance == null)
return instance = new NoteSQLiteOpenHelper(context);
return instance = new NotesDatabase(context);
else
return instance;
}
@ -108,214 +75,6 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
return serverSyncHelper;
}
/**
* Creates initial the Database
*
* @param db Database
*/
@Override
public void onCreate(SQLiteDatabase db) {
createAccountTable(db);
createNotesTable(db);
}
private void createNotesTable(@NonNull SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + table_notes + " ( " +
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
key_remote_id + " INTEGER, " +
key_account_id + " INTEGER, " +
key_status + " VARCHAR(50), " +
key_title + " TEXT, " +
key_modified + " INTEGER DEFAULT 0, " +
key_content + " TEXT, " +
key_favorite + " INTEGER DEFAULT 0, " +
key_category + " TEXT NOT NULL DEFAULT '', " +
key_etag + " TEXT," +
key_excerpt + " TEXT NOT NULL DEFAULT '', " +
"FOREIGN KEY(" + key_account_id + ") REFERENCES " + table_accounts + "(" + key_id + "))");
createNotesIndexes(db);
}
private void createAccountTable(@NonNull SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + table_accounts + " ( " +
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
key_url + " TEXT, " +
key_username + " TEXT, " +
key_account_name + " TEXT UNIQUE, " +
key_etag + " TEXT, " +
key_modified + " INTEGER)");
createAccountIndexes(db);
}
@SuppressWarnings("deprecation")
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 3) {
recreateDatabase(db);
return;
}
if (oldVersion < 4) {
db.delete(table_notes, null, null);
db.delete(table_accounts, null, null);
}
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()});
}
if (oldVersion < 6) {
db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_favorite + " INTEGER DEFAULT 0");
}
if (oldVersion < 7) {
DatabaseIndexUtil.dropIndexes(db);
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");
DatabaseIndexUtil.createIndex(db, table_notes, key_remote_id, key_status, key_favorite, key_category, key_modified);
}
if (oldVersion < 8) {
final String table_temp = "NOTES_TEMP";
db.execSQL("CREATE TABLE " + table_temp + " ( " +
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
key_remote_id + " INTEGER, " +
key_status + " VARCHAR(50), " +
key_title + " TEXT, " +
key_modified + " INTEGER DEFAULT 0, " +
key_content + " TEXT, " +
key_favorite + " INTEGER DEFAULT 0, " +
key_category + " TEXT NOT NULL DEFAULT '', " +
key_etag + " TEXT)");
DatabaseIndexUtil.createIndex(db, table_temp, key_remote_id, key_status, key_favorite, key_category, key_modified);
db.execSQL(String.format("INSERT INTO %s(%s,%s,%s,%s,%s,%s,%s,%s,%s) ", table_temp, key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag)
+ String.format("SELECT %s,%s,%s,%s,strftime('%%s',%s),%s,%s,%s,%s FROM %s", key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag, table_notes));
db.execSQL(String.format("DROP TABLE %s", table_notes));
db.execSQL(String.format("ALTER TABLE %s RENAME TO %s", table_temp, table_notes));
}
if (oldVersion < 9) {
// Create accounts table
createAccountTable(db);
// Add accountId to notes table
db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_account_id + " INTEGER NOT NULL DEFAULT 0");
DatabaseIndexUtil.createIndex(db, table_notes, key_account_id);
// Migrate existing account from SharedPreferences
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String username = sharedPreferences.getString("settingsUsername", "");
String url = sharedPreferences.getString("settingsUrl", "");
if (!url.isEmpty() && url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
try {
String accountName = username + "@" + new URL(url).getHost();
ContentValues migratedAccountValues = new ContentValues();
migratedAccountValues.put(key_url, url);
migratedAccountValues.put(key_username, username);
migratedAccountValues.put(key_account_name, accountName);
db.insert(table_accounts, null, migratedAccountValues);
// After successful insertion of migrated account, set accountId to 1 in each note
ContentValues values = new ContentValues();
values.put(key_account_id, 1);
db.update(table_notes, values, key_account_id + " = ?", new String[]{"NULL"});
// Add FOREIGN_KEY constraint
final String table_temp = "NOTES_TEMP";
db.execSQL(String.format("ALTER TABLE %s RENAME TO %s", table_notes, table_temp));
db.execSQL("CREATE TABLE " + table_notes + " ( " +
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
key_remote_id + " INTEGER, " +
key_account_id + " INTEGER, " +
key_status + " VARCHAR(50), " +
key_title + " TEXT, " +
key_modified + " INTEGER DEFAULT 0, " +
key_content + " TEXT, " +
key_favorite + " INTEGER DEFAULT 0, " +
key_category + " TEXT NOT NULL DEFAULT '', " +
key_etag + " TEXT," +
"FOREIGN KEY(" + key_account_id + ") REFERENCES " + table_accounts + "(" + key_id + "))");
DatabaseIndexUtil.createIndex(db, table_notes, key_remote_id, key_account_id, key_status, key_favorite, key_category, key_modified);
db.execSQL(String.format("INSERT INTO %s(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) ", table_notes, key_id, key_account_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag)
+ String.format("SELECT %s,%s,%s,%s,%s,%s,%s,%s,%s,%s FROM %s", key_id, values.get(key_account_id), key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag, table_temp));
db.execSQL(String.format("DROP TABLE %s;", table_temp));
AppWidgetManager awm = AppWidgetManager.getInstance(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
// Add accountId '1' to any existing (and configured) appwidgets
int[] appWidgetIdsNLW = awm.getAppWidgetIds(new ComponentName(context, NoteListWidget.class));
int[] appWidgetIdsSNW = awm.getAppWidgetIds(new ComponentName(context, SingleNoteWidget.class));
for (int appWidgetId : appWidgetIdsNLW) {
if (sharedPreferences.getInt(NoteListWidget.WIDGET_MODE_KEY + appWidgetId, -1) >= 0) {
editor.putLong(NoteListWidget.ACCOUNT_ID_KEY + appWidgetId, 1);
}
}
for (int appWidgetId : appWidgetIdsSNW) {
if (sharedPreferences.getLong(SingleNoteWidget.WIDGET_KEY + appWidgetId, -1) >= 0) {
editor.putLong(SingleNoteWidget.ACCOUNT_ID_KEY + appWidgetId, 1);
}
}
notifyNotesChanged();
// Clean up no longer needed SharedPreferences
editor.remove("notes_last_etag");
editor.remove("notes_last_modified");
editor.remove("settingsUrl");
editor.remove("settingsUsername");
editor.remove("settingsPassword");
editor.apply();
} catch (MalformedURLException e) {
Log.e(TAG, "Previous URL could not be parsed. Recreating database...");
e.printStackTrace();
recreateDatabase(db);
return;
}
} else {
Log.e(TAG, "Previous URL is empty or does not end with a '/' character. Recreating database...");
recreateDatabase(db);
return;
}
}
if (oldVersion < 10) {
db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_excerpt + " INTEGER NOT NULL DEFAULT ''");
Cursor cursor = db.query(table_notes, new String[]{key_id, key_content}, null, null, null, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
values.put(key_excerpt, NoteUtil.generateNoteExcerpt(cursor.getString(1)));
db.update(table_notes, values, key_id + " = ? ", new String[]{cursor.getString(0)});
}
cursor.close();
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
recreateDatabase(db);
}
private void recreateDatabase(SQLiteDatabase db) {
DatabaseIndexUtil.dropIndexes(db);
db.execSQL("DROP TABLE IF EXISTS " + table_notes);
db.execSQL("DROP TABLE IF EXISTS " + table_accounts);
onCreate(db);
}
private static void createNotesIndexes(@NonNull SQLiteDatabase db) {
DatabaseIndexUtil.createIndex(db, table_notes, key_remote_id, key_account_id, key_status, key_favorite, key_category, key_modified);
}
private static void createAccountIndexes(@NonNull SQLiteDatabase db) {
DatabaseIndexUtil.createIndex(db, table_accounts, key_url, key_username, key_account_name, key_etag, key_modified);
}
public Context getContext() {
return context;
}
/**
* Creates a new Note in the Database and adds a Synchronization Flag.
*
@ -480,15 +239,6 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
);
}
public void debugPrintFullDB(long accountId) {
validateAccountId(accountId);
List<DBNote> notes = getNotesCustom(accountId, "", new String[]{}, default_order, false);
Log.v(TAG, "Full Database (" + notes.size() + " notes):");
for (DBNote note : notes) {
Log.v(TAG, " " + note);
}
}
@NonNull
@WorkerThread
public Map<Long, Long> getIdMap(long accountId) {
@ -613,7 +363,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
key_category);
List<NavigationAdapter.NavigationItem> categories = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
Resources res = context.getResources();
Resources res = getContext().getResources();
String category = cursor.getString(0).toLowerCase();
int icon = NavigationAdapter.ICON_FOLDER;
if (category.equals(res.getString(R.string.category_music).toLowerCase())) {
@ -645,7 +395,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
key_category);
List<NavigationAdapter.NavigationItem> categories = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
Resources res = context.getResources();
Resources res = getContext().getResources();
String category = cursor.getString(0).toLowerCase();
int icon = NavigationAdapter.ICON_FOLDER;
if (category.equals(res.getString(R.string.category_music).toLowerCase())) {
@ -796,12 +546,12 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
getNoteServerSyncHelper().scheduleSync(ssoAccount, true);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
ShortcutManager shortcutManager = getContext().getSystemService(ShortcutManager.class);
shortcutManager.getPinnedShortcuts().forEach((shortcut) -> {
String shortcutId = id + "";
if (shortcut.getId().equals(shortcutId)) {
Log.v(TAG, "Removing shortcut for " + shortcutId);
shortcutManager.disableShortcuts(Collections.singletonList(shortcutId), context.getResources().getString(R.string.note_has_been_deleted));
shortcutManager.disableShortcuts(Collections.singletonList(shortcutId), getContext().getResources().getString(R.string.note_has_been_deleted));
}
});
}
@ -824,7 +574,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
/**
* Notify about changed notes.
*/
void notifyNotesChanged() {
protected void notifyNotesChanged() {
updateSingleNoteWidgets(getContext());
updateNoteListWidgets(getContext());
}
@ -832,19 +582,19 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
void updateDynamicShortcuts(long accountId) {
new Thread(() -> {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) {
ShortcutManager shortcutManager = context.getApplicationContext().getSystemService(ShortcutManager.class);
ShortcutManager shortcutManager = getContext().getApplicationContext().getSystemService(ShortcutManager.class);
if (!shortcutManager.isRateLimitingActive()) {
List<ShortcutInfo> newShortcuts = new ArrayList<>();
for (DBNote note : getRecentNotes(accountId)) {
if (!TextUtils.isEmpty(note.getTitle())) {
Intent intent = new Intent(context.getApplicationContext(), EditNoteActivity.class);
Intent intent = new Intent(getContext().getApplicationContext(), EditNoteActivity.class);
intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId());
intent.setAction(ACTION_SHORTCUT);
newShortcuts.add(new ShortcutInfo.Builder(context.getApplicationContext(), note.getId() + "")
newShortcuts.add(new ShortcutInfo.Builder(getContext().getApplicationContext(), note.getId() + "")
.setShortLabel(note.getTitle() + "")
.setIcon(Icon.createWithResource(context.getApplicationContext(), note.isFavorite() ? R.drawable.ic_star_yellow_24dp : R.drawable.ic_star_grey_ccc_24dp))
.setIcon(Icon.createWithResource(getContext().getApplicationContext(), note.isFavorite() ? R.drawable.ic_star_yellow_24dp : R.drawable.ic_star_grey_ccc_24dp))
.setIntent(intent)
.build());
} else {

View file

@ -37,7 +37,7 @@ public class SyncWorker extends Worker {
@NonNull
@Override
public Result doWork() {
NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(getApplicationContext());
NotesDatabase db = NotesDatabase.getInstance(getApplicationContext());
for (LocalAccount account : db.getAccounts()) {
try {
SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(getApplicationContext(), account.getAccountName());

View file

@ -0,0 +1,5 @@
- Split up NoteSQLiteOpenHelper (#750)
Requires at least Files app¹ version 3.9.0
¹ https://github.com/nextcloud/android