aboutsummaryrefslogtreecommitdiff
path: root/subsonic-android
diff options
context:
space:
mode:
authorScott Jackson <daneren2005@gmail.com>2013-06-14 20:51:05 -0700
committerScott Jackson <daneren2005@gmail.com>2013-06-14 20:51:05 -0700
commit9838cfeff21519445c654f69c545037265d837b9 (patch)
tree9f528e7d4a7ae7a37fff28a9f1d4b51c1b9743f0 /subsonic-android
parent3948efaa61af0a749101e5590205717e33c2904a (diff)
parent6aa48f1b3faece3d949cc3980ac405a557bcc9c1 (diff)
downloaddsub-9838cfeff21519445c654f69c545037265d837b9.tar.gz
dsub-9838cfeff21519445c654f69c545037265d837b9.tar.bz2
dsub-9838cfeff21519445c654f69c545037265d837b9.zip
Merge tombriden-offline_scrobble
Diffstat (limited to 'subsonic-android')
-rw-r--r--subsonic-android/res/values/strings.xml6
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java67
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java6
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java8
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/MusicService.java2
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java34
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java70
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/Constants.java6
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/Util.java30
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) {