diff options
author | Scott Jackson <daneren2005@gmail.com> | 2013-06-14 20:51:05 -0700 |
---|---|---|
committer | Scott Jackson <daneren2005@gmail.com> | 2013-06-14 20:51:05 -0700 |
commit | 9838cfeff21519445c654f69c545037265d837b9 (patch) | |
tree | 9f528e7d4a7ae7a37fff28a9f1d4b51c1b9743f0 /subsonic-android | |
parent | 3948efaa61af0a749101e5590205717e33c2904a (diff) | |
parent | 6aa48f1b3faece3d949cc3980ac405a557bcc9c1 (diff) | |
download | dsub-9838cfeff21519445c654f69c545037265d837b9.tar.gz dsub-9838cfeff21519445c654f69c545037265d837b9.tar.bz2 dsub-9838cfeff21519445c654f69c545037265d837b9.zip |
Merge tombriden-offline_scrobble
Diffstat (limited to 'subsonic-android')
9 files changed, 214 insertions, 15 deletions
diff --git a/subsonic-android/res/values/strings.xml b/subsonic-android/res/values/strings.xml index 02f55908..545dcdac 100644 --- a/subsonic-android/res/values/strings.xml +++ b/subsonic-android/res/values/strings.xml @@ -117,6 +117,12 @@ <string name="select_album.donate_dialog_later">Later</string>
<string name="select_album.donate_dialog_0_trial_days_left">Trial period is over</string>
+ <string name="offline.scrobbles_dialog_title">Offline scrobbles file found.</string>
+ <string name="offline.scrobbles_dialog_message">Process offline scrobbles file?</string>
+ <string name="offline.scrobbles_success">Successfully scrobbled %1$d songs</string>
+ <string name="offline.scrobbles_partial">Successfully scrobbled %1$d of %2$d songs. Try the rest on a different server.</string>
+ <string name="offline.scrobbles_error">Failed to scrobble songs</string>
+
<string name="select_genre.empty">No genres found</string>
<string name="select_genre.blank">Blank</string>
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java index bbe3c507..45d6eb49 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java @@ -1,10 +1,14 @@ package github.daneren2005.dsub.fragments;
+import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.StatFs;
+import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.View;
@@ -21,7 +25,10 @@ import github.daneren2005.dsub.util.Util; import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.MenuInflater;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.util.ModalBackgroundTask;
+import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.view.ChangeLog;
import java.io.File;
import java.util.ArrayList;
@@ -29,6 +36,7 @@ import java.util.Arrays; import java.util.List;
public class MainFragment extends SubsonicFragment {
+ private static final String TAG = MainFragment.class.getSimpleName();
private LayoutInflater inflater;
private static final int MENU_GROUP_SERVER = 10;
@@ -192,8 +200,16 @@ public class MainFragment extends SubsonicFragment { }
private void toggleOffline() {
- Util.setOffline(context, !Util.isOffline(context));
+ boolean isOffline = Util.isOffline(context);
+ Util.setOffline(context, !isOffline);
context.getPagerAdapter().invalidate();
+
+ if(isOffline) {
+ int count = Util.offlineScrobblesCount(context);
+ if(count > 0){
+ showOfflineScrobblesDialog(count);
+ }
+ }
}
private void showAlbumList(String type) {
@@ -211,6 +227,55 @@ public class MainFragment extends SubsonicFragment { replaceFragment(fragment, R.id.home_layout);
}
}
+
+ private void showOfflineScrobblesDialog(final int count) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setIcon(android.R.drawable.ic_dialog_info)
+ .setTitle(R.string.offline_scrobbles_dialog_title)
+ .setMessage(R.string.offline_scrobbles_dialog_message)
+ .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ new SilentBackgroundTask<Integer>(context) {
+ @Override
+ protected Integer doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ return musicService.processOfflineScrobbles(context, null);
+ }
+
+ @Override
+ protected void done(Integer result) {
+ if(result == count) {
+ Util.toast(context, context.getResources().getString(R.string.offline_scrobbles_success, result));
+ } else {
+ Util.toast(context, context.getResources().getString(R.string.offline_scrobbles_partial, result, count));
+ }
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ String msg = context.getResources().getString(R.string.offline_scrobbles_error) + " " + getErrorMessage(error);
+ Util.toast(context, msg);
+ }
+ }.execute();
+ }
+ }).setNeutralButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ dialogInterface.dismiss();
+ }
+ }).setNegativeButton(R.string.common_delete, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ dialogInterface.dismiss();
+ SharedPreferences.Editor offline = Util.getOfflineSync(context).edit();
+ offline.putInt(Constants.OFFLINE_SCROBBLE_COUNT, 0);
+ offline.commit();
+ }
+ });
+
+ builder.create().show();
+ }
private void showAboutDialog() {
try {
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index f0e370ce..82b6c481 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -341,9 +341,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter @Override
protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable {
- MusicService musicService = MusicServiceFactory.getMusicService(context);
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
MusicDirectory dir = load(musicService);
- boolean valid = musicService.isLicenseValid(context, this);
+ boolean valid = musicService.isLicenseValid(context, this);
return new Pair<MusicDirectory, Boolean>(dir, valid);
}
@@ -690,4 +690,4 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter return null;
}
}
-}
\ No newline at end of file +}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java index 5bb2338f..59e81c3b 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -38,6 +38,7 @@ import github.daneren2005.dsub.domain.SearchResult; import github.daneren2005.dsub.domain.Share; import github.daneren2005.dsub.domain.Version; import github.daneren2005.dsub.util.CancellableTask; +import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.LRUCache; import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.TimeLimitedCache; @@ -300,6 +301,13 @@ public class CachedMusicService implements MusicService { public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception { return musicService.getSongsByGenre(genre, count, offset, context, progressListener); } + + @Override + public int processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ + return musicService.processOfflineScrobbles(context, progressListener); + } + + private void checkSettingsChanged(Context context) { String newUrl = Util.getRestUrl(context, null); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java index c0d0b589..1689835f 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java @@ -117,4 +117,6 @@ public interface MusicService { List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception; public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception; + + int processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception; }
\ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index adc3a9e0..0c7dba27 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -19,8 +19,6 @@ package github.daneren2005.dsub.service; import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; import java.io.Reader; import java.io.FileReader; import java.util.ArrayList; @@ -34,7 +32,6 @@ import java.util.Set; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.media.MediaMetadataRetriever; import android.util.Log; import github.daneren2005.dsub.domain.Artist; @@ -47,7 +44,6 @@ import github.daneren2005.dsub.domain.MusicFolder; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.domain.SearchCritera; import github.daneren2005.dsub.domain.SearchResult; -import github.daneren2005.dsub.service.parser.PlaylistParser; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.ProgressListener; @@ -459,7 +455,30 @@ public class OfflineMusicService extends RESTMusicService { @Override public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Scrobbling not available in offline mode"); + if(!submission) { + return; + } + + SharedPreferences prefs = Util.getPreferences(context); + String cacheLocn = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); + + SharedPreferences offline = Util.getOfflineSync(context); + int scrobbles = offline.getInt(Constants.OFFLINE_SCROBBLE_COUNT, 0); + scrobbles++; + SharedPreferences.Editor offlineEditor = offline.edit(); + + if(id.indexOf(cacheLocn) != -1) { + String scrobbleSearchCriteria = Util.parseOfflineIDSearch(context, id, cacheLocn); + offlineEditor.putString(Constants.OFFLINE_SCROBBLE_SEARCH + scrobbles, scrobbleSearchCriteria); + offlineEditor.remove(Constants.OFFLINE_SCROBBLE_ID + scrobbles); + } else { + offlineEditor.putString(Constants.OFFLINE_SCROBBLE_ID + scrobbles, id); + offlineEditor.remove(Constants.OFFLINE_SCROBBLE_SEARCH + scrobbles); + } + + offlineEditor.putLong(Constants.OFFLINE_SCROBBLE_TIME + scrobbles, System.currentTimeMillis()); + offlineEditor.putInt(Constants.OFFLINE_SCROBBLE_COUNT, scrobbles); + offlineEditor.commit(); } @Override @@ -540,6 +559,11 @@ public class OfflineMusicService extends RESTMusicService { return result; } + + @Override + public int processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ + throw new OfflineException("Offline scrobble cached can not be processes while in offline mode"); + } private void listFilesRecursively(File parent, List<File> children) { for (File file : FileUtil.listMediaFiles(parent)) { diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index e12c4024..0edd1a6d 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -162,7 +162,7 @@ public class RESTMusicService implements MusicService { Log.e(TAG, "Failed to create custom SSL socket factory, using default.", x); return org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); } - } + } @Override public void ping(Context context, ProgressListener progressListener) throws Exception { @@ -176,7 +176,8 @@ public class RESTMusicService implements MusicService { @Override public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getLicense", null); + + Reader reader = getReader(context, progressListener, "getLicense", null); try { ServerInfo serverInfo = new LicenseParser(context).parse(reader); return serverInfo.isLicenseValid(); @@ -186,6 +187,7 @@ public class RESTMusicService implements MusicService { } public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception { + List<MusicFolder> cachedMusicFolders = readCachedMusicFolders(context); if (cachedMusicFolders != null && !refresh) { return cachedMusicFolders; @@ -489,8 +491,30 @@ public class RESTMusicService implements MusicService { @Override public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { + SharedPreferences prefs = Util.getPreferences(context); + String cacheLocn = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); + + if(id.indexOf(cacheLocn) != -1 && submission) { + String scrobbleSearchCriteria = Util.parseOfflineIDSearch(context, id, cacheLocn); + SearchCritera critera = new SearchCritera(scrobbleSearchCriteria, 0, 0, 1); + SearchResult result = searchNew(critera, context, progressListener); + if(result.getSongs().size() == 1){ + scrobble(result.getSongs().get(0).getId(), true, 0, context, progressListener); + } + } else { + scrobble(id, submission, 0, context, progressListener); + } + } + + public void scrobble(String id, boolean submission, long time, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.5", "Scrobbling not supported."); - Reader reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission"), Arrays.<Object>asList(id, submission)); + Reader reader; + if(time > 0){ + checkServerVersion(context, "1.8", "Scrobbling with a time not supported."); + reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission", "time"), Arrays.<Object>asList(id, submission, time)); + } + else + reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission"), Arrays.<Object>asList(id, submission)); try { new ErrorParser(context).parse(reader); } finally { @@ -855,6 +879,46 @@ public class RESTMusicService implements MusicService { Util.close(reader); } } + + @Override + public int processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ + SharedPreferences offline = Util.getOfflineSync(context); + SharedPreferences.Editor offlineEditor = offline.edit(); + int count = offline.getInt(Constants.OFFLINE_SCROBBLE_COUNT, 0); + int retry = 0; + for(int i = 1; i <= count; i++) { + String id = offline.getString(Constants.OFFLINE_SCROBBLE_ID + i, null); + long time = offline.getLong(Constants.OFFLINE_SCROBBLE_TIME + i, 0); + if(id != null) { + scrobble(id, true, time, context, progressListener); + } else { + String search = offline.getString(Constants.OFFLINE_SCROBBLE_SEARCH + i, ""); + try{ + SearchCritera critera = new SearchCritera(search, 0, 0, 1); + SearchResult result = searchNew(critera, context, progressListener); + if(result.getSongs().size() == 1){ + Log.i(TAG, "Query '" + search + "' returned song " + result.getSongs().get(0).getTitle() + " by " + result.getSongs().get(0).getArtist() + " with id " + result.getSongs().get(0).getId()); + Log.i(TAG, "Scrobbling " + result.getSongs().get(0).getId() + " with time " + time); + scrobble(result.getSongs().get(0).getId(), true, time, context, progressListener); + } + else{ + throw new Exception("Song not found on server"); + } + } + catch(Exception e){ + Log.e(TAG, e.toString()); + retry++; + offlineEditor.putString(Constants.OFFLINE_SCROBBLE_SEARCH + retry, search); + offlineEditor.putLong(Constants.OFFLINE_SCROBBLE_TIME + retry, time); + } + } + } + + offlineEditor.putInt(Constants.OFFLINE_SCROBBLE_COUNT, retry); + offlineEditor.commit(); + + return count - retry; + } private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams) throws Exception { return getReader(context, progressListener, method, requestParams, Collections.<String>emptyList(), Collections.emptyList()); diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java index 2ed5f2a8..a2f43dcd 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java @@ -111,6 +111,11 @@ public final class Constants { public static final String PREFERENCES_KEY_CHAT_ENABLED = "chatEnabled"; public static final String PREFERENCES_KEY_VIDEO_PLAYER = "videoPlayer"; + public static final String OFFLINE_SCROBBLE_COUNT = "scrobbleCount"; + public static final String OFFLINE_SCROBBLE_ID = "scrobbleID"; + public static final String OFFLINE_SCROBBLE_SEARCH = "scrobbleTitle"; + public static final String OFFLINE_SCROBBLE_TIME = "scrobbleTime"; + public static final String CACHE_KEY_IGNORE = "ignoreArticles"; public static final String MAIN_BACK_STACK = "backStackIds"; @@ -120,6 +125,7 @@ public final class Constants { // Name of the preferences file. public static final String PREFERENCES_FILE_NAME = "github.daneren2005.dsub_preferences"; + public static final String OFFLINE_SYNC_NAME = "github.daneren2005.dsub.offline"; // Number of free trial days for non-licensed servers. public static final int FREE_TRIAL_DAYS = 30; diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Util.java b/subsonic-android/src/github/daneren2005/dsub/util/Util.java index d0bac8f5..3ad4c623 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Util.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Util.java @@ -147,9 +147,6 @@ public final class Util { } public static boolean isScrobblingEnabled(Context context) { - if (isOffline(context)) { - return false; - } SharedPreferences prefs = getPreferences(context); return prefs.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false); } @@ -362,6 +359,33 @@ public final class Util { public static SharedPreferences getPreferences(Context context) { return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0); } + public static SharedPreferences getOfflineSync(Context context) { + return context.getSharedPreferences(Constants.OFFLINE_SYNC_NAME, 0); + } + + public static int offlineScrobblesCount(Context context) { + SharedPreferences offline = getOfflineSync(context); + return offline.getInt(Constants.OFFLINE_SCROBBLE_COUNT, 0); + } + + public static String parseOfflineIDSearch(Context context, String id, String cacheLocation) { + String name = id.replace(cacheLocation, ""); + if(name.startsWith("/")) { + name = name.substring(1); + } + name = name.replace(".complete", "").replace(".partial", ""); + int index = name.lastIndexOf("."); + name = index == -1 ? name : name.substring(0, index); + String[] details = name.split("/"); + + String artist = "artist:\"" + details[0] + "\""; + String title = details[details.length - 1]; + title = "title:\"" + title.substring(title.indexOf('-') + 1) + "\""; + + name = artist + " AND " + title; + + return name; + } public static String getContentType(HttpEntity entity) { if (entity == null || entity.getContentType() == null) { |