From 90b48bdc8cb1fde018aa2707d6d1047319ff6b5e Mon Sep 17 00:00:00 2001 From: Adrian Ulrich Date: Sat, 4 Feb 2017 09:47:18 +0100 Subject: [PATCH] implement scanner preferences --- res/layout/medialibrary_preferences.xml | 13 ++ res/values/translatable.xml | 7 +- .../android/medialibrary/MediaLibrary.java | 54 +++++++ .../android/medialibrary/MediaScanner.java | 38 ++--- .../android/medialibrary/MediaSchema.java | 3 +- .../vanilla/PreferencesMediaLibrary.java | 134 +++++++++++++++--- 6 files changed, 199 insertions(+), 50 deletions(-) diff --git a/res/layout/medialibrary_preferences.xml b/res/layout/medialibrary_preferences.xml index ef7bd97a..1ba40d93 100644 --- a/res/layout/medialibrary_preferences.xml +++ b/res/layout/medialibrary_preferences.xml @@ -23,6 +23,19 @@ along with this program. If not, see . android:paddingTop="5dp" android:orientation="vertical" > + + + + Scan full filesystem (Slow) Flush media database (Dangerzone!) Start scan + Always use builtin tagreader + Group albums by folder Statistics - Number of tracks Play time (Hours) Scan progress - Scanning media library… + Scanner options + Scanner options changed + Changing this option will start a full rescan of your library. Would you like to continue? Plugins diff --git a/src/ch/blinkenlights/android/medialibrary/MediaLibrary.java b/src/ch/blinkenlights/android/medialibrary/MediaLibrary.java index 4ba3620d..fd75fe1c 100644 --- a/src/ch/blinkenlights/android/medialibrary/MediaLibrary.java +++ b/src/ch/blinkenlights/android/medialibrary/MediaLibrary.java @@ -46,6 +46,24 @@ public class MediaLibrary { public static final int ROLE_ARTIST = 0; public static final int ROLE_COMPOSER = 1; + private static final String PREF_KEY_FORCE_BASTP = "force_bastp"; + private static final String PREF_KEY_GROUP_ALBUMS = "group_albums"; + private static final String PREF_KEY_NATIVE_LIBRARY_COUNT = "native_audio_db_count"; + private static final String PREF_KEY_NATIVE_LAST_MTIME = "native_last_mtime"; + + /** + * Options used by the MediaScanner class + */ + public static class Preferences { + public boolean forceBastp; + public boolean groupAlbumsByFolder; + int _nativeLibraryCount; + int _nativeLastMtime; + } + /** + * Cached preferences, may be null + */ + private static Preferences sPreferences; /** * Our static backend instance */ @@ -77,6 +95,42 @@ public class MediaLibrary { return sBackend; } + /** + * Returns the scanner preferences + * + * @param context the context to use + * @return MediaLibrary.Preferences + */ + public static MediaLibrary.Preferences getPreferences(Context context) { + MediaLibrary.Preferences prefs = sPreferences; + if (prefs == null) { + MediaLibraryBackend backend = getBackend(context); + prefs = new MediaLibrary.Preferences(); + prefs.forceBastp = backend.getSetPreference(PREF_KEY_FORCE_BASTP, 0) != 0; + prefs.groupAlbumsByFolder = backend.getSetPreference(PREF_KEY_GROUP_ALBUMS, 1) != 0; + prefs._nativeLibraryCount = backend.getSetPreference(PREF_KEY_NATIVE_LIBRARY_COUNT, 0); + prefs._nativeLastMtime = backend.getSetPreference(PREF_KEY_NATIVE_LAST_MTIME, 0); + sPreferences = prefs; // cached for frequent access + } + return prefs; + } + + /** + * Updates the scanner preferences + * + * @param context the context to use + * @param prefs the preferences to store - this will update ALL fields, so you are + * supposed to first call getPreferences() to obtain the current values + */ + public static void setPreferences(Context context, MediaLibrary.Preferences prefs) { + MediaLibraryBackend backend = getBackend(context); + backend.getSetPreference(PREF_KEY_FORCE_BASTP, prefs.forceBastp ? 1 : 0); + backend.getSetPreference(PREF_KEY_GROUP_ALBUMS, prefs.groupAlbumsByFolder ? 1 : 0); + backend.getSetPreference(PREF_KEY_NATIVE_LIBRARY_COUNT, prefs._nativeLibraryCount); + backend.getSetPreference(PREF_KEY_NATIVE_LAST_MTIME, prefs._nativeLastMtime); + sPreferences = null; + } + /** * Triggers a rescan of the library * diff --git a/src/ch/blinkenlights/android/medialibrary/MediaScanner.java b/src/ch/blinkenlights/android/medialibrary/MediaScanner.java index 64821db2..f609207c 100644 --- a/src/ch/blinkenlights/android/medialibrary/MediaScanner.java +++ b/src/ch/blinkenlights/android/medialibrary/MediaScanner.java @@ -69,14 +69,6 @@ public class MediaScanner implements Handler.Callback { * The id we are using for the scan notification */ private static final int NOTIFICATION_ID = 56162; - /** - * The preference key to store the last mtime - */ - private static final String PREF_KEY_MTIME = "native_last_mtime"; - /** - * The preference key to store the native audio size - */ - private static final String PREF_KEY_DBCOUNT = "native_audio_db_count"; MediaScanner(Context context, MediaLibraryBackend backend) { mContext = context; @@ -145,7 +137,10 @@ public class MediaScanner implements Handler.Callback { public void flushDatabase() { mBackend.delete(MediaLibrary.TABLE_SONGS, null, null); mBackend.cleanOrphanedEntries(false); // -> keep playlists - getSetPreference(PREF_KEY_MTIME, 0); + + MediaLibrary.Preferences prefs = MediaLibrary.getPreferences(mContext); + prefs._nativeLastMtime = 0; + MediaLibrary.setPreferences(mContext, prefs); } /** @@ -190,7 +185,7 @@ public class MediaScanner implements Handler.Callback { } case RPC_KICKSTART: { // a new scan was triggered: check if this is a 'initial / from scratch' scan - if (!mIsInitialScan && getSetPreference(PREF_KEY_MTIME, -1) == 0) { + if (!mIsInitialScan && MediaLibrary.getPreferences(mContext)._nativeLastMtime == 0) { mIsInitialScan = true; } break; @@ -270,7 +265,7 @@ public class MediaScanner implements Handler.Callback { * check for deleted or new/modified items */ private void guessQuickScanPlan() { - int lastSeenDbSize = getSetPreference(PREF_KEY_DBCOUNT, -1); + int lastSeenDbSize = MediaLibrary.getPreferences(mContext)._nativeLibraryCount; String selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0"; String[] projection = { "COUNT(*)" }; Cursor cursor = null; @@ -288,7 +283,9 @@ public class MediaScanner implements Handler.Callback { cursor.close(); // Store new db size - getSetPreference(PREF_KEY_DBCOUNT, currentDbSize); + MediaLibrary.Preferences prefs = MediaLibrary.getPreferences(mContext); + prefs._nativeLibraryCount = currentDbSize; + MediaLibrary.setPreferences(mContext, prefs); if (currentDbSize < lastSeenDbSize) { // db is smaller! check for deleted files @@ -309,7 +306,7 @@ public class MediaScanner implements Handler.Callback { */ private void rpcNativeVerify(Cursor cursor, int mtime) { if (cursor == null) { - mtime = getSetPreference(PREF_KEY_MTIME, -1); // starting a new scan -> read stored mtime from preferences + mtime = MediaLibrary.getPreferences(mContext)._nativeLastMtime; // starting a new scan -> read stored mtime from preferences String selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0 AND "+ MediaStore.MediaColumns.DATE_MODIFIED +" > " + mtime; String sort = MediaStore.MediaColumns.DATE_MODIFIED; String[] projection = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DATE_MODIFIED }; @@ -333,7 +330,9 @@ public class MediaScanner implements Handler.Callback { } } else { cursor.close(); - getSetPreference(PREF_KEY_MTIME, mtime); + MediaLibrary.Preferences prefs = MediaLibrary.getPreferences(mContext); + prefs._nativeLastMtime = mtime; + MediaLibrary.setPreferences(mContext, prefs); Log.v("VanillaMusic", "NativeLibraryScanner finished, mtime mark is now at "+mtime); } } @@ -527,17 +526,6 @@ public class MediaScanner implements Handler.Callback { } - /** - * Clunky shortcut to preferences editor - * - * @param newVal the new value to store, ignored if < 0 - * @return the value previously set, or 0 as a default - */ - private int getSetPreference(String prefKey, int newVal) { - return mBackend.getSetPreference(prefKey, newVal); - } - - // MediaScanPlan describes how we are going to perform the media scan class MediaScanPlan { class Step { diff --git a/src/ch/blinkenlights/android/medialibrary/MediaSchema.java b/src/ch/blinkenlights/android/medialibrary/MediaSchema.java index 9aeb074a..d7dbc321 100644 --- a/src/ch/blinkenlights/android/medialibrary/MediaSchema.java +++ b/src/ch/blinkenlights/android/medialibrary/MediaSchema.java @@ -244,7 +244,8 @@ public class MediaSchema { */ private static void triggerFullMediaScan(SQLiteDatabase dbh) { dbh.execSQL("UPDATE "+MediaLibrary.TABLE_SONGS+" SET "+MediaLibrary.SongColumns.MTIME+"=0"); - dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_PREFERENCES); + // wipes non-bools only - not nice but good enough for now + dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_PREFERENCES+" WHERE "+MediaLibrary.PreferenceColumns.VALUE+" < 2"); } } diff --git a/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java b/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java index 18f6dcc4..1c28e04e 100644 --- a/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java +++ b/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java @@ -1,24 +1,28 @@ /* - * Copyright (C) 2016 Xiao Bao Clark - * Copyright (C) 2016 Adrian Ulrich + * Copyright (C) 2017 Adrian Ulrich * - * This program is free software; you can redistribute it and/or modify + * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package ch.blinkenlights.android.vanilla; import ch.blinkenlights.android.medialibrary.MediaLibrary; +import android.app.AlertDialog; import android.app.Fragment; import android.content.Context; +import android.content.DialogInterface; import android.database.Cursor; import android.os.Bundle; import android.view.LayoutInflater; @@ -29,9 +33,8 @@ import android.widget.TextView; import java.util.Timer; import java.util.TimerTask; -import android.util.Log; -public class PreferencesMediaLibrary extends Fragment +public class PreferencesMediaLibrary extends Fragment implements View.OnClickListener { /** * The ugly timer which fires every 200ms @@ -65,6 +68,18 @@ public class PreferencesMediaLibrary extends Fragment * Checkbox for drop */ private CheckBox mDropDbCheck; + /** + * Checkbox for album group option + */ + private CheckBox mGroupAlbumsCheck; + /** + * Checkbox for targreader flavor + */ + private CheckBox mForceBastpCheck; + /** + * Set if we should start a full scan due to option changes + */ + private boolean mFullScanPending; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -82,20 +97,14 @@ public class PreferencesMediaLibrary extends Fragment mStatsPlaytime = (TextView)view.findViewById(R.id.media_stats_playtime); mFullScanCheck = (CheckBox)view.findViewById(R.id.media_scan_full); mDropDbCheck = (CheckBox)view.findViewById(R.id.media_scan_drop_db); + mGroupAlbumsCheck = (CheckBox)view.findViewById(R.id.media_scan_group_albums); + mForceBastpCheck = (CheckBox)view.findViewById(R.id.media_scan_force_bastp); - mStartButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startButtonPressed(v); - } - }); - - mCancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - cancelButtonPressed(v); - } - }); + // Bind onClickListener to some elements + mStartButton.setOnClickListener(this); + mCancelButton.setOnClickListener(this); + mGroupAlbumsCheck.setOnClickListener(this); + mForceBastpCheck.setOnClickListener(this); } @Override @@ -113,6 +122,8 @@ public class PreferencesMediaLibrary extends Fragment } }); }}), 0, 200); + + updatePreferences(null); } @Override @@ -122,6 +133,79 @@ public class PreferencesMediaLibrary extends Fragment mTimer.cancel(); mTimer = null; } + + if (mFullScanPending) { + MediaLibrary.startLibraryScan(getActivity(), true, true); + mFullScanPending = false; + } + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.start_button: + startButtonPressed(view); + break; + case R.id.cancel_button: + cancelButtonPressed(view); + break; + case R.id.media_scan_group_albums: + case R.id.media_scan_force_bastp: + confirmUpdatePreferences((CheckBox)view); + break; + } + } + + /** + * Wrapper for updatePreferences() which warns the user about + * possible consequences. + * + * @param checkbox the checkbox which was changed + */ + private void confirmUpdatePreferences(final CheckBox checkbox) { + if (mFullScanPending) { + // User was already warned, so we can just dispatch this + // without nagging again + updatePreferences(checkbox); + return; + } + + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.media_scan_preferences_change_title) + .setMessage(R.string.media_scan_preferences_change_message) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + mFullScanPending = true; + updatePreferences(checkbox); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // restore old condition if use does not want to proceed + checkbox.setChecked(!checkbox.isChecked()); + } + }) + .show(); + } + + /** + * Initializes and updates the scanner preferences + * + * @param checkbox the item to update, may be null + * @return void but sets the checkboxes to their correct state + */ + private void updatePreferences(CheckBox checkbox) { + MediaLibrary.Preferences prefs = MediaLibrary.getPreferences(getActivity()); + + if (checkbox == mGroupAlbumsCheck) + prefs.groupAlbumsByFolder = mGroupAlbumsCheck.isChecked(); + if (checkbox == mForceBastpCheck) + prefs.forceBastp = mForceBastpCheck.isChecked(); + + MediaLibrary.setPreferences(getActivity(), prefs); + + mGroupAlbumsCheck.setChecked(prefs.groupAlbumsByFolder); + mForceBastpCheck.setChecked(prefs.forceBastp); } /** @@ -130,9 +214,15 @@ public class PreferencesMediaLibrary extends Fragment private void updateProgress() { Context context = getActivity(); String scanText = MediaLibrary.describeScanProgress(getActivity()); + boolean scanIdle = scanText == null; + mProgress.setText(scanText); - mStartButton.setEnabled(scanText == null); - mCancelButton.setVisibility(scanText == null ? View.GONE : View.VISIBLE); + mStartButton.setEnabled(scanIdle); + mDropDbCheck.setEnabled(scanIdle); + mFullScanCheck.setEnabled(scanIdle); + mForceBastpCheck.setEnabled(scanIdle); + mGroupAlbumsCheck.setEnabled(scanIdle); + mCancelButton.setVisibility(scanIdle ? View.GONE : View.VISIBLE); Integer songCount = MediaLibrary.getLibrarySize(context); mStatsTracks.setText(songCount.toString());