diff --git a/app/src/main/java/com/stormtales/notevault/data/dao/SongDao.java b/app/src/main/java/com/stormtales/notevault/data/dao/SongDao.java index e108bee..ede1d48 100644 --- a/app/src/main/java/com/stormtales/notevault/data/dao/SongDao.java +++ b/app/src/main/java/com/stormtales/notevault/data/dao/SongDao.java @@ -69,4 +69,13 @@ public interface SongDao { @Query("SELECT * FROM NoteSheet WHERE songID IN (:localSongIDs)") List getNoteSheetFilesBySongIDs(List localSongIDs); + + @Insert + void insertSongs(List onlyRemoteExistingSongs); + + @Delete + void deleteSong(Song song); + + @Query("DELETE FROM NoteSheet WHERE songID = :localID") + void deleteNoteSheetsByLocalSongID(int localID); } diff --git a/app/src/main/java/com/stormtales/notevault/data/entities/Song.java b/app/src/main/java/com/stormtales/notevault/data/entities/Song.java index a359e5c..e960a03 100644 --- a/app/src/main/java/com/stormtales/notevault/data/entities/Song.java +++ b/app/src/main/java/com/stormtales/notevault/data/entities/Song.java @@ -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() { } diff --git a/app/src/main/java/com/stormtales/notevault/data/repositories/SongSyncRepository.java b/app/src/main/java/com/stormtales/notevault/data/repositories/SongSyncRepository.java index f17708c..4924e6f 100644 --- a/app/src/main/java/com/stormtales/notevault/data/repositories/SongSyncRepository.java +++ b/app/src/main/java/com/stormtales/notevault/data/repositories/SongSyncRepository.java @@ -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> callback) { + Executors.newSingleThreadExecutor().execute(() -> { + List songs = songDao.getSongsBySyncStatus(SyncStatus.REMOTE_MODIFIED); + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(()-> callback.onResult(songs)); + }); + } + + public void saveRemoteSongs(List remoteSongs, LoadDataCallback> callback) { + Executors.newSingleThreadExecutor().execute(() ->{ + List serverIDs = remoteSongs.stream().map(SongModel::getServerID).collect(Collectors.toList()); + List songs = songDao.getSongsByServerIDs(serverIDs); + List localSongIDs = songDao.getLocalSongIDsByServerIDs(serverIDs); + List noteSheets = songDao.getNoteSheetsBySongIDs(localSongIDs); + + List onlyRemoteExistingSongs = new ArrayList<>(); + List 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 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 { void onResult(T result); } diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncAPI.java b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncAPI.java index 52306fd..616997e 100644 --- a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncAPI.java +++ b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncAPI.java @@ -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 fetchRemoteModifiedSongs(@Query(value = "last_client_sync") String last_client_sync); + + @GET("/sync/songs/get") + Call fetchRemotelyModifiedSongData(@Query(value = "songID") String songID); + + @GET("/sync/songs/get/notesheet/") + Call downloadNotesheet(@Query("server_filename") String server_filename); } diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncModule.java b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncModule.java index e79a81a..ebf64b2 100644 --- a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncModule.java +++ b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncModule.java @@ -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 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); } diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncService.java b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncService.java index d8f390b..e1cf87a 100644 --- a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncService.java +++ b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncService.java @@ -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 remotelyModifiedSongs, SongSyncRepository.LoadDataCallback> callback) { + List songModels = new ArrayList<>(); + for(Song song : remotelyModifiedSongs) { + songSyncAPI.fetchRemotelyModifiedSongData(song.getServerID()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if(response.isSuccessful() && response.body() != null) { + songModels.add(response.body()); + if(songModels.size() == remotelyModifiedSongs.size()) { + callback.onResult(songModels); + } + } + } + + @Override + public void onFailure(Call call, Throwable throwable) { + Log.d("SongSyncService", "Fetch failed: " + throwable.getMessage(), throwable); + } + }); + } + } + + public void downloadNoteSheet(NoteSheet noteSheet, SongSyncRepository.LoadDataCallback callback) { + songSyncAPI.downloadNotesheet(noteSheet.getServerFileName()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if(response.isSuccessful() && response.body() != null) { + callback.onResult(response.body()); + } + } + + @Override + public void onFailure(Call call, Throwable throwable) { + Log.d("SongSyncService", "Download failed: " + throwable.getMessage(), throwable); + } + }); + } + public interface SyncDeletedSongsCallback { void finishSongSyncing(List remoteDeletedSongs, List localDeletedSongs); diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/models/RemotelyModifiedNoteSheetModel.java b/app/src/main/java/com/stormtales/notevault/network/sync/models/RemotelyModifiedNoteSheetModel.java new file mode 100644 index 0000000..c52a224 --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/models/RemotelyModifiedNoteSheetModel.java @@ -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; + } +} diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/models/SongModel.java b/app/src/main/java/com/stormtales/notevault/network/sync/models/SongModel.java new file mode 100644 index 0000000..4de962a --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/models/SongModel.java @@ -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 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 getNote_sheets() { + return note_sheets; + } + + public void setNote_sheets(List note_sheets) { + this.note_sheets = note_sheets; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } +} diff --git a/app/src/main/java/com/stormtales/notevault/utils/NoteSheetsUtil.java b/app/src/main/java/com/stormtales/notevault/utils/NoteSheetsUtil.java index d830504..fa66d89 100644 --- a/app/src/main/java/com/stormtales/notevault/utils/NoteSheetsUtil.java +++ b/app/src/main/java/com/stormtales/notevault/utils/NoteSheetsUtil.java @@ -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 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()); }