diff --git a/res/layout/medialibrary_preferences.xml b/res/layout/medialibrary_preferences.xml
new file mode 100644
index 00000000..d1e198d3
--- /dev/null
+++ b/res/layout/medialibrary_preferences.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/sdscanner_fragment.xml b/res/layout/sdscanner_fragment.xml
deleted file mode 100644
index ffeedb32..00000000
--- a/res/layout/sdscanner_fragment.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/res/values/translatable.xml b/res/values/translatable.xml
index 9cca7ee9..402d0433 100644
--- a/res/values/translatable.xml
+++ b/res/values/translatable.xml
@@ -303,21 +303,14 @@ THE SOFTWARE.
Send to…No receiving apps found for this media type!
-
- Starting a rescan causes Vanilla Music to trigger a full rebuild of the media database.
- Start Rescan
- Examined
- Removed reference to
- Will also check existing media database for updated or deleted files.
- Encountered error reading media database, and might miss updated or deleted files or rescan up-to-date files.
- Encountered error reading media database, but recovered.
- Encountered error reading media database. Retrying in 1 second...
- Processed
- Completed, ready to start another scan.
- Scan failed: bad path specified for new file search.
- Preparing initial list of files...
- Querying database...
- Not yet started.
- Encountered an error and skipping
- SD Scanner
+ Media library
+ Media scanner
+ Scan full filesystem (Slow)
+ Flush media database (Dangerzone!)
+ Start scan
+ Statistics
+
+ Number of songs
+ Play time (Hours)
+ Scan progress
diff --git a/res/xml/preference_headers.xml b/res/xml/preference_headers.xml
index 341ee025..a8119062 100644
--- a/res/xml/preference_headers.xml
+++ b/res/xml/preference_headers.xml
@@ -24,6 +24,9 @@ THE SOFTWARE.
+
diff --git a/res/xml/preference_misc.xml b/res/xml/preference_misc.xml
index 436fb0c3..e2f09765 100644
--- a/res/xml/preference_misc.xml
+++ b/res/xml/preference_misc.xml
@@ -24,9 +24,6 @@ THE SOFTWARE.
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:vanilla="http://schemas.android.com/apk/res/ch.blinkenlights.android.vanilla"
android:persistent="true">
- 0) {
- getBackend(context).cleanOrphanedEntries();
+ getBackend(context).cleanOrphanedEntries(true);
notifyObserver();
}
return rows;
diff --git a/src/ch/blinkenlights/android/medialibrary/MediaLibraryBackend.java b/src/ch/blinkenlights/android/medialibrary/MediaLibraryBackend.java
index 08427977..75440ad8 100644
--- a/src/ch/blinkenlights/android/medialibrary/MediaLibraryBackend.java
+++ b/src/ch/blinkenlights/android/medialibrary/MediaLibraryBackend.java
@@ -151,15 +151,20 @@ public class MediaLibraryBackend extends SQLiteOpenHelper {
/**
* Purges orphaned entries from the media library
+ *
+ * @param purgeUserData also delete user data, such as playlists if true
*/
- void cleanOrphanedEntries() {
+ void cleanOrphanedEntries(boolean purgeUserData) {
SQLiteDatabase dbh = getWritableDatabase();
dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_ALBUMS+" WHERE "+MediaLibrary.AlbumColumns._ID+" NOT IN (SELECT "+MediaLibrary.SongColumns.ALBUM_ID+" FROM "+MediaLibrary.TABLE_SONGS+");");
dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_GENRES_SONGS+" WHERE "+MediaLibrary.GenreSongColumns.SONG_ID+" NOT IN (SELECT "+MediaLibrary.SongColumns._ID+" FROM "+MediaLibrary.TABLE_SONGS+");");
dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_GENRES+" WHERE "+MediaLibrary.GenreColumns._ID+" NOT IN (SELECT "+MediaLibrary.GenreSongColumns._GENRE_ID+" FROM "+MediaLibrary.TABLE_GENRES_SONGS+");");
dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_CONTRIBUTORS_SONGS+" WHERE "+MediaLibrary.ContributorSongColumns.SONG_ID+" NOT IN (SELECT "+MediaLibrary.SongColumns._ID+" FROM "+MediaLibrary.TABLE_SONGS+");");
dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_CONTRIBUTORS+" WHERE "+MediaLibrary.ContributorColumns._ID+" NOT IN (SELECT "+MediaLibrary.ContributorSongColumns._CONTRIBUTOR_ID+" FROM "+MediaLibrary.TABLE_CONTRIBUTORS_SONGS+");");
- dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_PLAYLISTS_SONGS+" WHERE "+MediaLibrary.PlaylistSongColumns.SONG_ID+" NOT IN (SELECT "+MediaLibrary.SongColumns._ID+" FROM "+MediaLibrary.TABLE_SONGS+");");
+
+ if (purgeUserData) {
+ dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_PLAYLISTS_SONGS+" WHERE "+MediaLibrary.PlaylistSongColumns.SONG_ID+" NOT IN (SELECT "+MediaLibrary.SongColumns._ID+" FROM "+MediaLibrary.TABLE_SONGS+");");
+ }
}
/**
diff --git a/src/ch/blinkenlights/android/medialibrary/MediaScanner.java b/src/ch/blinkenlights/android/medialibrary/MediaScanner.java
index e3a6e632..29a1d58a 100644
--- a/src/ch/blinkenlights/android/medialibrary/MediaScanner.java
+++ b/src/ch/blinkenlights/android/medialibrary/MediaScanner.java
@@ -77,6 +77,7 @@ public class MediaScanner implements Handler.Callback {
public void startNormalScan() {
mScanPlan.addNextStep(RPC_NATIVE_VRFY, null)
.addNextStep(RPC_LIBRARY_VRFY, null);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SCAN_RPC, RPC_NOOP, 0));
}
/**
@@ -103,6 +104,15 @@ public class MediaScanner implements Handler.Callback {
}
}
+ /**
+ * Drops the media library
+ */
+ public void flushDatabase() {
+ mBackend.delete(MediaLibrary.TABLE_SONGS, null, null);
+ mBackend.cleanOrphanedEntries(false); // -> keep playlists
+ getSetScanMark(0);
+ }
+
/**
* Returns some scan statistics
*
@@ -373,7 +383,7 @@ public class MediaScanner implements Handler.Callback {
if (needsCleanup)
- mBackend.cleanOrphanedEntries();
+ mBackend.cleanOrphanedEntries(true);
Log.v("VanillaMusic", "MediaScanner: inserted "+path);
return (needsInsert || needsCleanup);
diff --git a/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java b/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java
new file mode 100644
index 00000000..c68d0318
--- /dev/null
+++ b/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 Xiao Bao Clark
+ * Copyright (C) 2016 Adrian Ulrich
+ *
+ * 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
+ * (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
+ * GNU General Public License for more details.
+ */
+
+package ch.blinkenlights.android.vanilla;
+
+import ch.blinkenlights.android.medialibrary.MediaLibrary;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import android.util.Log;
+
+public class PreferencesMediaLibrary extends Fragment
+{
+ /**
+ * The ugly timer which fires every 200ms
+ */
+ private Timer mTimer;
+ /**
+ * Our start button
+ */
+ private View mStartButton;
+ /**
+ * The debug / progress text describing the scan status
+ */
+ private TextView mProgress;
+ /**
+ * The number of songs on this device
+ */;
+ private TextView mStatsSongs;
+ /**
+ * The number of hours of music we have
+ */
+ private TextView mStatsPlaytime;
+ /**
+ * Checkbox for full scan
+ */
+ private CheckBox mFullScanCheck;
+ /**
+ * Checkbox for drop
+ */
+ private CheckBox mDropDbCheck;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.medialibrary_preferences, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mStartButton = (View)view.findViewById(R.id.start_button);
+ mProgress = (TextView)view.findViewById(R.id.media_stats_progress);
+ mStatsSongs = (TextView)view.findViewById(R.id.media_stats_songs);
+ 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);
+
+ mStartButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startButtonPressed(v);
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mTimer = new Timer();
+ // Yep: its as ugly as it seems: we are POLLING
+ // the database.
+ mTimer.scheduleAtFixedRate((new TimerTask() {
+ @Override
+ public void run() {
+ getActivity().runOnUiThread(new Runnable(){
+ public void run() {
+ updateProgress();
+ }
+ });
+ }}), 0, 200);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mTimer != null) {
+ mTimer.cancel();
+ mTimer = null;
+ }
+ }
+
+ /**
+ * Updates the view of this fragment with current information
+ */
+ private void updateProgress() {
+ Context context = getActivity();
+ String scanText = MediaLibrary.describeScanProgress(getActivity());
+ mProgress.setText(scanText);
+ mStartButton.setEnabled(scanText == null);
+
+ Integer songCount = MediaLibrary.getLibrarySize(context);
+ mStatsSongs.setText(songCount.toString());
+
+ Float playtime = calculateDuration(context) / 3600000F;
+ mStatsPlaytime.setText(playtime.toString());
+ }
+
+ /**
+ * Queries the media library and calculates the total amount of playtime in ms
+ *
+ * @param context the context to use
+ * @return the play time of the library in ms
+ */
+ public int calculateDuration(Context context) {
+ int duration = 0;
+ Cursor cursor = MediaLibrary.queryLibrary(context, MediaLibrary.TABLE_SONGS, new String[]{"SUM("+MediaLibrary.SongColumns.DURATION+")"}, null, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ duration = cursor.getInt(0);
+ }
+ cursor.close();
+ }
+ return duration;
+ }
+
+ /**
+ * Called when the user hits the start button
+ *
+ * @param view the view which was pressed
+ */
+ public void startButtonPressed(View view) {
+ MediaLibrary.scanLibrary(getActivity(), mFullScanCheck.isChecked(), mDropDbCheck.isChecked());
+ updateProgress();
+ }
+
+}
diff --git a/src/ch/blinkenlights/android/vanilla/SDScannerFragment.java b/src/ch/blinkenlights/android/vanilla/SDScannerFragment.java
deleted file mode 100644
index 0f0e2d1e..00000000
--- a/src/ch/blinkenlights/android/vanilla/SDScannerFragment.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2016 Xiao Bao Clark
- * Copyright (C) 2016 Adrian Ulrich
- *
- * 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
- * (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
- * GNU General Public License for more details.
- */
-
-package ch.blinkenlights.android.vanilla;
-
-import ch.blinkenlights.android.medialibrary.MediaLibrary;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import java.util.Timer;
-import java.util.TimerTask;
-import android.util.Log;
-
-public class SDScannerFragment extends Fragment
-{
- private Timer mTimer;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- return inflater.inflate(R.layout.sdscanner_fragment, container, false);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- view.findViewById(R.id.start_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startButtonPressed(v);
- }
- });
- }
-
- @Override
- public void onResume() {
- super.onResume();
- Log.v("VanillaMusic", "onResume! "+mTimer);
-
- mTimer = new Timer();
- mTimer.scheduleAtFixedRate((new TimerTask() {
- @Override
- public void run() {
- getActivity().runOnUiThread(new Runnable(){
- public void run() {
- updateProgress();
- }
- });
- }}), 0, 120);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- Log.v("VanillaMusic", "onPause "+mTimer);
- if (mTimer != null) {
- mTimer.cancel();
- mTimer = null;
- }
- }
-
- private void updateProgress() {
- View button = getActivity().findViewById(R.id.start_button);
- TextView progress = (TextView)getActivity().findViewById(R.id.progress_label);
- String scanText = MediaLibrary.describeScanProgress(getActivity());
- progress.setText(scanText);
- button.setEnabled(scanText == null);
- }
-
- public void startButtonPressed(View view) {
- MediaLibrary.scanLibrary(getActivity(), true, false);
- updateProgress();
- }
-
-}