replace queue store with room database
This commit is contained in:
parent
bb2793dbea
commit
44c1bb63b6
10 changed files with 150 additions and 324 deletions
|
|
@ -6,6 +6,9 @@ import android.content.Context;
|
|||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.room.Room;
|
||||
|
||||
import com.dkanada.gramophone.database.JellyDatabase;
|
||||
import com.dkanada.gramophone.helper.EventListener;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
|
|
@ -22,6 +25,7 @@ import org.jellyfin.apiclient.logging.ILogger;
|
|||
public class App extends Application {
|
||||
private static App app;
|
||||
|
||||
private static JellyDatabase database;
|
||||
private static ApiClient apiClient;
|
||||
|
||||
@Override
|
||||
|
|
@ -29,6 +33,7 @@ public class App extends Application {
|
|||
super.onCreate();
|
||||
|
||||
app = this;
|
||||
database = createDatabase(this);
|
||||
apiClient = createApiClient(this);
|
||||
|
||||
// default theme
|
||||
|
|
@ -42,6 +47,12 @@ public class App extends Application {
|
|||
}
|
||||
}
|
||||
|
||||
public static JellyDatabase createDatabase(Context context) {
|
||||
return Room.databaseBuilder(context, JellyDatabase.class, "database")
|
||||
.allowMainThreadQueries()
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ApiClient createApiClient(Context context) {
|
||||
String appName = context.getString(R.string.app_name);
|
||||
String appVersion = BuildConfig.VERSION_NAME;
|
||||
|
|
@ -59,6 +70,10 @@ public class App extends Application {
|
|||
return new ApiClient(httpClient, logger, server, appName, appVersion, device, eventListener);
|
||||
}
|
||||
|
||||
public static JellyDatabase getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
public static ApiClient getApiClient() {
|
||||
return apiClient;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.dkanada.gramophone.database;
|
||||
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
@androidx.room.Database(
|
||||
entities = {
|
||||
Song.class,
|
||||
QueueSong.class
|
||||
},
|
||||
version = 1,
|
||||
exportSchema = false
|
||||
)
|
||||
public abstract class JellyDatabase extends RoomDatabase {
|
||||
public abstract QueueSongDao queueSongDao();
|
||||
public abstract SongDao songDao();
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.dkanada.gramophone.database;
|
||||
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.ForeignKey;
|
||||
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
@Entity(
|
||||
tableName = "queueSongs",
|
||||
primaryKeys = {
|
||||
"index",
|
||||
"queue"
|
||||
}
|
||||
)
|
||||
public class QueueSong {
|
||||
public int index;
|
||||
|
||||
public int queue;
|
||||
|
||||
@ForeignKey(
|
||||
entity = Song.class,
|
||||
parentColumns = {"id"},
|
||||
childColumns = {"songId"},
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
public String songId;
|
||||
|
||||
public QueueSong(String songId, int index, int queue) {
|
||||
this.songId = songId;
|
||||
|
||||
this.index = index;
|
||||
this.queue = queue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.dkanada.gramophone.database;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.dkanada.gramophone.App;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public abstract class QueueSongDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract void insertQueueSongs(List<QueueSong> queueSongs);
|
||||
|
||||
@Query("DELETE FROM queueSongs")
|
||||
public abstract void deleteQueueSongs();
|
||||
|
||||
@Query("SELECT * from queueSongs WHERE queue = :queue ORDER BY `index`")
|
||||
public abstract List<QueueSong> getQueueSongs(int queue);
|
||||
|
||||
public List<Song> getQueue(int queue) {
|
||||
List<QueueSong> queueSongs = getQueueSongs(queue);
|
||||
List<Song> songs = new ArrayList<>();
|
||||
|
||||
for (QueueSong queueSong : queueSongs) {
|
||||
Song song = App.getDatabase().songDao().getSong(queueSong.songId);
|
||||
if (song != null) songs.add(song);
|
||||
}
|
||||
|
||||
return songs;
|
||||
}
|
||||
|
||||
public void setQueue(List<Song> songs, int queue) {
|
||||
List<QueueSong> queueSongs = new ArrayList<>();
|
||||
for (int i = 0; i < songs.size(); i++) {
|
||||
queueSongs.add(new QueueSong(songs.get(i).id, i, queue));
|
||||
}
|
||||
|
||||
insertQueueSongs(queueSongs);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.dkanada.gramophone.database;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface SongDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insertSongs(List<Song> songs);
|
||||
|
||||
@Query("DELETE FROM songs")
|
||||
void deleteSongs();
|
||||
|
||||
@Query("SELECT * FROM songs WHERE id = :id")
|
||||
Song getSong(String id);
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
package com.dkanada.gramophone.loader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.MediaStore.Audio.AudioColumns;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SongLoader {
|
||||
public static final String QUEUE_PRIMARY = "image";
|
||||
public static final String QUEUE_FAVORITE = "favorite";
|
||||
|
||||
protected static final String BASE_SELECTION = AudioColumns.IS_MUSIC + "=1" + " AND " + AudioColumns.TITLE + " != ''";
|
||||
protected static final String[] BASE_PROJECTION = new String[]{
|
||||
BaseColumns._ID,
|
||||
AudioColumns.TITLE,
|
||||
AudioColumns.TRACK,
|
||||
AudioColumns.YEAR,
|
||||
AudioColumns.DURATION,
|
||||
AudioColumns.ALBUM_ID,
|
||||
AudioColumns.ALBUM,
|
||||
AudioColumns.ARTIST_ID,
|
||||
AudioColumns.ARTIST,
|
||||
QUEUE_PRIMARY,
|
||||
QUEUE_FAVORITE,
|
||||
};
|
||||
|
||||
@NonNull
|
||||
public static List<Song> getAllSongs(@NonNull Context context) {
|
||||
Cursor cursor = makeSongCursor(context, null, null);
|
||||
return getSongs(cursor);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<Song> getSongs(@Nullable final Cursor cursor) {
|
||||
List<Song> songs = new ArrayList<>();
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
songs.add(getSongFromCursorImpl(cursor));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
if (cursor != null) cursor.close();
|
||||
return songs;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Song getSong(@Nullable Cursor cursor) {
|
||||
Song song;
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
song = getSongFromCursorImpl(cursor);
|
||||
} else {
|
||||
song = Song.EMPTY;
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Song getSongFromCursorImpl(@NonNull Cursor cursor) {
|
||||
final String id = cursor.getString(0);
|
||||
final String title = cursor.getString(1);
|
||||
final int trackNumber = cursor.getInt(2);
|
||||
final int year = cursor.getInt(3);
|
||||
final long duration = cursor.getLong(4);
|
||||
|
||||
final String albumId = cursor.getString(5);
|
||||
final String albumName = cursor.getString(6);
|
||||
|
||||
final String artistId = cursor.getString(7);
|
||||
final String artistName = cursor.getString(8);
|
||||
|
||||
final String primary = cursor.getString(9);
|
||||
final boolean favorite = Boolean.valueOf(cursor.getString(10));
|
||||
|
||||
return new Song(id, title, trackNumber, 1, year, duration, albumId, albumName, artistId, artistName, primary, favorite);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Cursor makeSongCursor(@NonNull final Context context, @Nullable final String selection, final String[] selectionValues) {
|
||||
return makeSongCursor(context, selection, selectionValues, PreferenceUtil.getInstance(context).getSongSortOrder());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Cursor makeSongCursor(@NonNull final Context context, @Nullable String selection, String[] selectionValues, final String sortOrder) {
|
||||
if (selection != null && !selection.trim().equals("")) {
|
||||
selection = BASE_SELECTION + " AND " + selection;
|
||||
} else {
|
||||
selection = BASE_SELECTION;
|
||||
}
|
||||
|
||||
try {
|
||||
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
BASE_PROJECTION, selection, selectionValues, sortOrder);
|
||||
} catch (SecurityException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import android.os.Parcel;
|
|||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemDto;
|
||||
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
|
||||
|
|
@ -12,9 +14,12 @@ import org.jellyfin.apiclient.model.entities.MediaStream;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity(tableName = "songs")
|
||||
public class Song implements Parcelable {
|
||||
public static final Song EMPTY = new Song();
|
||||
|
||||
@NonNull
|
||||
@PrimaryKey
|
||||
public String id;
|
||||
public String title;
|
||||
public int trackNumber;
|
||||
|
|
@ -93,24 +98,6 @@ public class Song implements Parcelable {
|
|||
}
|
||||
}
|
||||
|
||||
public Song(String id, String title, int trackNumber, int discNumber, int year, long duration, String albumId, String albumName, String artistId, String artistName, String primary, boolean favorite) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.trackNumber = trackNumber;
|
||||
this.discNumber = discNumber;
|
||||
this.year = year;
|
||||
this.duration = duration;
|
||||
|
||||
this.albumId = albumId;
|
||||
this.albumName = albumName;
|
||||
|
||||
this.artistId = artistId;
|
||||
this.artistName = artistName;
|
||||
|
||||
this.primary = primary;
|
||||
this.favorite = favorite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The CyanogenMod Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.dkanada.gramophone.provider;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore.Audio.AudioColumns;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.dkanada.gramophone.App;
|
||||
import com.dkanada.gramophone.loader.SongLoader;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class QueueStore extends SQLiteOpenHelper {
|
||||
@Nullable
|
||||
private static QueueStore sInstance = null;
|
||||
public static final String DATABASE_NAME = "music_playback_state.db";
|
||||
public static final String PLAYING_QUEUE_TABLE_NAME = "playing_queue";
|
||||
public static final String ORIGINAL_PLAYING_QUEUE_TABLE_NAME = "original_playing_queue";
|
||||
private static final int VERSION = 5;
|
||||
|
||||
public QueueStore(final Context context) {
|
||||
super(context, DATABASE_NAME, null, VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@NonNull final SQLiteDatabase db) {
|
||||
createTable(db, PLAYING_QUEUE_TABLE_NAME);
|
||||
createTable(db, ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
}
|
||||
|
||||
private void createTable(@NonNull final SQLiteDatabase db, final String tableName) {
|
||||
// noinspection StringBufferReplaceableByString
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("CREATE TABLE IF NOT EXISTS ");
|
||||
builder.append(tableName);
|
||||
builder.append("(");
|
||||
|
||||
builder.append(BaseColumns._ID);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.TITLE);
|
||||
builder.append(" STRING NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.TRACK);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.YEAR);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.DURATION);
|
||||
builder.append(" LONG NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.ALBUM_ID);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.ALBUM);
|
||||
builder.append(" STRING NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.ARTIST_ID);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.ARTIST);
|
||||
builder.append(" STRING NOT NULL,");
|
||||
|
||||
builder.append(SongLoader.QUEUE_PRIMARY);
|
||||
builder.append(" STRING NOT NULL,");
|
||||
|
||||
builder.append(SongLoader.QUEUE_FAVORITE);
|
||||
builder.append(" STRING NOT NULL);");
|
||||
|
||||
db.execSQL(builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static synchronized QueueStore getInstance(@NonNull final Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new QueueStore(context.getApplicationContext());
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public synchronized void saveQueues(@NonNull final List<Song> playingQueue, @NonNull final List<Song> originalPlayingQueue) {
|
||||
saveQueue(PLAYING_QUEUE_TABLE_NAME, playingQueue);
|
||||
saveQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME, originalPlayingQueue);
|
||||
}
|
||||
|
||||
private synchronized void saveQueue(final String tableName, @NonNull final List<Song> queue) {
|
||||
final SQLiteDatabase database = getWritableDatabase();
|
||||
database.beginTransaction();
|
||||
|
||||
try {
|
||||
database.delete(tableName, null, null);
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
|
||||
final int NUM_PROCESS = 20;
|
||||
int position = 0;
|
||||
while (position < queue.size()) {
|
||||
database.beginTransaction();
|
||||
try {
|
||||
for (int i = position; i < queue.size() && i < position + NUM_PROCESS; i++) {
|
||||
Song song = queue.get(i);
|
||||
ContentValues values = new ContentValues(4);
|
||||
|
||||
values.put(BaseColumns._ID, song.id);
|
||||
values.put(AudioColumns.TITLE, song.title);
|
||||
values.put(AudioColumns.TRACK, song.trackNumber);
|
||||
values.put(AudioColumns.YEAR, song.year);
|
||||
values.put(AudioColumns.DURATION, song.duration);
|
||||
values.put(AudioColumns.ALBUM_ID, song.albumId);
|
||||
values.put(AudioColumns.ALBUM, song.albumName);
|
||||
values.put(AudioColumns.ARTIST_ID, song.artistId);
|
||||
values.put(AudioColumns.ARTIST, song.artistName);
|
||||
values.put(SongLoader.QUEUE_PRIMARY, song.primary);
|
||||
values.put(SongLoader.QUEUE_FAVORITE, song.favorite);
|
||||
|
||||
database.insert(tableName, null, values);
|
||||
}
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
position += NUM_PROCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Song> getSavedPlayingQueue() {
|
||||
return getQueue(PLAYING_QUEUE_TABLE_NAME);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Song> getSavedOriginalPlayingQueue() {
|
||||
return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Song> getQueue(@NonNull final String tableName) {
|
||||
if (!PreferenceUtil.getInstance(App.getInstance()).getRememberQueue()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Cursor cursor = getReadableDatabase().query(tableName, null,
|
||||
null, null, null, null, null);
|
||||
return SongLoader.getSongs(cursor);
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,6 @@ import com.dkanada.gramophone.glide.CustomGlideRequest;
|
|||
import com.dkanada.gramophone.helper.ShuffleHelper;
|
||||
import com.dkanada.gramophone.model.Playlist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.provider.QueueStore;
|
||||
import com.dkanada.gramophone.service.notification.PlayingNotification;
|
||||
import com.dkanada.gramophone.service.notification.PlayingNotificationImpl;
|
||||
import com.dkanada.gramophone.service.notification.PlayingNotificationImpl24;
|
||||
|
|
@ -395,7 +394,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
}
|
||||
|
||||
private void saveQueue() {
|
||||
QueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue);
|
||||
App.getDatabase().songDao().deleteSongs();
|
||||
App.getDatabase().songDao().insertSongs(playingQueue);
|
||||
|
||||
App.getDatabase().queueSongDao().deleteQueueSongs();
|
||||
App.getDatabase().queueSongDao().setQueue(playingQueue, 0);
|
||||
App.getDatabase().queueSongDao().setQueue(originalPlayingQueue, 1);
|
||||
}
|
||||
|
||||
private void savePosition() {
|
||||
|
|
@ -427,8 +431,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
|
||||
private synchronized void restoreQueuesAndPositionIfNecessary() {
|
||||
if (!queuesRestored && playingQueue.isEmpty()) {
|
||||
List<Song> restoredQueue = QueueStore.getInstance(this).getSavedPlayingQueue();
|
||||
List<Song> restoredOriginalQueue = QueueStore.getInstance(this).getSavedOriginalPlayingQueue();
|
||||
List<Song> restoredQueue = App.getDatabase().queueSongDao().getQueue(0);
|
||||
List<Song> restoredOriginalQueue = App.getDatabase().queueSongDao().getQueue(1);
|
||||
|
||||
int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(PreferenceUtil.POSITION, -1);
|
||||
int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(PreferenceUtil.PROGRESS, -1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue