ADD: Sync Modified Songs with Server

This commit is contained in:
Fawkes100 2025-01-19 10:11:05 +01:00
parent b3c418968d
commit 191c50bac3
11 changed files with 271 additions and 27 deletions

View File

@ -39,4 +39,7 @@ public interface SongDao {
@Query("SELECT * FROM Song WHERE localID IN (:localIDs)")
List<Song> getSongsByLocalIDs(List<Integer> localIDs);
@Query("SELECT * FROM Song WHERE serverID IN (:serverIDs)")
List<Song> getSongsByServerIDs(List<String> serverIDs);
}

View File

@ -8,6 +8,7 @@ import com.stormtales.notevault.data.dao.SongDao;
import com.stormtales.notevault.data.entities.Song;
import com.stormtales.notevault.data.sync.SyncStatus;
import com.stormtales.notevault.network.sync.models.BatchCreateResponse;
import com.stormtales.notevault.network.sync.models.BatchModifyResponse;
import com.stormtales.notevault.network.sync.models.CreateResponse;
import java.time.LocalDateTime;
@ -32,6 +33,14 @@ public class SongSyncRepository {
});
}
public void loadModifiedSongs(LoadDataCallback<List<Song>> callback) {
Executors.newSingleThreadExecutor().execute(() -> {
List<Song> modifiedSongs = songDao.getSongsBySyncStatus(SyncStatus.MODIFIED);
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(()-> callback.onResult(modifiedSongs));
});
}
public void markCreatedSongsAsSynced(BatchCreateResponse createdSongs) {
Executors.newSingleThreadExecutor().execute(() -> {
List<Integer> localIDs = createdSongs.getCreateResponses().stream().map(CreateResponse::getLocalID).collect(Collectors.toList());
@ -58,6 +67,17 @@ public class SongSyncRepository {
}
}
public void markModifiedSongsAsSynced(BatchModifyResponse response) {
Executors.newSingleThreadExecutor().execute(() -> {
List<Song> requestedSongs = songDao.getSongsByServerIDs(response.getModifiedServerObjects());
for(Song song : requestedSongs) {
song.setSyncStatus(SyncStatus.SYNCED);
song.setSyncTime(LocalDateTime.now());
}
songDao.updateSongs(requestedSongs);
});
}
public interface LoadDataCallback<T> {
void onResult(T result);
}

View File

@ -3,7 +3,9 @@ package com.stormtales.notevault.network.sync;
import com.stormtales.notevault.network.auth.LoginRequest;
import com.stormtales.notevault.network.auth.LoginResponse;
import com.stormtales.notevault.network.sync.models.BatchCreateResponse;
import com.stormtales.notevault.network.sync.models.BatchModifyResponse;
import com.stormtales.notevault.network.sync.models.SongCreateBatchRequest;
import com.stormtales.notevault.network.sync.models.SongModifyBatchRequest;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
@ -12,4 +14,7 @@ public interface SongSyncAPI {
@POST("/sync/songs/create")
Call<BatchCreateResponse> syncCreatedSongs(@Body SongCreateBatchRequest songCreateBatchRequest);
@POST("/sync/songs/modify")
Call<BatchModifyResponse> syncModifiedSongs(@Body SongModifyBatchRequest songModifyBatchRequest);
}

View File

@ -1,14 +1,12 @@
package com.stormtales.notevault.network.sync;
import android.content.Context;
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.BatchCreateResponse;
import com.stormtales.notevault.network.sync.models.BatchModifyResponse;
import com.stormtales.notevault.ui.gallery.GalleryViewModel;
import java.util.List;
public class SongSyncModule {
private SongRepository songRepository;
private SongSyncRepository songSyncRepository;
@ -22,19 +20,29 @@ public class SongSyncModule {
this.syncViewModel = syncViewModel;
}
public void syncCreatedSongs(FinishSongSyncingCallback callback) {
public void syncCreatedSongs() {
songSyncRepository.loadCreatedSongs(result -> {
songSyncService.syncCreatedSongs(result, new FinishSongSyncingCallback() {
@Override
public void finishSongSyncing(BatchCreateResponse response) {
songSyncRepository.markCreatedSongsAsSynced(response);
callback.finishSongSyncing(response);
}
songSyncService.syncCreatedSongs(result, response -> {
songSyncRepository.markCreatedSongsAsSynced(response);
syncViewModel.finishCreateSongSyncing();
});
});
}
public interface FinishSongSyncingCallback {
public void syncModifiedSongs() {
songSyncRepository.loadModifiedSongs(result -> {
songSyncService.syncModifiedSongs(result, response -> {
songSyncRepository.markModifiedSongsAsSynced(response);
syncViewModel.finishModifiedSongSyncinc();
});
});
}
public interface FinishSongCreateSyncingCallback {
void finishSongSyncing(BatchCreateResponse response);
}
public interface FinishSongSyncingCallback {
void finishSongSyncing(BatchModifyResponse response);
}
}

View File

@ -4,9 +4,7 @@ import android.content.Context;
import com.stormtales.notevault.data.entities.Song;
import com.stormtales.notevault.network.NetworkModule;
import com.stormtales.notevault.network.auth.AuthAPI;
import com.stormtales.notevault.network.sync.models.BatchCreateResponse;
import com.stormtales.notevault.network.sync.models.SongCreateBatchRequest;
import com.stormtales.notevault.network.sync.models.SongCreateRequest;
import com.stormtales.notevault.network.sync.models.*;
import com.stormtales.notevault.ui.gallery.GalleryViewModel;
import retrofit2.Call;
import retrofit2.Callback;
@ -23,7 +21,7 @@ public class SongSyncService {
songSyncAPI = NetworkModule.getRetrofitInstance(context).create(SongSyncAPI.class);
}
public void syncCreatedSongs(List<Song> songs, SongSyncModule.FinishSongSyncingCallback callback) {
public void syncCreatedSongs(List<Song> songs, SongSyncModule.FinishSongCreateSyncingCallback callback) {
List<SongCreateRequest> createRequests = new ArrayList<>();
for(Song song : songs) {
createRequests.add(new SongCreateRequest(song));
@ -41,4 +39,23 @@ public class SongSyncService {
}
});
}
public void syncModifiedSongs(List<Song> songs, SongSyncModule.FinishSongSyncingCallback callback) {
List<SongModifyRequest> modifyRequests = new ArrayList<>();
for(Song song : songs) {
modifyRequests.add(new SongModifyRequest(song));
}
SongModifyBatchRequest songModifyBatchRequest = new SongModifyBatchRequest(modifyRequests);
songSyncAPI.syncModifiedSongs(songModifyBatchRequest).enqueue(new Callback<BatchModifyResponse>() {
@Override
public void onResponse(Call<BatchModifyResponse> call, Response<BatchModifyResponse> response) {
callback.finishSongSyncing(response.body());
}
@Override
public void onFailure(Call<BatchModifyResponse> call, Throwable throwable) {
}
});
}
}

View File

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

View File

@ -0,0 +1,19 @@
package com.stormtales.notevault.network.sync.models;
import java.util.List;
public class SongModifyBatchRequest {
private List<SongModifyRequest> songs;
public SongModifyBatchRequest(List<SongModifyRequest> songs) {
this.songs = songs;
}
public List<SongModifyRequest> getSongs() {
return songs;
}
public void setSongs(List<SongModifyRequest> songs) {
this.songs = songs;
}
}

View File

@ -0,0 +1,68 @@
package com.stormtales.notevault.network.sync.models;
import com.stormtales.notevault.data.entities.Song;
public class SongModifyRequest {
private String serverID;
private String title;
private String composer;
private String genre;
private int year;
public SongModifyRequest(String serverID, String title, String composer, String genre, int year) {
this.serverID = serverID;
this.title = title;
this.composer = composer;
this.genre = genre;
this.year = year;
}
public SongModifyRequest(Song song) {
this.serverID = song.getServerID();
this.title = song.getTitle();
this.composer = song.getComposer();
this.genre = song.getGenre();
this.year = song.getYear();
}
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;
}
}

View File

@ -22,6 +22,8 @@ public class GalleryFragment extends Fragment {
private FragmentGalleryBinding binding;
private ProgressBar progress_sync_created_songs;
private Button sync_created_songs_btn;
private ProgressBar progress_sync_modified_songs;
private Button sync_modified_songs_btn;
public View onCreateView(@NonNull LayoutInflater inflater,
@ -34,6 +36,9 @@ public class GalleryFragment extends Fragment {
progress_sync_created_songs = binding.progressSyncCreatedSongs;
sync_created_songs_btn = binding.syncCreatedBtn;
sync_created_songs_btn.setOnClickListener(v -> onSyncCreatedSongs());
progress_sync_modified_songs = binding.progressSyncModifiedSongs;
sync_modified_songs_btn = binding.syncModifiedBtn;
sync_modified_songs_btn.setOnClickListener(v -> onSyncModifiedSogs());
galleryViewModel.setSongSyncModule(new SongSyncModule(getContext(), galleryViewModel));
@ -46,16 +51,32 @@ public class GalleryFragment extends Fragment {
sync_created_songs_btn.setEnabled(true);
}
});
galleryViewModel.getIsModifiedSongSyncing().observe(getViewLifecycleOwner(), isModifiedSyncinc -> {
if(isModifiedSyncinc) {
progress_sync_modified_songs.setIndeterminate(true);
sync_modified_songs_btn.setEnabled(false);
} else {
progress_sync_modified_songs.setIndeterminate(false);
sync_modified_songs_btn.setEnabled(true);
}
});
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
public void onSyncCreatedSongs() {
private void onSyncCreatedSongs() {
galleryViewModel.startCreateSongSyncing();
}
private void onSyncModifiedSogs() {
galleryViewModel.startModifiedSongSyncinc();
}
}

View File

@ -9,12 +9,16 @@ import com.stormtales.notevault.network.sync.SongSyncModule;
public class GalleryViewModel extends ViewModel {
private final MutableLiveData<Boolean> isCreatedSongSyncing;
private final MutableLiveData<Boolean> isModifiedSongSyncing;
private SongSyncModule songSyncModule;
public GalleryViewModel() {
isCreatedSongSyncing = new MutableLiveData<>();
isCreatedSongSyncing.setValue(false);
isModifiedSongSyncing = new MutableLiveData<>();
isModifiedSongSyncing.setValue(false);
}
public MutableLiveData<Boolean> getIsCreatedSongSyncing() {
@ -25,12 +29,28 @@ public class GalleryViewModel extends ViewModel {
if(this.songSyncModule == null) throw new RuntimeException("SongSyncModule is not initialized");
this.isCreatedSongSyncing.setValue(true);
this.songSyncModule.syncCreatedSongs(batchResponse -> {
this.isCreatedSongSyncing.setValue(false);
});
this.songSyncModule.syncCreatedSongs();
}
public void finishCreateSongSyncing() {
this.isCreatedSongSyncing.setValue(false);
}
public void setSongSyncModule(SongSyncModule songSyncModule) {
this.songSyncModule = songSyncModule;
}
public void startModifiedSongSyncinc() {
if(this.songSyncModule == null) throw new RuntimeException("SongSyncModule is not initialized");
this.isModifiedSongSyncing.setValue(true);
this.songSyncModule.syncModifiedSongs();
}
public MutableLiveData<Boolean> getIsModifiedSongSyncing() {
return isModifiedSongSyncing;
}
public void finishModifiedSongSyncinc() {
this.isModifiedSongSyncing.setValue(false);
}
}

View File

@ -6,14 +6,18 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.gallery.GalleryFragment">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:padding="16dp">
<!-- Created Songs Sync Section -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.gallery.GalleryFragment">
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<!-- Label -->
<TextView
@ -26,7 +30,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp" />
<!-- Progress Bar -->
@ -35,9 +38,9 @@
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="44dp"
android:progress="0"
android:layout_marginEnd="15dp"
android:layout_marginStart="15dp"
android:indeterminate="true"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/sync_created_songs"
app:layout_constraintEnd_toStartOf="@id/sync_created_btn"
app:layout_constraintTop_toTopOf="@id/sync_created_songs"
@ -56,5 +59,50 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Modified Songs Sync Section -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Label -->
<TextView
android:id="@+id/sync_modified_songs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sync Client Side Modified Songs"
android:textSize="16sp"
android:textColor="@android:color/black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="8dp" />
<!-- Progress Bar -->
<ProgressBar
android:id="@+id/progress_sync_modified_songs"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="44dp"
android:indeterminate="true"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/sync_modified_songs"
app:layout_constraintEnd_toStartOf="@id/sync_modified_btn"
app:layout_constraintTop_toTopOf="@id/sync_modified_songs"
app:layout_constraintBottom_toBottomOf="@id/sync_modified_songs" />
<!-- Sync Button -->
<Button
android:id="@+id/sync_modified_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sync"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/sync_modified_songs"
app:layout_constraintBottom_toBottomOf="@id/sync_modified_songs"
android:layout_marginEnd="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>