ADD: Load Remote Songs from Server
This commit is contained in:
parent
a53bc4bee7
commit
d059f7ed8e
@ -69,4 +69,13 @@ public interface SongDao {
|
|||||||
|
|
||||||
@Query("SELECT * FROM NoteSheet WHERE songID IN (:localSongIDs)")
|
@Query("SELECT * FROM NoteSheet WHERE songID IN (:localSongIDs)")
|
||||||
List<NoteSheet> getNoteSheetFilesBySongIDs(List<Integer> localSongIDs);
|
List<NoteSheet> getNoteSheetFilesBySongIDs(List<Integer> localSongIDs);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
void insertSongs(List<Song> onlyRemoteExistingSongs);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
void deleteSong(Song song);
|
||||||
|
|
||||||
|
@Query("DELETE FROM NoteSheet WHERE songID = :localID")
|
||||||
|
void deleteNoteSheetsByLocalSongID(int localID);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import androidx.room.Entity;
|
|||||||
import androidx.room.Ignore;
|
import androidx.room.Ignore;
|
||||||
import androidx.room.PrimaryKey;
|
import androidx.room.PrimaryKey;
|
||||||
import com.stormtales.notevault.data.sync.SyncStatus;
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
|
import com.stormtales.notevault.network.sync.models.SongModel;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -32,6 +33,17 @@ public class Song {
|
|||||||
this.syncStatus = SyncStatus.CREATED;
|
this.syncStatus = SyncStatus.CREATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public Song(SongModel songModel) {
|
||||||
|
this.serverID = songModel.getServerID();
|
||||||
|
this.title = songModel.getTitle();
|
||||||
|
this.composer = songModel.getComposer();
|
||||||
|
this.genre = songModel.getGenre();
|
||||||
|
this.year = songModel.getYear();
|
||||||
|
this.syncStatus = SyncStatus.SYNCED;
|
||||||
|
this.syncTime = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
public Song() {
|
public Song() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,8 +10,10 @@ import com.stormtales.notevault.data.entities.NoteSheet;
|
|||||||
import com.stormtales.notevault.data.entities.Song;
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
import com.stormtales.notevault.data.sync.SyncStatus;
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
import com.stormtales.notevault.network.sync.models.*;
|
import com.stormtales.notevault.network.sync.models.*;
|
||||||
|
import com.stormtales.notevault.utils.NoteSheetsUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -173,6 +175,81 @@ public class SongSyncRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadRemotelyModifiedSongs(LoadDataCallback<List<Song>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<Song> songs = songDao.getSongsBySyncStatus(SyncStatus.REMOTE_MODIFIED);
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(songs));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveRemoteSongs(List<SongModel> remoteSongs, LoadDataCallback<List<NoteSheet>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() ->{
|
||||||
|
List<String> serverIDs = remoteSongs.stream().map(SongModel::getServerID).collect(Collectors.toList());
|
||||||
|
List<Song> songs = songDao.getSongsByServerIDs(serverIDs);
|
||||||
|
List<Integer> localSongIDs = songDao.getLocalSongIDsByServerIDs(serverIDs);
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySongIDs(localSongIDs);
|
||||||
|
|
||||||
|
List<Song> onlyRemoteExistingSongs = new ArrayList<>();
|
||||||
|
List<NoteSheet> outdatedNoteSheets = new ArrayList<>();
|
||||||
|
for(SongModel songModel : remoteSongs) {
|
||||||
|
boolean found = false;
|
||||||
|
for(Song song : songs) {
|
||||||
|
if(song.getServerID().equals(songModel.getServerID())) {
|
||||||
|
found = true;
|
||||||
|
if(!songModel.isDeleted()) {
|
||||||
|
song.setSyncStatus(SyncStatus.SYNCED);
|
||||||
|
song.setSyncTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
song.setTitle(songModel.getTitle());
|
||||||
|
song.setComposer(songModel.getComposer());
|
||||||
|
song.setGenre(songModel.getGenre());
|
||||||
|
song.setYear(songModel.getYear());
|
||||||
|
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
for(RemotelyModifiedNoteSheetModel remoteNoteSheet : songModel.getNote_sheets()) {
|
||||||
|
if(remoteNoteSheet.getServer_filename().equals(noteSheet.getServerFileName())) {
|
||||||
|
if(!remoteNoteSheet.getHash().equals(noteSheet.getHash())) {
|
||||||
|
outdatedNoteSheets.add(noteSheet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removeSong(song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found) {
|
||||||
|
Song song = new Song(songModel);
|
||||||
|
onlyRemoteExistingSongs.add(song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
songDao.updateSongs(songs);
|
||||||
|
songDao.insertSongs(onlyRemoteExistingSongs);
|
||||||
|
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(outdatedNoteSheets));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSong(Song song) {
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySong(song.getLocalID());
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
try {
|
||||||
|
NoteSheetsUtil.deleteNoteSheet(noteSheet.getLocalFileName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
songDao.deleteNoteSheets(noteSheets);
|
||||||
|
songDao.deleteSong(song);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public interface LoadDataCallback<T> {
|
public interface LoadDataCallback<T> {
|
||||||
void onResult(T result);
|
void onResult(T result);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import com.stormtales.notevault.network.auth.LoginResponse;
|
|||||||
import com.stormtales.notevault.network.sync.models.*;
|
import com.stormtales.notevault.network.sync.models.*;
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.*;
|
import retrofit2.http.*;
|
||||||
|
|
||||||
@ -31,4 +32,10 @@ public interface SongSyncAPI {
|
|||||||
|
|
||||||
@GET("/sync/songs/fetch")
|
@GET("/sync/songs/fetch")
|
||||||
Call<FetchResponse> fetchRemoteModifiedSongs(@Query(value = "last_client_sync") String last_client_sync);
|
Call<FetchResponse> fetchRemoteModifiedSongs(@Query(value = "last_client_sync") String last_client_sync);
|
||||||
|
|
||||||
|
@GET("/sync/songs/get")
|
||||||
|
Call<SongModel> fetchRemotelyModifiedSongData(@Query(value = "songID") String songID);
|
||||||
|
|
||||||
|
@GET("/sync/songs/get/notesheet/")
|
||||||
|
Call<ResponseBody> downloadNotesheet(@Query("server_filename") String server_filename);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package com.stormtales.notevault.network.sync;
|
package com.stormtales.notevault.network.sync;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
import com.stormtales.notevault.data.entities.Song;
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
import com.stormtales.notevault.data.repositories.SongRepository;
|
import com.stormtales.notevault.data.repositories.SongRepository;
|
||||||
import com.stormtales.notevault.data.repositories.SongSyncRepository;
|
import com.stormtales.notevault.data.repositories.SongSyncRepository;
|
||||||
import com.stormtales.notevault.network.sync.models.*;
|
import com.stormtales.notevault.network.sync.models.*;
|
||||||
import com.stormtales.notevault.ui.gallery.GalleryViewModel;
|
import com.stormtales.notevault.ui.gallery.GalleryViewModel;
|
||||||
|
import com.stormtales.notevault.utils.NoteSheetsUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -17,12 +21,14 @@ public class SongSyncModule {
|
|||||||
private SongSyncRepository songSyncRepository;
|
private SongSyncRepository songSyncRepository;
|
||||||
private SongSyncService songSyncService;
|
private SongSyncService songSyncService;
|
||||||
private GalleryViewModel syncViewModel;
|
private GalleryViewModel syncViewModel;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
public SongSyncModule(Context context, GalleryViewModel syncViewModel) {
|
public SongSyncModule(Context context, GalleryViewModel syncViewModel) {
|
||||||
this.songRepository = new SongRepository(context);
|
this.songRepository = new SongRepository(context);
|
||||||
this.songSyncRepository = new SongSyncRepository(context);
|
this.songSyncRepository = new SongSyncRepository(context);
|
||||||
this.songSyncService = new SongSyncService(context);
|
this.songSyncService = new SongSyncService(context);
|
||||||
this.syncViewModel = syncViewModel;
|
this.syncViewModel = syncViewModel;
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void syncCreatedSongs() {
|
public void syncCreatedSongs() {
|
||||||
@ -84,11 +90,32 @@ public class SongSyncModule {
|
|||||||
@Override
|
@Override
|
||||||
public void onResult(FetchResponse result) {
|
public void onResult(FetchResponse result) {
|
||||||
songSyncRepository.markSongsAsRemotelyModified(result.getServerIDs());
|
songSyncRepository.markSongsAsRemotelyModified(result.getServerIDs());
|
||||||
syncViewModel.finishFetching();
|
getRemotelyModifiedSongData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void getRemotelyModifiedSongData() {
|
||||||
|
songSyncRepository.loadRemotelyModifiedSongs(songs -> {
|
||||||
|
songSyncService.getRemotelyModifiedSongData(songs, remoteSongs -> {
|
||||||
|
songSyncRepository.saveRemoteSongs(remoteSongs, this::downloadNoteSheets);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadNoteSheets(List<NoteSheet> noteSheets) {
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
songSyncService.downloadNoteSheet(noteSheet, responseBody -> {
|
||||||
|
String localFilename = noteSheet.getServerFileName().substring(36);
|
||||||
|
|
||||||
|
NoteSheetsUtil.saveImageFromServer(responseBody, context.getFilesDir());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
syncViewModel.finishFetching();
|
||||||
|
}
|
||||||
|
|
||||||
public interface FinishSongCreateSyncingCallback {
|
public interface FinishSongCreateSyncingCallback {
|
||||||
void finishSongSyncing(BatchCreateResponse response);
|
void finishSongSyncing(BatchCreateResponse response);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import com.stormtales.notevault.ui.gallery.GalleryViewModel;
|
|||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
@ -176,6 +177,44 @@ public class SongSyncService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void getRemotelyModifiedSongData(List<Song> remotelyModifiedSongs, SongSyncRepository.LoadDataCallback<List<SongModel>> callback) {
|
||||||
|
List<SongModel> songModels = new ArrayList<>();
|
||||||
|
for(Song song : remotelyModifiedSongs) {
|
||||||
|
songSyncAPI.fetchRemotelyModifiedSongData(song.getServerID()).enqueue(new Callback<SongModel>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<SongModel> call, Response<SongModel> response) {
|
||||||
|
if(response.isSuccessful() && response.body() != null) {
|
||||||
|
songModels.add(response.body());
|
||||||
|
if(songModels.size() == remotelyModifiedSongs.size()) {
|
||||||
|
callback.onResult(songModels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<SongModel> call, Throwable throwable) {
|
||||||
|
Log.d("SongSyncService", "Fetch failed: " + throwable.getMessage(), throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadNoteSheet(NoteSheet noteSheet, SongSyncRepository.LoadDataCallback<ResponseBody> callback) {
|
||||||
|
songSyncAPI.downloadNotesheet(noteSheet.getServerFileName()).enqueue(new Callback<ResponseBody>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
|
||||||
|
if(response.isSuccessful() && response.body() != null) {
|
||||||
|
callback.onResult(response.body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ResponseBody> call, Throwable throwable) {
|
||||||
|
Log.d("SongSyncService", "Download failed: " + throwable.getMessage(), throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public interface SyncDeletedSongsCallback {
|
public interface SyncDeletedSongsCallback {
|
||||||
void finishSongSyncing(List<String> remoteDeletedSongs, List<Integer> localDeletedSongs);
|
void finishSongSyncing(List<String> remoteDeletedSongs, List<Integer> localDeletedSongs);
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
public class RemotelyModifiedNoteSheetModel {
|
||||||
|
private String filename;
|
||||||
|
private String server_filename;
|
||||||
|
private String hash;
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilename(String filename) {
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServer_filename() {
|
||||||
|
return server_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServer_filename(String server_filename) {
|
||||||
|
this.server_filename = server_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHash(String hash) {
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SongModel {
|
||||||
|
private String serverID;
|
||||||
|
private String title;
|
||||||
|
private String composer;
|
||||||
|
private String genre;
|
||||||
|
private int year;
|
||||||
|
private List<RemotelyModifiedNoteSheetModel> note_sheets;
|
||||||
|
private boolean deleted;
|
||||||
|
|
||||||
|
public String getServerID() {
|
||||||
|
return serverID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerID(String serverID) {
|
||||||
|
this.serverID = serverID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComposer() {
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComposer(String composer) {
|
||||||
|
this.composer = composer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGenre() {
|
||||||
|
return genre;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGenre(String genre) {
|
||||||
|
this.genre = genre;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getYear() {
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setYear(int year) {
|
||||||
|
this.year = year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RemotelyModifiedNoteSheetModel> getNote_sheets() {
|
||||||
|
return note_sheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNote_sheets(List<RemotelyModifiedNoteSheetModel> note_sheets) {
|
||||||
|
this.note_sheets = note_sheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleted() {
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleted(boolean deleted) {
|
||||||
|
this.deleted = deleted;
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,9 @@ import android.content.ContentResolver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.ExifInterface;
|
import android.media.ExifInterface;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@ -85,6 +83,34 @@ public class NoteSheetsUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Tupel<String, String> saveImageFromServer(ResponseBody body, File filesDir) {
|
||||||
|
File imageFile = new File(filesDir, "saved_image_" + System.currentTimeMillis() + ".jpg");
|
||||||
|
try (InputStream inputStream = body.byteStream();
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(imageFile)) {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
|
||||||
|
// Daten blockweise lesen und gleichzeitig schreiben und hashen
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
digest.update(buffer, 0, bytesRead); // Hash aktualisieren
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash berechnen
|
||||||
|
byte[] hashBytes = digest.digest();
|
||||||
|
StringBuilder hashString = new StringBuilder();
|
||||||
|
for (byte b : hashBytes) {
|
||||||
|
hashString.append(String.format("%02x", b)); // Bytes in Hexadezimal umwandeln
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashwert zurückgeben (kann auch zusammen mit dem Pfad gespeichert werden)
|
||||||
|
return new Tupel<>(imageFile.getAbsolutePath(), hashString.toString());
|
||||||
|
} catch (IOException | NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void deleteNoteSheet(String filePath) throws IOException {
|
public static void deleteNoteSheet(String filePath) throws IOException {
|
||||||
Files.delete(new File(filePath).toPath());
|
Files.delete(new File(filePath).toPath());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user