import old playlists
This commit is contained in:
parent
2a562230da
commit
d26d10570b
@ -313,4 +313,7 @@ THE SOFTWARE.
|
|||||||
<string name="media_stats_tracks">Number of tracks</string>
|
<string name="media_stats_tracks">Number of tracks</string>
|
||||||
<string name="media_stats_playtime">Play time (Hours)</string>
|
<string name="media_stats_playtime">Play time (Hours)</string>
|
||||||
<string name="media_stats_progress">Scan progress</string>
|
<string name="media_stats_progress">Scan progress</string>
|
||||||
|
|
||||||
|
<string name="media_library_import_started">Scanning your media library, this might take some time</string>
|
||||||
|
<string name="media_library_import_ended">Media library scan finished!</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package ch.blinkenlights.android.medialibrary;
|
package ch.blinkenlights.android.medialibrary;
|
||||||
|
|
||||||
|
import ch.blinkenlights.android.vanilla.R;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@ -29,6 +31,8 @@ import android.os.HandlerThread;
|
|||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -50,6 +54,11 @@ public class MediaScanner implements Handler.Callback {
|
|||||||
* Instance of a media backend
|
* Instance of a media backend
|
||||||
*/
|
*/
|
||||||
private MediaLibraryBackend mBackend;
|
private MediaLibraryBackend mBackend;
|
||||||
|
/**
|
||||||
|
* True if this is a from-scratch import
|
||||||
|
* Set by KICKSTART rpc
|
||||||
|
*/
|
||||||
|
private boolean mIsInitialScan;
|
||||||
|
|
||||||
|
|
||||||
MediaScanner(Context context, MediaLibraryBackend backend) {
|
MediaScanner(Context context, MediaLibraryBackend backend) {
|
||||||
@ -77,7 +86,7 @@ public class MediaScanner implements Handler.Callback {
|
|||||||
public void startNormalScan() {
|
public void startNormalScan() {
|
||||||
mScanPlan.addNextStep(RPC_NATIVE_VRFY, null)
|
mScanPlan.addNextStep(RPC_NATIVE_VRFY, null)
|
||||||
.addNextStep(RPC_LIBRARY_VRFY, null);
|
.addNextStep(RPC_LIBRARY_VRFY, null);
|
||||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_SCAN_RPC, RPC_NOOP, 0));
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_SCAN_RPC, RPC_KICKSTART, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,7 +98,7 @@ public class MediaScanner implements Handler.Callback {
|
|||||||
}
|
}
|
||||||
mScanPlan.addNextStep(RPC_LIBRARY_VRFY, null);
|
mScanPlan.addNextStep(RPC_LIBRARY_VRFY, null);
|
||||||
mScanPlan.addNextStep(RPC_NATIVE_VRFY, null);
|
mScanPlan.addNextStep(RPC_NATIVE_VRFY, null);
|
||||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_SCAN_RPC, RPC_NOOP, 0));
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_SCAN_RPC, RPC_KICKSTART, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,7 +111,7 @@ public class MediaScanner implements Handler.Callback {
|
|||||||
if (!mHandler.hasMessages(MSG_SCAN_RPC)) {
|
if (!mHandler.hasMessages(MSG_SCAN_RPC)) {
|
||||||
mScanPlan.addNextStep(RPC_NATIVE_VRFY, null)
|
mScanPlan.addNextStep(RPC_NATIVE_VRFY, null)
|
||||||
.addOptionalStep(RPC_LIBRARY_VRFY, null); // only runs if previous scan found no change
|
.addOptionalStep(RPC_LIBRARY_VRFY, null); // only runs if previous scan found no change
|
||||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCAN_RPC, RPC_NOOP, 0), delay);
|
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCAN_RPC, RPC_KICKSTART, 0), delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,8 +134,9 @@ public class MediaScanner implements Handler.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final int MSG_SCAN_RPC = 0;
|
private static final int MSG_SCAN_RPC = 0;
|
||||||
private static final int MSG_NOTIFY_CHANGE = 1;
|
private static final int MSG_SCAN_FINISHED = 1;
|
||||||
private static final int RPC_NOOP = 100;
|
private static final int MSG_NOTIFY_CHANGE = 2;
|
||||||
|
private static final int RPC_KICKSTART = 100;
|
||||||
private static final int RPC_READ_DIR = 101;
|
private static final int RPC_READ_DIR = 101;
|
||||||
private static final int RPC_INSPECT_FILE = 102;
|
private static final int RPC_INSPECT_FILE = 102;
|
||||||
private static final int RPC_LIBRARY_VRFY = 103;
|
private static final int RPC_LIBRARY_VRFY = 103;
|
||||||
@ -141,8 +151,20 @@ public class MediaScanner implements Handler.Callback {
|
|||||||
MediaLibrary.notifyObserver();
|
MediaLibrary.notifyObserver();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RPC_NOOP: {
|
case MSG_SCAN_FINISHED: {
|
||||||
// just used to trigger the initial scan
|
if (mIsInitialScan) {
|
||||||
|
mIsInitialScan = false;
|
||||||
|
PlaylistBridge.importAndroidPlaylists(mContext);
|
||||||
|
toastMsg(R.string.media_library_import_ended);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RPC_KICKSTART: {
|
||||||
|
// a new scan was triggered: check if this is a 'initial / from scratch' scan
|
||||||
|
if (!mIsInitialScan && getSetScanMark(-1) == 0) {
|
||||||
|
mIsInitialScan = true;
|
||||||
|
toastMsg(R.string.media_library_import_started);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RPC_INSPECT_FILE: {
|
case RPC_INSPECT_FILE: {
|
||||||
@ -174,7 +196,7 @@ public class MediaScanner implements Handler.Callback {
|
|||||||
if (message.what == MSG_SCAN_RPC && !mHandler.hasMessages(MSG_SCAN_RPC)) {
|
if (message.what == MSG_SCAN_RPC && !mHandler.hasMessages(MSG_SCAN_RPC)) {
|
||||||
MediaScanPlan.Step step = mScanPlan.getNextStep();
|
MediaScanPlan.Step step = mScanPlan.getNextStep();
|
||||||
if (step == null) {
|
if (step == null) {
|
||||||
Log.v("VanillaMusic", "--- all scanners finished ---");
|
mHandler.sendEmptyMessage(MSG_SCAN_FINISHED);
|
||||||
} else {
|
} else {
|
||||||
Log.v("VanillaMusic", "--- starting scan of type "+step.msg);
|
Log.v("VanillaMusic", "--- starting scan of type "+step.msg);
|
||||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_SCAN_RPC, step.msg, 0, step.arg));
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_SCAN_RPC, step.msg, 0, step.arg));
|
||||||
@ -423,6 +445,17 @@ public class MediaScanner implements Handler.Callback {
|
|||||||
return oldVal;
|
return oldVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a toast message
|
||||||
|
* Not sure if this should really be here - a callback to the
|
||||||
|
* observer would probably be nicer
|
||||||
|
*
|
||||||
|
* @param id the message id to display
|
||||||
|
*/
|
||||||
|
private void toastMsg(int resId) {
|
||||||
|
Toast.makeText(mContext, resId, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
// MediaScanPlan describes how we are going to perform the media scan
|
// MediaScanPlan describes how we are going to perform the media scan
|
||||||
class MediaScanPlan {
|
class MediaScanPlan {
|
||||||
class Step {
|
class Step {
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||||
|
*
|
||||||
|
* 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 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
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.blinkenlights.android.medialibrary;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.MediaStore.Audio;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
class PlaylistBridge {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries all native playlists and imports them
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
*/
|
||||||
|
static void importAndroidPlaylists(Context context) {
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
Cursor cursor = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cursor = resolver.query(Audio.Playlists.EXTERNAL_CONTENT_URI, new String[]{Audio.Playlists._ID, Audio.Playlists.NAME}, null, null, null);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.v("VanillaMusic", "Unable to query existing playlists, exception: "+e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
long playlistId = cursor.getLong(0);
|
||||||
|
String playlistName = cursor.getString(1);
|
||||||
|
importAndroidPlaylist(context, playlistName, playlistId);
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports a single native playlist into our own media library
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
* @param targetName the name of the playlist in our media store
|
||||||
|
* @param playlistId the native playlist id to import
|
||||||
|
*/
|
||||||
|
static void importAndroidPlaylist(Context context, String targetName, long playlistId) {
|
||||||
|
ArrayList<Long> bulkIds = new ArrayList<>();
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
Uri uri = Audio.Playlists.Members.getContentUri("external", playlistId);
|
||||||
|
Cursor cursor = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cursor = resolver.query(uri, new String[]{Audio.Media.DATA}, null, null, Audio.Playlists.Members.DEFAULT_SORT_ORDER);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.v("VanillaMusic", "Failed to query playlist: "+e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
String path = cursor.getString(0);
|
||||||
|
// We do not need to do a lookup by path as we can calculate the id used
|
||||||
|
// by the mediastore using the path
|
||||||
|
bulkIds.add(MediaLibrary.hash63(path));
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bulkIds.size() == 0)
|
||||||
|
return; // do not import empty playlists
|
||||||
|
|
||||||
|
long targetPlaylistId = MediaLibrary.createPlaylist(context, targetName);
|
||||||
|
if (targetPlaylistId == -1)
|
||||||
|
return; // already exists, won't touch
|
||||||
|
|
||||||
|
MediaLibrary.addToPlaylist(context, targetPlaylistId, bulkIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user