diff --git a/app/src/androidTest/java/com/adam/aslfms/receiver/MusicAPITest.java b/app/src/androidTest/java/com/adam/aslfms/receiver/MusicAPITest.java index 13ec3387..c3edf05e 100644 --- a/app/src/androidTest/java/com/adam/aslfms/receiver/MusicAPITest.java +++ b/app/src/androidTest/java/com/adam/aslfms/receiver/MusicAPITest.java @@ -129,11 +129,11 @@ public void testGettersFromDB() { public void testEnabling() { MusicAPI a = getMusicAPIA(); assertTrue(a.isEnabled()); - a.setEnabled(ctx, false); + a.setEnabled(ctx, 0); assertFalse(a.isEnabled()); MusicAPI b = MusicAPI.fromDatabase(ctx, a.getId()); assertFalse(b.isEnabled()); - b.setEnabled(ctx, true); + b.setEnabled(ctx, 1); assertTrue(b.isEnabled()); a = MusicAPI.fromDatabase(ctx, b.getId()); assertTrue(a.isEnabled()); diff --git a/app/src/main/assets/changelog.txt b/app/src/main/assets/changelog.txt index 29e43c2a..b14c8714 100644 --- a/app/src/main/assets/changelog.txt +++ b/app/src/main/assets/changelog.txt @@ -5,7 +5,11 @@ https://github.com/simple-last-fm-scrobbler/sls/wiki/Privacy-Concerns For privacy concerns. - 1.6.8 (2019-9-15) codename: kingus - * Correction Rules database problem fixed + * Database problems fixed + * Database migration + * Stop automatic app adding, so user must manually add music apps Android 5.0+ + * Offline Heart Track + * A grey record of sent tracks is kept now * Back button in permissions fixed * Privacy information in permissions * Russian thanks to alexesprit diff --git a/app/src/main/java/com/adam/aslfms/MusicAppsActivity.java b/app/src/main/java/com/adam/aslfms/MusicAppsActivity.java index fb1de407..dcf00d5c 100644 --- a/app/src/main/java/com/adam/aslfms/MusicAppsActivity.java +++ b/app/src/main/java/com/adam/aslfms/MusicAppsActivity.java @@ -113,9 +113,22 @@ public boolean onPreferenceTreeClick(PreferenceScreen prefScreen, if (mapi != null) { CheckBoxPreference cbp = (CheckBoxPreference) pref; boolean checked = cbp.isChecked(); - mapi.setEnabled(this, checked); + mapi.setEnabled(this, checked ? 1 : 0); setSMASummary(pref, mapi); + switch (mapi.getEnabledValue()){ + case 0: + pref.setIcon(android.R.drawable.checkbox_off_background); + break; + case 1: + pref.setIcon(android.R.drawable.checkbox_on_background); + break; + case 2: + default: + pref.setIcon(android.R.drawable.stat_sys_warning); + break; + } + if (checked && mScrobbleDroidInstalled && mapi.clashesWithScrobbleDroid()) { Util.warningDialog(this, getString( @@ -157,11 +170,26 @@ private void update() { MusicAPI[] mapis = MusicAPI.all(this); for (MusicAPI mapi : mapis) { + CheckBoxPreference appPref = new CheckBoxPreference(this, null); appPref.setTitle(mapi.getName()); appPref.setPersistent(false); appPref.setChecked(mapi.isEnabled()); + + switch (mapi.getEnabledValue()){ + case 0: + appPref.setIcon(android.R.drawable.checkbox_off_background); + break; + case 1: + appPref.setIcon(android.R.drawable.checkbox_on_background); + break; + case 2: + default: + appPref.setIcon(android.R.drawable.stat_sys_warning); + break; + } + mSupportedMusicAppsList.addPreference(appPref); mPrefsToMapisMap.put(appPref, mapi); mMapisToPrefsMap.put(mapi, appPref); @@ -197,7 +225,8 @@ private void setSMASummary(Preference pref, MusicAPI mapi) { pref.setSummary(getString(R.string.incompatability_short) .replaceAll("%1", mScrobbleDroidLabel)); } else { - pref.setSummary(mapi.getMessage()); + if (!mapi.getMessage().equals("generic receiver")) pref.setSummary(mapi.getMessage()); + else pref.setSummary(""); } } } diff --git a/app/src/main/java/com/adam/aslfms/SettingsActivity.java b/app/src/main/java/com/adam/aslfms/SettingsActivity.java index fe0638a1..58b7548f 100644 --- a/app/src/main/java/com/adam/aslfms/SettingsActivity.java +++ b/app/src/main/java/com/adam/aslfms/SettingsActivity.java @@ -145,7 +145,7 @@ public boolean onPreferenceTreeClick(PreferenceScreen prefScreen, Preference pref) { if (pref == mScrobbleAllNow) { checkNetwork(); - int numInCache = mDb.queryNumberOfTracks(); + int numInCache = mDb.queryNumberOfUnscrobbledTracks(); Util.scrobbleAllIfPossible(this, numInCache); return true; } else if (pref == mViewScrobbleCache) { @@ -168,7 +168,8 @@ public boolean onPreferenceTreeClick(PreferenceScreen prefScreen, * whether stuff is enabled or checked, etc. */ private void update() { - int numCache = mDb.queryNumberOfTracks(); + mDb = new ScrobblesDatabase(this); + int numCache = mDb.queryNumberOfUnscrobbledTracks(); mScrobbleAllNow.setSummary(getString(R.string.scrobbles_cache).replace( "%1", Integer.toString(numCache))); mScrobbleAllNow.setEnabled(numCache > 0); @@ -206,12 +207,9 @@ public void onReceive(Context context, Intent intent) { private void runChecks(){ settings = new AppSettings(this); - // TODO: VERIFY EVERYTHING BELOW IS SAFE int v = Util.getAppVersionCode(this, getPackageName()); if (settings.getWhatsNewViewedVersion() < v){ settings.setKeyBypassNewPermissions(2); - mDb.rebuildScrobblesDatabaseOnce(); // keep as not all users have the newest database. - // TODO: verify only needed databases are destroyed } if (settings.getKeyBypassNewPermissions() == 2){ startActivity(new Intent(this, PermissionsActivity.class)); diff --git a/app/src/main/java/com/adam/aslfms/StatusActivity.java b/app/src/main/java/com/adam/aslfms/StatusActivity.java index a3780957..bc8a3b22 100644 --- a/app/src/main/java/com/adam/aslfms/StatusActivity.java +++ b/app/src/main/java/com/adam/aslfms/StatusActivity.java @@ -110,7 +110,7 @@ public boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { case MENU_SCROBBLE_NOW_ID: - int numInCache = mDb.queryNumberOfTracks(); + int numInCache = mDb.queryNumberOfUnscrobbledTracks(); Util.scrobbleAllIfPossible(this, numInCache); return true; case R.id.MENU_RESET_STATS_ID: diff --git a/app/src/main/java/com/adam/aslfms/ViewScrobbleCacheActivity.java b/app/src/main/java/com/adam/aslfms/ViewScrobbleCacheActivity.java index 65fbe5f0..107cb785 100644 --- a/app/src/main/java/com/adam/aslfms/ViewScrobbleCacheActivity.java +++ b/app/src/main/java/com/adam/aslfms/ViewScrobbleCacheActivity.java @@ -28,6 +28,7 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.database.Cursor; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.support.v7.app.ActionBar; @@ -57,6 +58,8 @@ public class ViewScrobbleCacheActivity extends AppCompatActivity { private static final String TAG = "VSCacheActivity"; + private static final int disabledColor = Color.argb(25, 0,0,0); + private AppSettings settings; @@ -65,6 +68,7 @@ public class ViewScrobbleCacheActivity extends AppCompatActivity { * mNetApp == null means that we should view cache for all netapps */ private NetApp mNetApp; + private NetApp[] mNetApps; private Cursor mScrobblesCursor = null; @@ -178,6 +182,7 @@ private void refillData() { private void fillData() { SortField sf = settings.getCacheSortField(); + if (mNetApp == null) { mScrobblesCursor = mDb.fetchAllTracksCursor(sf); } else { @@ -218,6 +223,15 @@ public boolean onOptionsItemSelected(MenuItem item) { mScrobblesCursor); } return true; + case R.id.menu_clear_completed_cache: + if (mNetApp == null) { + Util.deleteAllScrobbledTracksFromAllCaches(this, mDb, + mScrobblesCursor); + } else { + Util.deleteAllScrobbledTracksFromCache(this, mDb, mNetApp, + mScrobblesCursor); + } + return true; case R.id.menu_change_sort_order: viewChangeSortOrder(); return true; @@ -318,6 +332,15 @@ public MyAdapter(Context context, Cursor c) { @Override public void bindView(View view, Context context, Cursor cursor) { + + if (mNetApp == null) { + mNetApps = mDb.fetchNetAppsForScrobble(cursor.getInt(cursor.getColumnIndex("_id"))); + } else { + mNetApps = null; + } + + if (mNetApps.length == 0) view.setBackgroundColor(disabledColor); + String track = cursor.getString(cursor.getColumnIndex("track")); TextView trackView = (TextView) view.findViewById(R.id.track); trackView.setText(track); diff --git a/app/src/main/java/com/adam/aslfms/receiver/AbstractPlayStatusReceiver.java b/app/src/main/java/com/adam/aslfms/receiver/AbstractPlayStatusReceiver.java index 373ab674..d7c3f60e 100644 --- a/app/src/main/java/com/adam/aslfms/receiver/AbstractPlayStatusReceiver.java +++ b/app/src/main/java/com/adam/aslfms/receiver/AbstractPlayStatusReceiver.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.util.Log; +import com.adam.aslfms.MusicAppsActivity; import com.adam.aslfms.R; import com.adam.aslfms.UserCredActivity; import com.adam.aslfms.service.ScrobblingService; @@ -91,16 +92,6 @@ public final void onReceive(Context context, Intent intent) { bundle = Bundle.EMPTY; } - // we must be logged in to scrobble - AppSettings settings = new AppSettings(context); - if (!settings.isAnyAuthenticated()) { - Util.myNotify(context, context.getResources().getString(R.string.warning) , context.getResources().getString(R.string.not_logged_in),05233, UserCredActivity.class); - Log - .d(TAG, - "The user has not authenticated, won't propagate the submission request"); - return; - } - mService = new Intent(context, ScrobblingService.class); mService.setAction(ScrobblingService.ACTION_PLAYSTATECHANGED); @@ -116,15 +107,19 @@ public final void onReceive(Context context, Intent intent) { } // check if the user wants to scrobble music from this MusicAPI - if (!mMusicAPI.isEnabled()) { + if (mMusicAPI.getEnabledValue() == 0) { Log.d(TAG, "App: " + mMusicAPI.getName() + " has been disabled, won't propagate"); return; + } else if (mMusicAPI.getEnabledValue() == 2) { + Util.myNotify(context, mMusicAPI.getName(), context.getString(R.string.new_music_app), 12473, MusicAppsActivity.class); + Log.d(TAG, "App: " + mMusicAPI.getName() + + " has been ignored, will propagate"); } // submit track for the ScrobblingService InternalTrackTransmitter.appendTrack(mTrack); - + AppSettings settings = new AppSettings(context); // start/call the Scrobbling Service if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && settings.isActiveAppEnabled(Util.checkPower(context))) { context.startForegroundService(mService); diff --git a/app/src/main/java/com/adam/aslfms/receiver/GenericControllerReceiver.java b/app/src/main/java/com/adam/aslfms/receiver/GenericControllerReceiver.java index 8e7925bb..4e1fdf5e 100644 --- a/app/src/main/java/com/adam/aslfms/receiver/GenericControllerReceiver.java +++ b/app/src/main/java/com/adam/aslfms/receiver/GenericControllerReceiver.java @@ -70,7 +70,7 @@ private void parseData(Context ctx, String action, Bundle bundle){ if (playerPackage != null && !playerPackage.isEmpty()) { PackageManager packageManager = ctx.getPackageManager(); playerName = packageManager.getApplicationLabel(packageManager.getApplicationInfo(playerPackage, PackageManager.GET_META_DATA)).toString(); - mMusicApi = MusicAPI.fromReceiver(ctx, playerName, playerPackage, null, false); + mMusicApi = MusicAPI.fromReceiver(ctx, playerName, playerPackage, "generic receiver", false); setMusicAPI(mMusicApi); } } @@ -78,7 +78,7 @@ private void parseData(Context ctx, String action, Bundle bundle){ Log.w(TAG, e.toString()); } if (mMusicApi == null) { - mMusicApi = MusicAPI.fromReceiver(ctx, ctx.getResources().getString(R.string.notification_controller), ctx.getPackageName(), null, false); + mMusicApi = MusicAPI.fromReceiver(ctx, ctx.getResources().getString(R.string.notification_controller), ctx.getPackageName(), "generic receiver", false); setMusicAPI(mMusicApi); } if (bundle.containsKey("track")) { @@ -142,7 +142,6 @@ private void parseData(Context ctx, String action, Bundle bundle){ break; case 5: default: - setState(Track.State.UNKNOWN_NONPLAYING); break; } } diff --git a/app/src/main/java/com/adam/aslfms/receiver/MusicAPI.java b/app/src/main/java/com/adam/aslfms/receiver/MusicAPI.java index 03b2c401..117eb394 100644 --- a/app/src/main/java/com/adam/aslfms/receiver/MusicAPI.java +++ b/app/src/main/java/com/adam/aslfms/receiver/MusicAPI.java @@ -58,7 +58,7 @@ public class MusicAPI { int enabled; MusicAPI(long id, String name, String pkg, String msg, - boolean clashWithScrobbleDroid, boolean enabled) { + boolean clashWithScrobbleDroid, int enabled) { super(); if (name == null) @@ -71,7 +71,7 @@ public class MusicAPI { this.pkg = pkg; this.msg = msg; this.clashWithScrobbleDroid = clashWithScrobbleDroid ? 1 : 0; - this.enabled = enabled ? 1 : 0; + this.enabled = enabled; } /** @@ -138,6 +138,17 @@ public boolean isEnabled() { return enabled == 1; } + /** + * Returns int if the user has not enabled or disabled scrobbling through this API / music + * app. Default is true. + * + * @return int of scrobbling from this API / music app + * @see MusicAppsActivity + */ + public int getEnabledValue() { + return enabled; + } + /** * Enables / disables scrobbling from this API / music app. * @@ -145,8 +156,8 @@ public boolean isEnabled() { * @param enabled whether this API / app should be enabled or disabled * @see MusicAppsActivity */ - public void setEnabled(Context ctx, boolean enabled) { - int en = enabled ? 1 : 0; + public void setEnabled(Context ctx, int enabled) { + int en = enabled; if (en == this.enabled) return; @@ -285,7 +296,8 @@ public static MusicAPI fromReceiver(Context ctx, String name, String pkg, vals.put("pkg", pkg); vals.put("msg", msg); vals.put("sdclash", clashWithScrobbleDroid ? 1 : 0); - vals.put("enabled", 1); + if (msg.equals("generic receiver")) vals.put("enabled", 2); + else vals.put("enabled", 1); long id = db.insert("music_api", null, vals); @@ -296,8 +308,8 @@ public static MusicAPI fromReceiver(Context ctx, String name, String pkg, Log.d(TAG, "new mapiinserted into db"); } - mapi = new MusicAPI(id, name, pkg, msg, clashWithScrobbleDroid, - true); + if (msg.equals("generic receiver")) mapi = new MusicAPI(id, name, pkg, msg, clashWithScrobbleDroid, 2); + else mapi = new MusicAPI(id, name, pkg, msg, clashWithScrobbleDroid, 1); Log.d(TAG, mapi.toString()); } DatabaseHelper.closeDatabase(); @@ -329,7 +341,7 @@ public static MusicAPI fromDatabase(Context ctx, long id) { // track // hasn't been played after the upgrade to v1.2.3 mapi = new MusicAPI(-1, ctx.getString(R.string.unknown_mapi), - NOT_AN_APPLICATION_PACKAGE + "pre_1_2_3", null, false, true); + NOT_AN_APPLICATION_PACKAGE + "pre_1_2_3", null, false, 1); } c.close(); DatabaseHelper.closeDatabase(); @@ -366,7 +378,7 @@ private static MusicAPI readMusicAPI(Cursor c) { c.getString(c.getColumnIndex("pkg")), // c.getString(c.getColumnIndex("msg")), // c.getInt(c.getColumnIndex("sdclash")) == 1, // - c.getInt(c.getColumnIndex("enabled")) == 1); + c.getInt(c.getColumnIndex("enabled"))); } static final String DATABASE_NAME = "music_apis"; diff --git a/app/src/main/java/com/adam/aslfms/service/ControllerReceiverCallback.java b/app/src/main/java/com/adam/aslfms/service/ControllerReceiverCallback.java index ecfec759..0fb93aaf 100644 --- a/app/src/main/java/com/adam/aslfms/service/ControllerReceiverCallback.java +++ b/app/src/main/java/com/adam/aslfms/service/ControllerReceiverCallback.java @@ -71,6 +71,7 @@ public void onPlaybackStateChanged(PlaybackState state) { case PlaybackState.STATE_ERROR: case PlaybackState.STATE_PAUSED: case PlaybackState.STATE_STOPPED: + case PlaybackState.STATE_NONE: playing = 3; // pause break; case PlaybackState.STATE_SKIPPING_TO_NEXT: @@ -78,9 +79,7 @@ public void onPlaybackStateChanged(PlaybackState state) { case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM: playing = 4; // complete break; - case PlaybackState.STATE_NONE: default: - playing = 5; // unknown break; } localIntent.putExtra("playing", playing); diff --git a/app/src/main/java/com/adam/aslfms/service/Handshaker.java b/app/src/main/java/com/adam/aslfms/service/Handshaker.java index 970858d7..78e15ddf 100644 --- a/app/src/main/java/com/adam/aslfms/service/Handshaker.java +++ b/app/src/main/java/com/adam/aslfms/service/Handshaker.java @@ -35,6 +35,7 @@ import com.adam.aslfms.util.AuthStatus.TemporaryFailureException; import com.adam.aslfms.util.AuthStatus.UnknownResponseException; import com.adam.aslfms.util.MD5; +import com.adam.aslfms.util.ScrobblesDatabase; import com.adam.aslfms.util.Util; import com.adam.aslfms.util.Util.NetworkStatus; @@ -133,9 +134,11 @@ public void run() { // we don't need/want it anymore, settings.getPwdMd5() is enough settings.setPassword(getNetApp(), ""); + ScrobblesDatabase db = new ScrobblesDatabase(mCtx); - notifyAuthStatusUpdate(AuthStatus.AUTHSTATUS_OK); + db.verifyOrUpdateScrobblesAlreadyInCache(getNetApp()); + notifyAuthStatusUpdate(AuthStatus.AUTHSTATUS_OK); // launch services after login Util.runServices(mCtx); @@ -198,9 +201,11 @@ public void run() { getNetworker().resetSleeper(); getNetworker().launchNetworkWaiter(); getNetworker().launchScrobbler(); + getNetworker().launchHeart(); } else { getNetworker().launchSleeper(); getNetworker().launchScrobbler(); + getNetworker().launchHeart(); } e.getStackTrace(); } diff --git a/app/src/main/java/com/adam/aslfms/service/Heart.java b/app/src/main/java/com/adam/aslfms/service/Heart.java index ca62d069..4bad59aa 100644 --- a/app/src/main/java/com/adam/aslfms/service/Heart.java +++ b/app/src/main/java/com/adam/aslfms/service/Heart.java @@ -28,6 +28,7 @@ import com.adam.aslfms.R; import com.adam.aslfms.util.AppSettings; import com.adam.aslfms.util.MD5; +import com.adam.aslfms.util.ScrobblesDatabase; import com.adam.aslfms.util.Track; import org.json.JSONObject; @@ -52,64 +53,72 @@ public class Heart extends NetRunnable { private static final String TAG = "Heart"; - protected Track hearTrack; + protected ScrobblesDatabase db; protected AppSettings settings; Context mCtx; - public Heart(NetApp napp, Context ctx, Networker net, Track hearTrack, AppSettings settings) { + public Heart(NetApp napp, Context ctx, Networker net, ScrobblesDatabase db) { super(napp, ctx, net); - this.hearTrack = hearTrack; - this.settings = settings; + this.db = db; this.mCtx = ctx; } public final void run() { + + settings = new AppSettings(mCtx); // can't heart track - String sigText = "api_key" - + settings.rcnvK(settings.getAPIkey()) - + "artist" + hearTrack.getArtist() - + "methodtrack.lovesk" - + settings.getSessionKey(getNetApp()) - + "track" + hearTrack.getTrack() - + settings.rcnvK(settings.getSecret()); - String signature = MD5.getHashString(sigText); + String[][] strings = db.fetchHeartsArray(); - try { - String response = postHeartTrack(hearTrack, settings.rcnvK(settings.getAPIkey()), signature, settings.getSessionKey(getNetApp())); - // TODO: ascertain if string is Json - if (response.equals("okSuccess")) { - Handler h = new Handler(mCtx.getMainLooper()); - h.post(() -> Toast.makeText(mCtx, mCtx.getString(R.string.loved_track) + " " + getNetApp().getName(), Toast.LENGTH_SHORT).show()); - Log.d(TAG, "Successful heart track: " + getNetApp().getName()); - } else { - JSONObject jObject = new JSONObject(response); - if (jObject.has("error")) { - int code = jObject.getInt("error"); - if (code == 6) { - // store hearTrack in database or allow failure. - // settings.setSessionKey(getNetApp(), ""); + for (String[] s : strings) { + + boolean failure = false; + String sigText = "api_key" + + settings.rcnvK(settings.getAPIkey()) + + "artist" + s[1] + + "methodtrack.lovesk" + + settings.getSessionKey(getNetApp()) + + "track" + s[0] + + settings.rcnvK(settings.getSecret()); + + String signature = MD5.getHashString(sigText); + + try { + String response = postHeartTrack(s, settings.rcnvK(settings.getAPIkey()), signature, settings.getSessionKey(getNetApp())); + // TODO: ascertain if string is Json + if (response.equals("okSuccess")) { + Log.d(TAG, "Successful heart track: " + getNetApp().getName()); + } else { + JSONObject jObject = new JSONObject(response); + if (jObject.has("error")) { + int code = jObject.getInt("error"); + if (code == 9 || code == 11) { + Log.d(TAG, "Failed heart track."); + } + } else { Log.d(TAG, "Failed heart track."); + failure = true; } - } else { - Log.d(TAG, "Failed heart track."); } + } catch (Exception e) { + Log.e(TAG, "Heart track fail " + e); + //e.printStackTrace(); + failure = true; } - } catch (Exception e) { - Log.e(TAG, "Heart track fail " + e); - //e.printStackTrace(); + if (failure) db.deleteHeart(s); } } - private String postHeartTrack(Track track, String testAPI, String signature, String + private String postHeartTrack(String[] track, String testAPI, String signature, String sessionKey) { URL url; HttpURLConnection conn = null; try { + url = new URL(getNetApp().getWebserviceUrl(settings)); conn = (HttpURLConnection) url.openConnection(); @@ -125,8 +134,8 @@ private String postHeartTrack(Track track, String testAPI, String signature, Str Map params = new LinkedHashMap<>(); params.put("method", "track.love"); - params.put("track", track.getTrack()); - params.put("artist", track.getArtist()); + params.put("track", track[0]); + params.put("artist", track[1]); params.put("api_key", testAPI); params.put("api_sig", signature); params.put("sk", sessionKey); diff --git a/app/src/main/java/com/adam/aslfms/service/Networker.java b/app/src/main/java/com/adam/aslfms/service/Networker.java index bff1be6a..98dced03 100644 --- a/app/src/main/java/com/adam/aslfms/service/Networker.java +++ b/app/src/main/java/com/adam/aslfms/service/Networker.java @@ -120,7 +120,7 @@ public void launchNPNotifier(Track track) { mExecutor.execute(n); } - public void launchHeartTrack(Track track) { + public void launchHeart() { Iterator i = mExecutor.getQueue().iterator(); while (i.hasNext()) { Runnable r = i.next(); @@ -129,8 +129,8 @@ public void launchHeartTrack(Track track) { } } - Heart n = new Heart(mNetApp, mCtx, this, track, settings); - mExecutor.execute(n); + Heart s = new Heart(mNetApp, mCtx, this, mDb); + mExecutor.execute(s); } public void launchUserInfo() { diff --git a/app/src/main/java/com/adam/aslfms/service/NetworkerManager.java b/app/src/main/java/com/adam/aslfms/service/NetworkerManager.java index 9f5f3d62..11f2a45b 100644 --- a/app/src/main/java/com/adam/aslfms/service/NetworkerManager.java +++ b/app/src/main/java/com/adam/aslfms/service/NetworkerManager.java @@ -89,8 +89,16 @@ public void launchAllScrobblers() { } } - public void launchHeartTrack(Track track, NetApp napp) { - mSupportedNetApps.get(napp).launchHeartTrack(track); + public void launchAllHearts(){ + for (NetApp napp : NetApp.values()) { + if( napp != NetApp.LISTENBRAINZ && napp != NetApp.LISTENBRAINZCUSTOM){ + launchHeart(napp); + } + } + } + + public void launchHeart(NetApp napp) { + mSupportedNetApps.get(napp).launchHeart(); } public void launchGetUserInfo(NetApp napp) { diff --git a/app/src/main/java/com/adam/aslfms/service/Scrobbler.java b/app/src/main/java/com/adam/aslfms/service/Scrobbler.java index 9e94b421..bb596c17 100644 --- a/app/src/main/java/com/adam/aslfms/service/Scrobbler.java +++ b/app/src/main/java/com/adam/aslfms/service/Scrobbler.java @@ -108,7 +108,7 @@ public boolean doRun(HandshakeResult hInfo) { // delete scrobbles (not tracks) from db (not array) for (Track track : tracks) { - mDb.deleteScrobble(netApp, track.getRowId()); + if ( mDb.setSentField(netApp, track.getRowId()) <= 0) Log.e(TAG, "failed to set sent field for trackid: " + track.getRowId() + " netapp: " + netApp.getValue()); } // clean up tracks if no one else wants to scrobble them @@ -484,10 +484,6 @@ public void scrobbleCommit(HandshakeResult hInfo, Track[] tracks) if (track.getTrackNr() != null) { params.put("trackNumber" + is, track.getTrackNr()); } - - if (track.getRating().equals("L")) { - mNetManager.launchHeartTrack(track, netApp); - } } for (Map.Entry param : params.entrySet()) { sign += param.getKey() + String.valueOf(param.getValue()); diff --git a/app/src/main/java/com/adam/aslfms/service/ScrobblingService.java b/app/src/main/java/com/adam/aslfms/service/ScrobblingService.java index 9996915b..9cf0aa65 100644 --- a/app/src/main/java/com/adam/aslfms/service/ScrobblingService.java +++ b/app/src/main/java/com/adam/aslfms/service/ScrobblingService.java @@ -32,6 +32,7 @@ import com.adam.aslfms.PermissionsActivity; import com.adam.aslfms.R; +import com.adam.aslfms.UserCredActivity; import com.adam.aslfms.util.AppSettings; import com.adam.aslfms.util.InternalTrackTransmitter; import com.adam.aslfms.util.NotificationCreator; @@ -184,15 +185,21 @@ private void handleCommand(Intent i, int startId) { Toast.makeText(this, this.getString(R.string.no_heart_track), Toast.LENGTH_LONG).show(); } else { - mDb.loveRecentTrack(); + for (NetApp napp : NetApp.values()){ + if (napp != NetApp.LISTENBRAINZCUSTOM && napp != NetApp.LISTENBRAINZ) mDb.insertHeart(mCurrentTrack, napp); + } + mNetManager.launchAllHearts(); Toast.makeText(this, this.getString(R.string.song_is_ready), Toast.LENGTH_SHORT).show(); - Log.d(TAG, "Love Track Rating!"); + Log.d(TAG, "Love track insert"); } } catch (Exception e) { Log.e(TAG, "CAN'T HEART TRACK" + e); } } else if (mCurrentTrack != null) { - mCurrentTrack.setRating(); + for (NetApp napp : NetApp.values()){ + if (napp != NetApp.LISTENBRAINZCUSTOM && napp != NetApp.LISTENBRAINZ) mDb.insertHeart(mCurrentTrack, napp); + } + mNetManager.launchAllHearts(); Toast.makeText(this, this.getString(R.string.song_is_ready), Toast.LENGTH_SHORT).show(); Log.d(TAG, "Love Track Rating!"); } else { @@ -208,11 +215,11 @@ private void handleCommand(Intent i, int startId) { if (sdk < Build.VERSION_CODES.HONEYCOMB) { @SuppressWarnings("deprecation") android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(tempTrack.getTrack() + " by " + tempTrack.getArtist() + ", " + tempTrack.getAlbum() + ", on " + tempTrack.getMusicAPI().getName()); + clipboard.setText(tempTrack.getTrack() + R.string.by + tempTrack.getArtist() + ", " + tempTrack.getAlbum() + "; " + tempTrack.getMusicAPI().getName()); } else { @SuppressWarnings("deprecation") android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("Track", tempTrack.getTrack() + " by " + tempTrack.getArtist() + ", " + tempTrack.getAlbum() + ", on " + tempTrack.getMusicAPI().getName()); + android.content.ClipData clip = android.content.ClipData.newPlainText("Track", tempTrack.getTrack() + R.string.by + tempTrack.getArtist() + ", " + tempTrack.getAlbum() + "; " + tempTrack.getMusicAPI().getName()); clipboard.setPrimaryClip(clip); } Log.d(TAG, "Copy Track!"); @@ -227,11 +234,11 @@ private void handleCommand(Intent i, int startId) { if (sdk < Build.VERSION_CODES.HONEYCOMB) { @SuppressWarnings("deprecation") android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(mCurrentTrack.getTrack() + " by " + mCurrentTrack.getArtist() + ", " + mCurrentTrack.getAlbum() + ", on " + mCurrentTrack.getMusicAPI().getName()); + clipboard.setText(mCurrentTrack.getTrack() + R.string.by + mCurrentTrack.getArtist() + ", " + mCurrentTrack.getAlbum() + "; " + mCurrentTrack.getMusicAPI().getName()); } else { @SuppressWarnings("deprecation") android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("Track", mCurrentTrack.getTrack() + " by " + mCurrentTrack.getArtist() + ", " + mCurrentTrack.getAlbum() + ", on " + mCurrentTrack.getMusicAPI().getName()); + android.content.ClipData clip = android.content.ClipData.newPlainText("Track", mCurrentTrack.getTrack() + R.string.by + mCurrentTrack.getArtist() + ", " + mCurrentTrack.getAlbum() + "; " + mCurrentTrack.getMusicAPI().getName()); clipboard.setPrimaryClip(clip); } Log.d(TAG, "Copy Track!"); @@ -278,7 +285,13 @@ private synchronized void onPlayStateChanged(Track track, Track.State state) { tryNotifyNP(mCurrentTrack); foreGroundService(); - + // we must be logged in to scrobble + if (!settings.isAnyAuthenticated()) { + Util.myNotify(this, this.getResources().getString(R.string.warning) , this.getResources().getString(R.string.not_logged_in),05233, UserCredActivity.class); + Log + .d(TAG, + "The user has not authenticated, won't propagate the submission request"); + } } else if (state == Track.State.PAUSE) { // pause // TODO: test this state if (mCurrentTrack == null) { @@ -417,16 +430,19 @@ private void queue(Track track) { // now set up scrobbling rels for (NetApp napp : NetApp.values()) { + Log.d(TAG, "inserting scrobble: " + napp.getName()); if (settings.isAuthenticated(napp)) { - Log.d(TAG, "inserting scrobble: " + napp.getName()); - mDb.insertScrobble(napp, rowId); - - // tell interested parties - Intent i = new Intent( - ScrobblingService.BROADCAST_ONSTATUSCHANGED); - i.putExtra("netapp", napp.toString()); - sendBroadcast(i); + if (mDb.insertScrobble(napp, rowId)) { + Log.d(TAG, "inserting scrobble successful"); + } else { + Log.d(TAG, "inserting scrobble failure"); + } } + // tell interested parties + Intent i = new Intent( + ScrobblingService.BROADCAST_ONSTATUSCHANGED); + i.putExtra("netapp", napp.toString()); + sendBroadcast(i); } } else { Log.e(TAG, "Could not insert scrobble into the db"); diff --git a/app/src/main/java/com/adam/aslfms/util/ScrobblesDatabase.java b/app/src/main/java/com/adam/aslfms/util/ScrobblesDatabase.java index f9b6994c..37591d0c 100644 --- a/app/src/main/java/com/adam/aslfms/util/ScrobblesDatabase.java +++ b/app/src/main/java/com/adam/aslfms/util/ScrobblesDatabase.java @@ -21,6 +21,7 @@ package com.adam.aslfms.util; +import android.app.TaskStackBuilder; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -33,6 +34,11 @@ import com.adam.aslfms.service.NetApp; import com.adam.aslfms.util.enums.SortField; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * * @author tgwizard @@ -47,75 +53,101 @@ public class ScrobblesDatabase { private final Context mCtx; public static final String DATABASE_NAME = "data"; - private static final int DATABASE_VERSION = 6; + private static final int DATABASE_VERSION = 7; private static final String ENABLE_FOREIGN_KEYS = "PRAGMA foreign_keys = ON;"; private static final String TABLENAME_SCROBBLES = "scrobbles"; + private static final String TABLENAME_HEARTS = "hearts"; private static final String TABLENAME_CORRNETAPP = "scrobbles_netapp"; + private static final String TABLENAME_CORRNETAPP_REPAIRED = "scrobbles_netapp_repaired"; private static final String TABLENAME_CORRECTION_RULES = "correction_rules"; private static final String TABLENAME_RULE_CHANGES = "rule_changes"; private static final String TRIGGER_NAME_CHECK_CORRECTION_RULES = "check_correction_rules"; - private static final String DATABASE_CREATE_CORRECTION_RULES = - "create table correction_rules (" + - " _id integer primary key autoincrement," + - " track_to_change text not null," + - " album_to_change text not null," + - " artist_to_change text not null," + - " track_correction text not null," + - " album_correction text not null," + - " artist_correction text not null" + // Remember to add ',' when integrating musicapp support -// " musicapp integer not null" + - ");"; - - private static final String DATABASE_CREATE_RULE_CHANGES = - "create table rule_changes (" + - " track_id integer primary key," + - " original_track text not null," + - " original_album text not null," + - " original_artist text not null," + - " foreign key (track_id) references scrobbles(_id) on delete cascade on update cascade" + - ");"; - - private static final String DATABASE_CREATE_SCROBBLES = "create table scrobbles (" - + "_id integer primary key autoincrement, " // - + "musicapp integer not null, " // - + "artist text not null, " // - + "album text not null, " // - + "albumartist text not null, " // - + "trackartist text not null, " // - + "track text not null, " // - + "tracknr text not null, " // - + "mbid text not null, " // - + "source text not null, " // - + "duration integer not null, " // - + "whenplayed integer not null," // - + "rating text not null);"; - - private static final String DATABASE_CREATE_CORRNETAPP = "create table scrobbles_netapp (" - + "netappid integer not null, " - + "trackid integer not null, " - + "primary key (netappid, trackid), " - + "foreign key (trackid) references scrobbles(_id) " - + "on delete cascade on update cascade)"; + private static final String end_cmd = ";"; + private static final String begin_curve_brace = "("; + private static final String end_curve_brace = ")"; + private static final String comma = ","; + private static final String sp = " "; + private static final String if_exists = "IF EXISTS"; + private static final String if_not_exists = "IF NOT EXISTS"; + private static final String table = "table"; + private static final String create = "create"; + private static final String drop = "drop"; + private static final String alter = "alter"; + private static final String trigger = "trigger"; + private static final String add_column = "add column"; + private static final String int_opt = "INTEGER NOT NULL"; + private static final String str_opt = "TEXT NOT NULL DEFAULT ''"; + private static final String id_int_prim_key_auto_opt = "_id integer primary key autoincrement"; + private static final String music_api_int = "musicapp integer not null"; + private static final String on_update_cascade = "on delete cascade on update cascade"; + private static final String foreign_key_ref_track_id_refs_scrobbles_id = "foreign key (track_id) references scrobbles(_id)"; + private static final String foreign_key_ref_trackid_refs_scrobbles_id = "foreign key (trackid) references scrobbles(_id)"; + + private static final String[] correction_rules_strings = {"track_to_change", "album_to_change", "artist_to_change", "track_correction", "album_correction", "artist_correction"}; + + private static final String rule_changes_track_id = "track_id integer primary key"; + private static final String[] rule_changes_strings = {"original_track","original_album","original_artist"}; + + private static final String[] scrobbles_heart_only_strings = {"track", "artist", "netapp", "rating"}; + private static final String[] scrobbles_strings = {"album", "albumartist", "trackartist", "tracknr", "mbid", "source"}; + private static final String[] scrobbles_ints = {"duration", "whenplayed"}; + + private static final String[] scrobbles_netapp_strings = {"netappid", "trackid", "sentstatus", "acceptedstatus"}; + private static final String scrobbles_netapp_primary_key = "primary key (netappid, trackid)"; // failure DEPRECATED, do not use + + + private static String buildStringOptions(String[] stringArray, String option){ + StringBuilder stringBuilder = new StringBuilder(); + for (int j = 0; j < stringArray.length; j++){ + stringBuilder.append(sp); + stringBuilder.append(stringArray[j]); + stringBuilder.append(sp); + stringBuilder.append(option); + if (j != stringArray.length - 1) stringBuilder.append(comma); + } + return stringBuilder.toString(); + } + private static final String DATABASE_CREATE_CORRECTION_RULES = TABLENAME_CORRECTION_RULES + sp + begin_curve_brace + id_int_prim_key_auto_opt + comma + buildStringOptions(correction_rules_strings,str_opt) + end_curve_brace + end_cmd; + private static final String DATABASE_CREATE_RULE_CHANGES = TABLENAME_RULE_CHANGES + sp + begin_curve_brace + rule_changes_track_id + comma + buildStringOptions(rule_changes_strings, str_opt) + comma + sp + foreign_key_ref_track_id_refs_scrobbles_id + on_update_cascade + end_curve_brace + end_cmd; + private static final String DATABASE_CREATE_SCROBBLES = TABLENAME_SCROBBLES + sp + begin_curve_brace + id_int_prim_key_auto_opt + comma + sp + music_api_int + comma + sp + + buildStringOptions(scrobbles_heart_only_strings, str_opt) + comma + sp + buildStringOptions(scrobbles_strings, str_opt) + comma + sp + buildStringOptions(scrobbles_ints, int_opt) + end_curve_brace + end_cmd; + private static final String DATABASE_CREATE_CORRNETAPP_REPAIRED = TABLENAME_CORRNETAPP_REPAIRED + sp + begin_curve_brace + sp + id_int_prim_key_auto_opt + comma + buildStringOptions(scrobbles_netapp_strings, str_opt) + comma + sp + foreign_key_ref_trackid_refs_scrobbles_id + on_update_cascade + end_curve_brace ; + private static final String DATABASE_CREATE_HEARTS = TABLENAME_HEARTS + sp + begin_curve_brace + id_int_prim_key_auto_opt + comma + buildStringOptions(scrobbles_heart_only_strings, str_opt) + end_curve_brace + end_cmd; private static final String TRIGGGER_CREATE_CHECK_CORRECTION_RULES = - "create trigger check_correction_rules" + - " after insert on scrobbles" + + "check_correction_rules" + + " after insert on "+ TABLENAME_SCROBBLES + " for each row" + " when (select count(*) from correction_rules where new.track = track_to_change and new.album = album_to_change and new.artist = artist_to_change) = 1 " + "begin" + " insert into rule_changes (track_id, original_track, original_album, original_artist)" + " select _id track_id, track original_track, album original_album, artist original_artist" + - " from scrobbles" + + " from " + TABLENAME_SCROBBLES + " where _id = new._id;" + - " update scrobbles" + + " update " + TABLENAME_SCROBBLES + " set track = (select track_correction from correction_rules where new.track = track_to_change and new.album = album_to_change and new.artist = artist_to_change)," + " album = (select album_correction from correction_rules where new.track = track_to_change and new.album = album_to_change and new.artist = artist_to_change)," + " artist = (select artist_correction from correction_rules where new.track = track_to_change and new.album = album_to_change and new.artist = artist_to_change) where _id = new._id; " + "end;"; + private static final String TRIGGGER_CREATE_CHECK_CORRECTION_RULES_HEARTS = + "check_correction_rules_hearts" + + " after insert on "+ TABLENAME_HEARTS + + " for each row" + + " when (select count(*) from correction_rules where new.track = track_to_change and new.artist = artist_to_change) = 1 " + + "begin" + + " insert into rule_changes (track_id, original_track,original_artist)" + + " select _id track_id, track original_track, artist original_artist" + + " from " + TABLENAME_HEARTS + + " where _id = new._id;" + + " update " + TABLENAME_HEARTS + + " set track = (select track_correction from correction_rules where new.track = track_to_change and new.artist = artist_to_change)," + + " artist = (select artist_correction from correction_rules where new.track = track_to_change and new.artist = artist_to_change) where _id = new._id; " + + "end;"; + private static class DatabaseHelper extends SQLiteOpenHelper { private DatabaseHelper(Context _context) { @@ -163,28 +195,76 @@ public static void closeDatabase() { @Override public void onCreate(SQLiteDatabase db) { + db.execSQL(ENABLE_FOREIGN_KEYS); + String pre_cmd = create + sp + table + sp; + String pre_cmd_with_trigger = create + sp + trigger + sp; Log.d(TAG, "create sql scrobbles: " + DATABASE_CREATE_SCROBBLES); - Log.d(TAG, "create sql corrnetapp: " + DATABASE_CREATE_CORRNETAPP); - db.execSQL(DATABASE_CREATE_SCROBBLES); - db.execSQL(DATABASE_CREATE_CORRNETAPP); + Log.d(TAG, "create sql corrnetapp: " + DATABASE_CREATE_CORRNETAPP_REPAIRED); + db.execSQL(pre_cmd + DATABASE_CREATE_SCROBBLES); + db.execSQL(pre_cmd + DATABASE_CREATE_CORRNETAPP_REPAIRED); // Tables and trigger for updating scrobbles based on rules. - db.execSQL(DATABASE_CREATE_CORRECTION_RULES); - db.execSQL(DATABASE_CREATE_RULE_CHANGES); - db.execSQL(TRIGGGER_CREATE_CHECK_CORRECTION_RULES); + Log.d(TAG, "create sql correction_rules: " + DATABASE_CREATE_CORRECTION_RULES); + Log.d(TAG, "create sql rules_changes: " + DATABASE_CREATE_RULE_CHANGES); + Log.d(TAG, "create sql hearts: " + DATABASE_CREATE_HEARTS); + Log.d(TAG, "create sql triggers: " + TRIGGGER_CREATE_CHECK_CORRECTION_RULES); + Log.d(TAG, "create sql triggers_hearts: " + TRIGGGER_CREATE_CHECK_CORRECTION_RULES_HEARTS); + db.execSQL(pre_cmd + DATABASE_CREATE_CORRECTION_RULES); + db.execSQL(pre_cmd + DATABASE_CREATE_RULE_CHANGES); + db.execSQL(pre_cmd + DATABASE_CREATE_HEARTS); + db.execSQL(pre_cmd_with_trigger + TRIGGGER_CREATE_CHECK_CORRECTION_RULES); + db.execSQL(pre_cmd_with_trigger + TRIGGGER_CREATE_CHECK_CORRECTION_RULES_HEARTS); + } + + private void verifyOrAddColumnInTable(String[] cols, String opt, String myTable, SQLiteDatabase db){ + Cursor cursor = db.rawQuery("SELECT * FROM " + myTable, null); + for (String col : cols) { + int columnIndex = cursor.getColumnIndex(col); + if (columnIndex < 0) { + db.execSQL(alter + sp + table + sp + myTable + sp + add_column + sp + col + sp + opt); + } + } + } + + private void repairCoreNetAppTable(SQLiteDatabase db){ + Cursor c = db.rawQuery( "SELECT name FROM sqlite_master WHERE type='table' AND name='" + TABLENAME_CORRNETAPP + "'",null); // check if table exists + if (c.getCount() > 0) { + Cursor dbCursor = db.query(TABLENAME_CORRNETAPP_REPAIRED, null, null, null, null, null, null); + String[] columnNames = dbCursor.getColumnNames(); + Log.d(TAG, columnNames[0]); + db.execSQL("INSERT INTO " + TABLENAME_CORRNETAPP_REPAIRED + " SELECT " + null + "," + scrobbles_netapp_strings[0] + "," + scrobbles_netapp_strings[1] + " FROM " + TABLENAME_CORRNETAPP + ";"); + } + db.execSQL(drop + sp + table + sp + if_exists + sp + TABLENAME_CORRNETAPP); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + String pre_cmd = create + sp + table + sp + if_not_exists + sp; + String pre_cmd_with_trigger = create + sp + trigger + sp + if_not_exists + sp; Log.w(TAG, "Upgrading scrobbles database from version " + oldVersion + " to " + newVersion - + ", which will destroy all old data"); - db.execSQL("DROP TABLE IF EXISTS " + TABLENAME_SCROBBLES); - db.execSQL("DROP TABLE IF EXISTS " + TABLENAME_CORRNETAPP); - // TODO add migration of old rules if/when necessary - db.execSQL("DROP TABLE IF EXISTS " + TABLENAME_CORRECTION_RULES); - db.execSQL("DROP TABLE IF EXISTS " + TABLENAME_RULE_CHANGES); - db.execSQL("DROP TRIGGER IF EXISTS " + TRIGGER_NAME_CHECK_CORRECTION_RULES); - onCreate(db); + + ", which will test and update database functionality."); + if (newVersion > oldVersion) { + db.execSQL(ENABLE_FOREIGN_KEYS); + db.execSQL(pre_cmd + DATABASE_CREATE_CORRNETAPP_REPAIRED); + db.execSQL(pre_cmd + DATABASE_CREATE_SCROBBLES); + // Tables and trigger for updating scrobbles based on rules. + db.execSQL(pre_cmd + DATABASE_CREATE_CORRECTION_RULES); + db.execSQL(pre_cmd + DATABASE_CREATE_RULE_CHANGES); + db.execSQL(pre_cmd + DATABASE_CREATE_HEARTS); + db.execSQL(pre_cmd_with_trigger + TRIGGGER_CREATE_CHECK_CORRECTION_RULES); + db.execSQL(pre_cmd_with_trigger + TRIGGGER_CREATE_CHECK_CORRECTION_RULES_HEARTS); + // check for primary key bug in CORENETAPP + repairCoreNetAppTable(db); + // alter database to add missing values if possible + // scrobbles table + verifyOrAddColumnInTable(scrobbles_heart_only_strings, str_opt, TABLENAME_SCROBBLES, db); + verifyOrAddColumnInTable(scrobbles_strings, str_opt, TABLENAME_SCROBBLES, db); + verifyOrAddColumnInTable(scrobbles_ints, int_opt, TABLENAME_SCROBBLES, db); + // netapp table + verifyOrAddColumnInTable(scrobbles_netapp_strings, str_opt, TABLENAME_CORRNETAPP_REPAIRED, db); + // hearts table + verifyOrAddColumnInTable(scrobbles_heart_only_strings, str_opt, TABLENAME_HEARTS, db); + } } @Override @@ -233,6 +313,20 @@ public long insertTrack(Track track) { return mDb.insert(TABLENAME_SCROBBLES, null, vals); } + /** + * Return the new rowId for that heart, otherwise return a -1 to indicate + * failure. + * + * @return rowId or -1 if failed + */ + public long insertHeart(Track track, NetApp netApp){ + ContentValues vals = new ContentValues(); + vals.put("artist", track.getArtist()); + vals.put("track", track.getTrack()); + vals.put("netapp", netApp.getValue()); + return mDb.insert(TABLENAME_HEARTS, null, vals); + } + /** * * @param napp @@ -246,7 +340,31 @@ public boolean insertScrobble(NetApp napp, long trackid) { iVals.put("netappid", napp.getValue()); iVals.put("trackid", trackid); - return mDb.insert(TABLENAME_CORRNETAPP, null, iVals) > 0; + return mDb.insert(TABLENAME_CORRNETAPP_REPAIRED, null, iVals) > 0; + } + + public long verifyOrUpdateScrobblesAlreadyInCache(NetApp napp){ + open(); + Cursor c; + String sql = "select * from scrobbles"; + c = mDb.rawQuery(sql, null); + c.moveToFirst(); + long count = 0; + long temp = 0; + while (!c.isAfterLast()) { + ContentValues iVals = new ContentValues(); + iVals.put("netappid", napp.getValue()); + iVals.put("trackid", c.getInt(c.getColumnIndex("_id"))); + Cursor c2 = mDb.rawQuery("SELECT * FROM " + TABLENAME_CORRNETAPP_REPAIRED + " WHERE " + scrobbles_netapp_strings[0] + " =? AND " + scrobbles_netapp_strings[1] + " =? ", new String[] { Integer.toString(napp.getValue()), Integer.toString(c.getInt(c.getColumnIndex("_id")))}); + if (c2.moveToFirst()){ + // do nothing + } else { + temp = mDb.insert(TABLENAME_CORRNETAPP_REPAIRED, null, iVals); + count += temp < 0 ? 1 : 0; + } + c.moveToNext(); + } + return count; } public int deleteScrobble(NetApp napp, int trackId) { @@ -254,10 +372,26 @@ public int deleteScrobble(NetApp napp, int trackId) { Log.e(TAG, "Trying to delete scrobble with trackId == -1"); return -2; } - return mDb.delete(TABLENAME_CORRNETAPP, "netappid = ? and trackid = ?", + return mDb.delete(TABLENAME_CORRNETAPP_REPAIRED, "netappid = ? and trackid = ?", new String[]{"" + napp.getValue(), "" + trackId}); } + public int setSentField(NetApp napp, int trackId) { + if (trackId == -1) { + Log.e(TAG, "Failed to set sent field"); + return -2; + } + Log.d(TAG, "Trying to set sent field"); + ContentValues contentValues = new ContentValues(); + contentValues.put("sentstatus", "sent"); + return mDb.update(TABLENAME_CORRNETAPP_REPAIRED, contentValues,"netappid = " + napp.getValue() +" and _id = " + trackId, null); + } + + public int deleteHeart(String[] s) { + return mDb.delete(TABLENAME_HEARTS, scrobbles_heart_only_strings[0] + " = ? and " + scrobbles_heart_only_strings[1] + " = ? and " + scrobbles_heart_only_strings[2] + " = ?", + new String[]{"" + s[0], "" + s[1], "" + s[2]}); + } + public void setAlbum(String album, int trackId) { ContentValues values = new ContentValues(); values.put("album", album); @@ -294,13 +428,24 @@ public void setTrack(String track, int trackId) { * @return the number of rows affected */ public int deleteAllScrobbles(NetApp napp) { - return mDb.delete(TABLENAME_CORRNETAPP, "netappid = ?", + return mDb.delete(TABLENAME_CORRNETAPP_REPAIRED, "netappid = ?", new String[]{"" + napp.getValue()}); } + public int deleteAllScrobbledTracks(NetApp napp) { + return mDb.delete(TABLENAME_CORRNETAPP_REPAIRED, "netappid = ? AND sentstatus = ?", + new String[]{"" + napp.getValue(), "sent"}); + } + public boolean cleanUpTracks() { - mDb.execSQL("delete from scrobbles where _id not in " - + "(select trackid as _id from scrobbles_netapp)"); + mDb.execSQL("delete from scrobbles where scrobbles._id not in " + + "(select trackid as _id from " + TABLENAME_CORRNETAPP_REPAIRED + ")"); + return true; + } + + public boolean cleanUpScrobbledTracks() { + mDb.execSQL("delete from scrobbles where scrobbles._id not in " + + "(select trackid as _id from " + TABLENAME_CORRNETAPP_REPAIRED + " WHERE " + TABLENAME_CORRNETAPP_REPAIRED + ".sentstatus = 'sent')"); return true; } @@ -327,8 +472,8 @@ private Track readTrack(Cursor c) { public Track[] fetchTracksArray(NetApp napp, int maxFetch) { Cursor c; // try { - String sql = "select * from scrobbles, scrobbles_netapp " - + "where _id = trackid and netappid = " + napp.getValue(); + String sql = "select * from scrobbles, " + TABLENAME_CORRNETAPP_REPAIRED + + " where scrobbles._id = trackid and netappid = " + napp.getValue(); c = mDb.rawQuery(sql, null); /* * } catch (SQLiteException e) { Log.e(TAG, @@ -349,10 +494,29 @@ public Track[] fetchTracksArray(NetApp napp, int maxFetch) { return tracks; } + public String[][] fetchHeartsArray(){ + Cursor c; + // try { + String sql = "select * from " + TABLENAME_HEARTS ; + c = mDb.rawQuery(sql, null); + + int count = c.getCount(); + c.moveToFirst(); + String[][] tracks = new String[count][3]; + for (int i = 0; i < count; i++) { + tracks[i][0] = c.getString(c.getColumnIndex(scrobbles_heart_only_strings[0])); + tracks[i][1] = c.getString(c.getColumnIndex(scrobbles_heart_only_strings[1])); + tracks[i][2] = c.getString(c.getColumnIndex(scrobbles_heart_only_strings[2])); + c.moveToNext(); + } + c.close(); + return tracks; + } + public Cursor fetchTracksCursor(NetApp napp, SortField sf) { Cursor c; - String sql = "select * from scrobbles, scrobbles_netapp " - + "where _id = trackid and netappid = " + napp.getValue() + String sql = "select * from scrobbles, " + TABLENAME_CORRNETAPP_REPAIRED + + " where scrobbles._id = trackid and netappid = " + napp.getValue() + " order by " + sf.getSql(); c = mDb.rawQuery(sql, null); return c; @@ -378,21 +542,6 @@ public Track fetchTrack(int trackId) { return track; } - public void loveRecentTrack() { - String sql = "select * from scrobbles order by rowid desc limit 1"; - Cursor c = mDb.rawQuery(sql, null); - - if (c.getCount() == 0) - return; - - c.moveToFirst(); - long trackId = c.getLong(c.getColumnIndex("_id")); - ContentValues values = new ContentValues(); - values.put("rating", "L"); - mDb.update("scrobbles", values, "_id=" + trackId, null); - c.close(); - } - public Track fetchRecentTrack() { String sql = "select * from scrobbles order by rowid desc limit 1"; Cursor c = mDb.rawQuery(sql, null); @@ -407,8 +556,8 @@ public Track fetchRecentTrack() { } public NetApp[] fetchNetAppsForScrobble(int trackId) { - String sql = "select netappid from scrobbles_netapp where trackid = " - + trackId; + String sql = "select netappid from " + TABLENAME_CORRNETAPP_REPAIRED + " where trackid = " + + trackId + " and sentstatus != 'sent'"; Cursor c = mDb.rawQuery(sql, null); if (c.getCount() == 0) @@ -438,11 +587,25 @@ public int queryNumberOfTracks() { return count; } + public int queryNumberOfUnscrobbledTracks() { + if (mDb == null || !mDb.isOpen()) { + open(); + } + Cursor c = mDb.rawQuery("select count(distinct trackid) from " + TABLENAME_CORRNETAPP_REPAIRED + " where sentstatus = ''", null); + int count = c.getCount(); + if (count != 0) { + c.moveToFirst(); + count = c.getInt(0); + } + c.close(); + return count; + } + public int queryNumberOfScrobbles(NetApp napp) { Cursor c; c = mDb.rawQuery( - "select count(trackid) from scrobbles_netapp where netappid = " - + napp.getValue(), null); + "select count(trackid) from " + TABLENAME_CORRNETAPP_REPAIRED + " where netappid = " + + napp.getValue() + " and sentstatus != 'sent'" , null); int count = c.getCount(); if (count != 0) { c.moveToFirst(); @@ -503,30 +666,4 @@ public CorrectionRule fetchCorrectioneRule(int id) { c.close(); return rule; } - - public void rebuildCoreNetappDatabaseOnce(){ - Log.d(TAG, "dropping sql corenetapp "); - mDb.execSQL("DROP TABLE IF EXISTS " + TABLENAME_CORRNETAPP); - Log.d(TAG, "create sql corrnetapp: " + DATABASE_CREATE_CORRNETAPP); - mDb.execSQL(DATABASE_CREATE_CORRNETAPP); - } - - public void rebuildCorrectionsDatabaseOnce(){ - Log.d(TAG, "dropping sql corrections "); - mDb.execSQL("DROP TABLE IF EXISTS " + TABLENAME_CORRECTION_RULES); - mDb.execSQL("DROP TABLE IF EXISTS " + TABLENAME_RULE_CHANGES); - mDb.execSQL("DROP TRIGGER IF EXISTS " + TRIGGER_NAME_CHECK_CORRECTION_RULES); - Log.d(TAG, "create sql corrections: " + DATABASE_CREATE_CORRECTION_RULES); - mDb.execSQL(DATABASE_CREATE_CORRECTION_RULES); - mDb.execSQL(DATABASE_CREATE_RULE_CHANGES); - mDb.execSQL(TRIGGGER_CREATE_CHECK_CORRECTION_RULES); - } - - - public void rebuildScrobblesDatabaseOnce(){ - Log.d(TAG, "dropping sql scrobbles "); - mDb.execSQL("DROP TABLE IF EXISTS " + TABLENAME_SCROBBLES); - Log.d(TAG, "create sql scrobbles: " + DATABASE_CREATE_SCROBBLES); - mDb.execSQL(DATABASE_CREATE_SCROBBLES); - } } diff --git a/app/src/main/java/com/adam/aslfms/util/Util.java b/app/src/main/java/com/adam/aslfms/util/Util.java index 19335bf2..98d920e6 100644 --- a/app/src/main/java/com/adam/aslfms/util/Util.java +++ b/app/src/main/java/com/adam/aslfms/util/Util.java @@ -373,6 +373,54 @@ public static void deleteAllScrobblesFromAllCaches(Context ctx, } } + public static void deleteAllScrobbledTracksFromCache(Context ctx, + final ScrobblesDatabase db, final NetApp napp, final Cursor cursor) { + int numInCache = db.queryNumberOfScrobbles(napp); + if (numInCache > 0) { + Util.confirmDialog(ctx, ctx.getString( + R.string.confirm_delete_all_scd_tr).replaceAll("%1", + napp.getName()), R.string.clear_cache, android.R.string.cancel, + (dialog, which) -> { + Log.d(TAG, "Will remove all scrobbles from cache: " + + napp.getName()); + db.deleteAllScrobbledTracks(napp); + db.cleanUpScrobbledTracks(); + // need to refill data, otherwise the screen won't + // update + if (cursor != null) + cursor.requery(); + }); + } else { + Toast.makeText(ctx, ctx.getString(R.string.no_scrobbles_in_cache), + Toast.LENGTH_LONG).show(); + } + } + + public static void deleteAllScrobbledTracksFromAllCaches(Context ctx, + final ScrobblesDatabase db, final Cursor cursor) { + int numInCache = db.queryNumberOfTracks(); + if (numInCache > 0) { + Util.confirmDialog(ctx, ctx + .getString(R.string.confirm_delete_all_scd_tr_from_all), + R.string.clear_cache, android.R.string.cancel, + (dialog, which) -> { + Log + .d(TAG, + "Will remove scrobbled tracks from cache for all netapps"); + for (NetApp napp : NetApp.values()) + db.deleteAllScrobbledTracks(napp); + db.cleanUpScrobbledTracks(); + // need to refill data, otherwise the screen won't + // update + if (cursor != null) + cursor.requery(); + }); + } else { + Toast.makeText(ctx, ctx.getString(R.string.no_scrobbles_in_cache), + Toast.LENGTH_LONG).show(); + } + } + public static String getStatusSummary(Context ctx, AppSettings settings, NetApp napp) { return getStatusSummary(ctx, settings, napp, true); @@ -613,7 +661,7 @@ public static boolean isMyServiceRunning(Context context, Class serviceClass) { return false; } - public static void runServices(Context context) { + public static void runServices(Context context) { if (!isMyServiceRunning(context, ScrobblingService.class)) { Log.d(TAG, "(re)starting scrobbleservice"); Intent i = new Intent(context, ScrobblingService.class); diff --git a/app/src/main/res/menu/view_scrobble_cache.xml b/app/src/main/res/menu/view_scrobble_cache.xml index d90f0f4d..575c5845 100644 --- a/app/src/main/res/menu/view_scrobble_cache.xml +++ b/app/src/main/res/menu/view_scrobble_cache.xml @@ -8,6 +8,10 @@ android:id="@+id/menu_clear_cache" android:icon="@android:drawable/ic_menu_close_clear_cancel" android:title="@string/clear_cache" /> + Are you sure you don\'t want to scrobble this track? This operation cannot be undone. Are you sure you want to clear all tracks in the cache for %1? This operation cannot be undone. Are you sure you want to clear all tracks in the cache? This operation cannot be undone. + Are you sure you want to clear all scrobbled tracks in the cache for %1? This operation cannot be undone. + Are you sure you want to clear all scrobbled tracks in the cache? This operation cannot be undone. There are no cached scrobbles View scrobble cache View details Remove from cache Clear cache + Clear completed cache Track info Scrobble cache for %1 View and edit the list of unsubmitted scrobbles