nextNoteVault #23
@ -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