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)")
|
||||
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.PrimaryKey;
|
||||
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||
import com.stormtales.notevault.network.sync.models.SongModel;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
@ -32,6 +33,17 @@ public class Song {
|
||||
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() {
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,10 @@ import com.stormtales.notevault.data.entities.NoteSheet;
|
||||
import com.stormtales.notevault.data.entities.Song;
|
||||
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||
import com.stormtales.notevault.network.sync.models.*;
|
||||
import com.stormtales.notevault.utils.NoteSheetsUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
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> {
|
||||
void onResult(T result);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import com.stormtales.notevault.network.auth.LoginResponse;
|
||||
import com.stormtales.notevault.network.sync.models.*;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.*;
|
||||
|
||||
@ -31,4 +32,10 @@ public interface SongSyncAPI {
|
||||
|
||||
@GET("/sync/songs/fetch")
|
||||
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;
|
||||
|
||||
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.repositories.SongRepository;
|
||||
import com.stormtales.notevault.data.repositories.SongSyncRepository;
|
||||
import com.stormtales.notevault.network.sync.models.*;
|
||||
import com.stormtales.notevault.ui.gallery.GalleryViewModel;
|
||||
import com.stormtales.notevault.utils.NoteSheetsUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -17,12 +21,14 @@ public class SongSyncModule {
|
||||
private SongSyncRepository songSyncRepository;
|
||||
private SongSyncService songSyncService;
|
||||
private GalleryViewModel syncViewModel;
|
||||
private Context context;
|
||||
|
||||
public SongSyncModule(Context context, GalleryViewModel syncViewModel) {
|
||||
this.songRepository = new SongRepository(context);
|
||||
this.songSyncRepository = new SongSyncRepository(context);
|
||||
this.songSyncService = new SongSyncService(context);
|
||||
this.syncViewModel = syncViewModel;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void syncCreatedSongs() {
|
||||
@ -84,11 +90,32 @@ public class SongSyncModule {
|
||||
@Override
|
||||
public void onResult(FetchResponse result) {
|
||||
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 {
|
||||
void finishSongSyncing(BatchCreateResponse response);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import com.stormtales.notevault.ui.gallery.GalleryViewModel;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
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 {
|
||||
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.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
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 {
|
||||
Files.delete(new File(filePath).toPath());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user