ADD: Handle Modified NoteSheeds

This commit is contained in:
Fawkes100 2025-01-19 13:41:09 +01:00
parent d221502bef
commit 007f6a8501
10 changed files with 224 additions and 29 deletions

View File

@ -60,4 +60,13 @@ public interface SongDao {
@Query("SELECT localID FROM Song WHERE serverID IN (:serverIDs)") @Query("SELECT localID FROM Song WHERE serverID IN (:serverIDs)")
List<Integer> getLocalSongIDsByServerIDs(List<String> serverIDs); List<Integer> getLocalSongIDsByServerIDs(List<String> serverIDs);
@Query("SELECT * FROM NoteSheet WHERE serverFileName IN (:serverFileNames)")
List<NoteSheet> getNoteSheetsByServerFileNames(List<String> serverFileNames);
@Query("SELECT * FROM NoteSheet WHERE syncStatus = :status")
List<NoteSheet> getNoteSheetsBySyncStatus(SyncStatus status);
@Query("SELECT * FROM NoteSheet WHERE songID IN (:localSongIDs)")
List<NoteSheet> getNoteSheetFilesBySongIDs(List<Integer> localSongIDs);
} }

View File

@ -2,6 +2,7 @@ package com.stormtales.notevault.data.entities;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import com.stormtales.notevault.data.sync.SyncStatus;
import java.util.Objects; import java.util.Objects;
@ -14,9 +15,12 @@ public class NoteSheet {
private String serverFileName; private String serverFileName;
private String hash; private String hash;
private SyncStatus syncStatus;
public NoteSheet(String localFileName, String hash) { public NoteSheet(String localFileName, String hash) {
this.localFileName = localFileName; this.localFileName = localFileName;
this.hash = hash; this.hash = hash;
this.syncStatus = SyncStatus.CREATED;
} }
public int getLocalID() { public int getLocalID() {
@ -70,4 +74,12 @@ public class NoteSheet {
public void setSongID(int songID) { public void setSongID(int songID) {
this.songID = songID; this.songID = songID;
} }
public SyncStatus getSyncStatus() {
return syncStatus;
}
public void setSyncStatus(SyncStatus syncStatus) {
this.syncStatus = syncStatus;
}
} }

View File

@ -3,20 +3,20 @@ package com.stormtales.notevault.data.repositories;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import androidx.recyclerview.widget.AsyncListUtil;
import com.stormtales.notevault.data.MusicDatabase; import com.stormtales.notevault.data.MusicDatabase;
import com.stormtales.notevault.data.dao.SongDao; import com.stormtales.notevault.data.dao.SongDao;
import com.stormtales.notevault.data.entities.NoteSheet; 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.BatchCreateResponse; import com.stormtales.notevault.network.sync.models.*;
import com.stormtales.notevault.network.sync.models.BatchModifyResponse;
import com.stormtales.notevault.network.sync.models.CreateResponse;
import com.stormtales.notevault.network.sync.models.UploadResponse;
import java.io.File; import java.io.File;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -36,11 +36,16 @@ public class SongSyncRepository {
}); });
} }
public void loadModifiedSongs(LoadDataCallback<List<Song>> callback) { public void loadModifiedSongs(LoadDataCallback<Map<Song, List<NoteSheet>>> callback) {
Executors.newSingleThreadExecutor().execute(() -> { Executors.newSingleThreadExecutor().execute(() -> {
Map<Song, List<NoteSheet>> result = new HashMap<>();
List<Song> modifiedSongs = songDao.getSongsBySyncStatus(SyncStatus.MODIFIED); List<Song> modifiedSongs = songDao.getSongsBySyncStatus(SyncStatus.MODIFIED);
for(Song song : modifiedSongs) {
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySong(song.getLocalID());
result.put(song, noteSheets);
}
Handler mainHandler = new Handler(Looper.getMainLooper()); Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(()-> callback.onResult(modifiedSongs)); mainHandler.post(()-> callback.onResult(result));
}); });
} }
@ -78,13 +83,27 @@ public class SongSyncRepository {
} }
} }
public void markModifiedSongsAsSynced(BatchModifyResponse response) { public void markModifiedSongsAsSynced(SongModifyBatchResponse modifyBatchResponse) {
Executors.newSingleThreadExecutor().execute(() -> { Executors.newSingleThreadExecutor().execute(() -> {
List<Song> requestedSongs = songDao.getSongsByServerIDs(response.getModifiedServerObjects()); List<String> serverIDs = modifyBatchResponse.getModifiedServerObjects().stream().map(SongModifyResponse::getServerID).collect(Collectors.toList());
List<Song> requestedSongs = songDao.getSongsByServerIDs(serverIDs);
for(Song song : requestedSongs) { for(Song song : requestedSongs) {
song.setSyncStatus(SyncStatus.SYNCED); song.setSyncStatus(SyncStatus.SYNCED);
song.setSyncTime(LocalDateTime.now()); song.setSyncTime(LocalDateTime.now());
for(SongModifyResponse modifyResponse : modifyBatchResponse.getModifiedServerObjects()) {
if(modifyResponse.getServerID().equals(song.getServerID())) {
List<NoteSheet> outdatedNoteSheets = songDao.getNoteSheetsByServerFileNames(modifyResponse.getOutdated_note_sheets());
for(NoteSheet noteSheet : outdatedNoteSheets) {
noteSheet.setSyncStatus(SyncStatus.MODIFIED);
} }
songDao.updateNoteSheets(outdatedNoteSheets);
}
}
}
songDao.updateSongs(requestedSongs); songDao.updateSongs(requestedSongs);
}); });
} }
@ -96,9 +115,9 @@ public class SongSyncRepository {
}); });
} }
public void getNoteSheetFilesBySongIDs(List<Integer> songIDs, LoadDataCallback<List<NoteSheet>> callback) { public void getNoteSheetFilesBySongIDs(LoadDataCallback<List<NoteSheet>> callback) {
Executors.newSingleThreadExecutor().execute(() -> { Executors.newSingleThreadExecutor().execute(() -> {
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySongIDs(songIDs); List<NoteSheet> noteSheets = songDao.getNoteSheetsBySyncStatus(SyncStatus.CREATED);
Handler mainHandler = new Handler(Looper.getMainLooper()); Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(()-> callback.onResult(noteSheets)); mainHandler.post(()-> callback.onResult(noteSheets));
}); });
@ -114,6 +133,7 @@ public class SongSyncRepository {
for(NoteSheet noteSheet : uploadedNoteSheets) { for(NoteSheet noteSheet : uploadedNoteSheets) {
if(new File(noteSheet.getLocalFileName()).getName().equals(uploadResponse.getLocalFile())) { if(new File(noteSheet.getLocalFileName()).getName().equals(uploadResponse.getLocalFile())) {
noteSheet.setServerFileName(uploadResponse.getServerFile()); noteSheet.setServerFileName(uploadResponse.getServerFile());
noteSheet.setSyncStatus(SyncStatus.SYNCED);
break; break;
} }
} }
@ -122,6 +142,27 @@ public class SongSyncRepository {
}); });
} }
public void loadModifiedNoteSheets(LoadDataCallback<List<NoteSheet>> callback) {
Executors.newSingleThreadExecutor().execute(() -> {
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySyncStatus(SyncStatus.MODIFIED);
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(()-> callback.onResult(noteSheets));
});
}
public void markModifiedNoteSheetsAsSynced(List<UploadResponse> uploadResponses) {
Executors.newSingleThreadExecutor().execute(() -> {
List<String> serverFileNames = uploadResponses.stream().map(UploadResponse::getServerFile).collect(Collectors.toList());
List<NoteSheet> noteSheets = songDao.getNoteSheetsByServerFileNames(serverFileNames);
for(NoteSheet noteSheet : noteSheets) {
noteSheet.setSyncStatus(SyncStatus.SYNCED);
}
songDao.updateNoteSheets(noteSheets);
});
}
public interface LoadDataCallback<T> { public interface LoadDataCallback<T> {
void onResult(T result); void onResult(T result);
} }

View File

@ -17,7 +17,7 @@ public interface SongSyncAPI {
Call<BatchCreateResponse> syncCreatedSongs(@Body SongCreateBatchRequest songCreateBatchRequest); Call<BatchCreateResponse> syncCreatedSongs(@Body SongCreateBatchRequest songCreateBatchRequest);
@POST("/sync/songs/modify") @POST("/sync/songs/modify")
Call<BatchModifyResponse> syncModifiedSongs(@Body SongModifyBatchRequest songModifyBatchRequest); Call<SongModifyBatchResponse> syncModifiedSongs(@Body SongModifyBatchRequest songModifyBatchRequest);
@POST("/sync/songs/delete") @POST("/sync/songs/delete")
Call<BatchModifyResponse> syncDeletedSongs(@Body SongBatchDeleteRequest songBatchDeleteRequest); Call<BatchModifyResponse> syncDeletedSongs(@Body SongBatchDeleteRequest songBatchDeleteRequest);
@ -25,4 +25,8 @@ public interface SongSyncAPI {
@Multipart @Multipart
@POST("/sync/songs/note_sheet/upload") @POST("/sync/songs/note_sheet/upload")
Call<UploadResponse> uploadNoteSheet(@Part("serverID")RequestBody serverID, @Part("fileName") RequestBody fileName, @Part MultipartBody.Part image); Call<UploadResponse> uploadNoteSheet(@Part("serverID")RequestBody serverID, @Part("fileName") RequestBody fileName, @Part MultipartBody.Part image);
@Multipart
@POST("/sync/songs/note_sheet/update")
Call<UploadResponse> updateNoteSheet(@Part("server_filename") RequestBody server_filename, @Part MultipartBody.Part image);
} }

View File

@ -6,6 +6,7 @@ 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.BatchCreateResponse; import com.stormtales.notevault.network.sync.models.BatchCreateResponse;
import com.stormtales.notevault.network.sync.models.BatchModifyResponse; import com.stormtales.notevault.network.sync.models.BatchModifyResponse;
import com.stormtales.notevault.network.sync.models.SongModifyBatchResponse;
import com.stormtales.notevault.network.sync.models.UploadResponse; import com.stormtales.notevault.network.sync.models.UploadResponse;
import com.stormtales.notevault.ui.gallery.GalleryViewModel; import com.stormtales.notevault.ui.gallery.GalleryViewModel;
@ -33,23 +34,39 @@ public class SongSyncModule {
if(response.getCreateResponses().isEmpty()) { if(response.getCreateResponses().isEmpty()) {
syncViewModel.finishCreateSongSyncing(); syncViewModel.finishCreateSongSyncing();
} else { } else {
uploadCreatedNoteSheets(result, response);
}
});
});
}
public void uploadCreatedNoteSheets(List<Song> result, BatchCreateResponse response) {
List<Integer> songIDs = result.stream().map(Song::getLocalID).collect(Collectors.toList()); List<Integer> songIDs = result.stream().map(Song::getLocalID).collect(Collectors.toList());
songSyncRepository.getNoteSheetFilesBySongIDs(songIDs, noteSheets -> { songSyncRepository.getNoteSheetFilesBySongIDs(noteSheets -> {
songSyncService.uploadNoteSheetsOfCreatedSongs(noteSheets, response, uploadResponses -> { songSyncService.uploadNoteSheetsOfCreatedSongs(noteSheets, response, uploadResponses -> {
songSyncRepository.markCreatedNoteSheetsAsSynced(uploadResponses); songSyncRepository.markCreatedNoteSheetsAsSynced(uploadResponses);
syncViewModel.finishCreateSongSyncing(); syncViewModel.finishCreateSongSyncing();
}); });
}); });
} }
public void syncModifiedSongs() {
songSyncRepository.loadModifiedSongs(result -> {
songSyncService.syncModifiedSongs(result, (FinishSongSyncingCallback<SongModifyBatchResponse>) response -> {
songSyncRepository.markModifiedSongsAsSynced(response);
uploadModifiedNoteSheets();
}); });
}); });
} }
public void syncModifiedSongs() { public void uploadModifiedNoteSheets() {
songSyncRepository.loadModifiedSongs(result -> { songSyncRepository.loadModifiedNoteSheets(modifiedNoteSheets -> {
songSyncService.syncModifiedSongs(result, response -> { songSyncService.uploadModifiedNoteSheets(modifiedNoteSheets, new SongSyncService.UploadNoteSheetCallback() {
songSyncRepository.markModifiedSongsAsSynced(response); @Override
public void finishUploadNoteSheets(List<UploadResponse> uploadResponses) {
songSyncRepository.markModifiedNoteSheetsAsSynced(uploadResponses);
syncViewModel.finishModifiedSongSyncinc(); syncViewModel.finishModifiedSongSyncinc();
}
}); });
}); });
} }
@ -67,7 +84,7 @@ public class SongSyncModule {
void finishSongSyncing(BatchCreateResponse response); void finishSongSyncing(BatchCreateResponse response);
} }
public interface FinishSongSyncingCallback { public interface FinishSongSyncingCallback<T> {
void finishSongSyncing(BatchModifyResponse response); void finishSongSyncing(T response);
} }
} }

View File

@ -1,6 +1,7 @@
package com.stormtales.notevault.network.sync; package com.stormtales.notevault.network.sync;
import android.content.Context; import android.content.Context;
import android.util.Log;
import com.stormtales.notevault.data.entities.NoteSheet; 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.network.NetworkModule; import com.stormtales.notevault.network.NetworkModule;
@ -17,6 +18,7 @@ import retrofit2.Response;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
public class SongSyncService { public class SongSyncService {
private final SongSyncAPI songSyncAPI; private final SongSyncAPI songSyncAPI;
@ -79,20 +81,20 @@ public class SongSyncService {
} }
} }
public void syncModifiedSongs(List<Song> songs, SongSyncModule.FinishSongSyncingCallback callback) { public void syncModifiedSongs(Map<Song, List<NoteSheet>> songs, SongSyncModule.FinishSongSyncingCallback<SongModifyBatchResponse> callback) {
List<SongModifyRequest> modifyRequests = new ArrayList<>(); List<SongModifyRequest> modifyRequests = new ArrayList<>();
for(Song song : songs) { for(Map.Entry<Song, List<NoteSheet>> entry: songs.entrySet()) {
modifyRequests.add(new SongModifyRequest(song)); modifyRequests.add(new SongModifyRequest(entry.getKey(), entry.getValue()));
} }
SongModifyBatchRequest songModifyBatchRequest = new SongModifyBatchRequest(modifyRequests); SongModifyBatchRequest songModifyBatchRequest = new SongModifyBatchRequest(modifyRequests);
songSyncAPI.syncModifiedSongs(songModifyBatchRequest).enqueue(new Callback<BatchModifyResponse>() { songSyncAPI.syncModifiedSongs(songModifyBatchRequest).enqueue(new Callback<SongModifyBatchResponse>() {
@Override @Override
public void onResponse(Call<BatchModifyResponse> call, Response<BatchModifyResponse> response) { public void onResponse(Call<SongModifyBatchResponse> call, Response<SongModifyBatchResponse> response) {
callback.finishSongSyncing(response.body()); callback.finishSongSyncing(response.body());
} }
@Override @Override
public void onFailure(Call<BatchModifyResponse> call, Throwable throwable) { public void onFailure(Call<SongModifyBatchResponse> call, Throwable throwable) {
} }
}); });
@ -125,6 +127,34 @@ public class SongSyncService {
}); });
} }
public void uploadModifiedNoteSheets(List<NoteSheet> modifiedNoteSheets, UploadNoteSheetCallback callback) {
List<UploadResponse> uploadResponses = new ArrayList<>();
for(NoteSheet noteSheet : modifiedNoteSheets) {
RequestBody server_filename = RequestBody.create(MediaType.parse("text/plain"), noteSheet.getServerFileName());
RequestBody requestFile = RequestBody.create(MediaType.parse("image/**"), new File(noteSheet.getLocalFileName()));
MultipartBody.Part image = MultipartBody.Part.createFormData("image", noteSheet.getLocalFileName(), requestFile);
songSyncAPI.updateNoteSheet(server_filename, image).enqueue(new Callback<UploadResponse>() {
@Override
public void onResponse(Call<UploadResponse> call, Response<UploadResponse> response) {
if(response.isSuccessful() && response.body() != null) {
uploadResponses.add(response.body());
if(uploadResponses.size() == modifiedNoteSheets.size()) {
callback.finishUploadNoteSheets(uploadResponses);
}
} else {
Log.d("SongSyncService", "Something went wrong");
}
}
@Override
public void onFailure(Call<UploadResponse> call, Throwable throwable) {
Log.e("SongSyncService", "Upload failed: " + throwable.getMessage(), throwable);
}
});
}
}
public interface SyncDeletedSongsCallback { public interface SyncDeletedSongsCallback {

View File

@ -0,0 +1,27 @@
package com.stormtales.notevault.network.sync.models;
public class NoteSheetModifyRequest {
private String serverFileName;
private String clientHash;
public NoteSheetModifyRequest(String serverFileName, String clientHash) {
this.serverFileName = serverFileName;
this.clientHash = clientHash;
}
public String getServerFileName() {
return serverFileName;
}
public void setServerFileName(String serverFileName) {
this.serverFileName = serverFileName;
}
public String getClientHash() {
return clientHash;
}
public void setClientHash(String clientHash) {
this.clientHash = clientHash;
}
}

View File

@ -0,0 +1,16 @@
package com.stormtales.notevault.network.sync.models;
import java.util.List;
public class SongModifyBatchResponse {
private List<SongModifyResponse> modifiedServerObjects;
public List<SongModifyResponse> getModifiedServerObjects() {
return modifiedServerObjects;
}
public void setModifiedServerObjects(List<SongModifyResponse> modifiedServerObjects) {
this.modifiedServerObjects = modifiedServerObjects;
}
}

View File

@ -1,7 +1,11 @@
package com.stormtales.notevault.network.sync.models; package com.stormtales.notevault.network.sync.models;
import com.stormtales.notevault.data.entities.NoteSheet;
import com.stormtales.notevault.data.entities.Song; import com.stormtales.notevault.data.entities.Song;
import java.util.List;
import java.util.stream.Collectors;
public class SongModifyRequest { public class SongModifyRequest {
private String serverID; private String serverID;
@ -9,6 +13,7 @@ public class SongModifyRequest {
private String composer; private String composer;
private String genre; private String genre;
private int year; private int year;
private List<NoteSheetModifyRequest> noteSheets;
public SongModifyRequest(String serverID, String title, String composer, String genre, int year) { public SongModifyRequest(String serverID, String title, String composer, String genre, int year) {
this.serverID = serverID; this.serverID = serverID;
@ -18,12 +23,13 @@ public class SongModifyRequest {
this.year = year; this.year = year;
} }
public SongModifyRequest(Song song) { public SongModifyRequest(Song song, List<NoteSheet> noteSheets) {
this.serverID = song.getServerID(); this.serverID = song.getServerID();
this.title = song.getTitle(); this.title = song.getTitle();
this.composer = song.getComposer(); this.composer = song.getComposer();
this.genre = song.getGenre(); this.genre = song.getGenre();
this.year = song.getYear(); this.year = song.getYear();
this.noteSheets = noteSheets.stream().map(noteSheet -> new NoteSheetModifyRequest(noteSheet.getServerFileName(), noteSheet.getHash())).collect(Collectors.toList());
} }
public String getServerID() { public String getServerID() {
@ -65,4 +71,12 @@ public class SongModifyRequest {
public void setYear(int year) { public void setYear(int year) {
this.year = year; this.year = year;
} }
public List<NoteSheetModifyRequest> getNoteSheets() {
return noteSheets;
}
public void setNoteSheets(List<NoteSheetModifyRequest> noteSheets) {
this.noteSheets = noteSheets;
}
} }

View File

@ -0,0 +1,25 @@
package com.stormtales.notevault.network.sync.models;
import java.util.List;
public class SongModifyResponse {
private String serverID;
private List<String> outdated_note_sheets;
public List<String> getOutdated_note_sheets() {
return outdated_note_sheets;
}
public void setOutdated_note_sheets(List<String> outdated_note_sheets) {
this.outdated_note_sheets = outdated_note_sheets;
}
public String getServerID() {
return serverID;
}
public void setServerID(String serverID) {
this.serverID = serverID;
}
}