From 0f40f73e448f7b9df068782f6b468da293420d69 Mon Sep 17 00:00:00 2001 From: Maximilian Bauer Date: Thu, 7 Oct 2021 21:27:14 +0200 Subject: [PATCH] Syncer: sync newest songs on user refresh request --- .../Api/Ampache/AmpacheLibrarySyncer.swift | 48 +++++++++++++++++++ Amperfy/Api/BackendApi.swift | 1 + .../Api/Subsonic/SsAlbumParserDelegate.swift | 4 ++ .../Api/Subsonic/SubsonicLibrarySyncer.swift | 15 ++++++ Amperfy/Api/Subsonic/SubsonicServerApi.swift | 7 +++ Amperfy/Screens/Base.lproj/Main.storyboard | 18 +++++-- Amperfy/Screens/ViewController/AlbumsVC.swift | 19 ++++++++ .../Screens/ViewController/ArtistsVC.swift | 19 ++++++++ Amperfy/Screens/ViewController/GenresVC.swift | 19 ++++++++ Amperfy/Screens/ViewController/SongsVC.swift | 19 ++++++++ 10 files changed, 166 insertions(+), 3 deletions(-) diff --git a/Amperfy/Api/Ampache/AmpacheLibrarySyncer.swift b/Amperfy/Api/Ampache/AmpacheLibrarySyncer.swift index 1d51ce21..d7c572ee 100644 --- a/Amperfy/Api/Ampache/AmpacheLibrarySyncer.swift +++ b/Amperfy/Api/Ampache/AmpacheLibrarySyncer.swift @@ -123,6 +123,54 @@ class AmpacheLibrarySyncer: LibrarySyncer { ampacheXmlServerApi.eventLogger.error(topic: "Internal Error", statusCode: .internalError, message: "GetMusicDirectory API function is not support by Ampache") } + func syncLatestLibraryElements(library: LibraryStorage) { + guard var syncWave = library.getLatestSyncWave() else { return } + guard let ampacheMetaData = ampacheXmlServerApi.requesetLibraryMetaData() else { return } + guard syncWave.libraryChangeDates.dateOfLastAdd != ampacheMetaData.libraryChangeDates.dateOfLastAdd else { + os_log("No new library elements available", log: log, type: .info) + return + } + let lastStr = "\(syncWave.libraryChangeDates.dateOfLastAdd)" + let newStr = "\(ampacheMetaData.libraryChangeDates.dateOfLastAdd)" + os_log("New library elements available (last: %s, new: %s)", log: log, type: .info, lastStr, newStr) + + let addDate = Date(timeInterval: 1, since: syncWave.libraryChangeDates.dateOfLastAdd) + syncWave = library.createSyncWave() + syncWave.setMetaData(fromLibraryChangeDates: ampacheMetaData.libraryChangeDates) + library.saveContext() + + var allParsed = false + var syncIndex = 0 + repeat { + let artistParser = ArtistParserDelegate(library: library, syncWave: syncWave) + ampacheXmlServerApi.requestArtists(parserDelegate: artistParser, addDate: addDate, startIndex: syncIndex, pollCount: AmpacheXmlServerApi.maxItemCountToPollAtOnce) + syncIndex += artistParser.parsedCount + allParsed = artistParser.parsedCount == 0 + } while(!allParsed) + os_log("%i new Artists synced", log: log, type: .info, syncIndex) + library.saveContext() + + syncIndex = 0 + repeat { + let albumParser = AlbumParserDelegate(library: library, syncWave: syncWave) + ampacheXmlServerApi.requestAlbums(parserDelegate: albumParser, addDate: addDate, startIndex: syncIndex, pollCount: AmpacheXmlServerApi.maxItemCountToPollAtOnce) + syncIndex += albumParser.parsedCount + allParsed = albumParser.parsedCount == 0 + } while(!allParsed) + os_log("%i new Albums synced", log: log, type: .info, syncIndex) + library.saveContext() + + syncIndex = 0 + repeat { + let songParser = SongParserDelegate(library: library, syncWave: syncWave) + ampacheXmlServerApi.requestSongs(parserDelegate: songParser, addDate: addDate, startIndex: syncIndex, pollCount: AmpacheXmlServerApi.maxItemCountToPollAtOnce) + syncIndex += songParser.parsedCount + allParsed = songParser.parsedCount == 0 + } while(!allParsed) + os_log("%i new Songs synced", log: log, type: .info, syncIndex) + library.saveContext() + } + func requestRandomSongs(playlist: Playlist, count: Int, library: LibraryStorage) { guard let syncWave = library.getLatestSyncWave() else { return } let parser = SongParserDelegate(library: library, syncWave: syncWave, parseNotifier: nil) diff --git a/Amperfy/Api/BackendApi.swift b/Amperfy/Api/BackendApi.swift index b28936cb..e999ca1d 100644 --- a/Amperfy/Api/BackendApi.swift +++ b/Amperfy/Api/BackendApi.swift @@ -29,6 +29,7 @@ protocol LibrarySyncer { func sync(currentContext: NSManagedObjectContext, persistentContainer: NSPersistentContainer, statusNotifyier: SyncCallbacks?) func sync(artist: Artist, library: LibraryStorage) func sync(album: Album, library: LibraryStorage) + func syncLatestLibraryElements(library: LibraryStorage) func syncDownPlaylistsWithoutSongs(library: LibraryStorage) func syncDown(playlist: Playlist, library: LibraryStorage) func syncUpload(playlistToAddSongs playlist: Playlist, songs: [Song], library: LibraryStorage) diff --git a/Amperfy/Api/Subsonic/SsAlbumParserDelegate.swift b/Amperfy/Api/Subsonic/SsAlbumParserDelegate.swift index f4231128..4e423352 100644 --- a/Amperfy/Api/Subsonic/SsAlbumParserDelegate.swift +++ b/Amperfy/Api/Subsonic/SsAlbumParserDelegate.swift @@ -6,6 +6,7 @@ import os.log class SsAlbumParserDelegate: SsXmlLibWithArtworkParser { var guessedArtist: Artist? + var parsedAlbums = [Album]() private var albumBuffer: Album? override func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { @@ -68,6 +69,9 @@ class SsAlbumParserDelegate: SsXmlLibWithArtworkParser { switch(elementName) { case "album": parsedCount += 1 + if let album = albumBuffer { + parsedAlbums.append(album) + } albumBuffer = nil default: break diff --git a/Amperfy/Api/Subsonic/SubsonicLibrarySyncer.swift b/Amperfy/Api/Subsonic/SubsonicLibrarySyncer.swift index f2364698..eeb185e3 100644 --- a/Amperfy/Api/Subsonic/SubsonicLibrarySyncer.swift +++ b/Amperfy/Api/Subsonic/SubsonicLibrarySyncer.swift @@ -105,6 +105,21 @@ class SubsonicLibrarySyncer: LibrarySyncer { library.saveContext() } + func syncLatestLibraryElements(library: LibraryStorage) { + guard let syncWave = library.getLatestSyncWave() else { return } + os_log("Sync newest albums", log: log, type: .info) + let albumDelegate = SsAlbumParserDelegate(library: library, syncWave: syncWave, subsonicUrlCreator: subsonicServerApi) + subsonicServerApi.requestLatestAlbums(parserDelegate: albumDelegate) + library.saveContext() + os_log("Sync songs of newest albums", log: log, type: .info) + for album in albumDelegate.parsedAlbums { + let songParser = SsSongParserDelegate(library: library, syncWave: syncWave, subsonicUrlCreator: subsonicServerApi) + subsonicServerApi.requestAlbum(parserDelegate: songParser, id: album.id) + } + os_log("%i newest Albums synced", log: log, type: .info, albumDelegate.parsedAlbums.count) + library.saveContext() + } + func syncMusicFolders(library: LibraryStorage) { guard let syncWave = library.getLatestSyncWave() else { return } let musicFolderParser = SsMusicFolderParserDelegate(library: library, syncWave: syncWave) diff --git a/Amperfy/Api/Subsonic/SubsonicServerApi.swift b/Amperfy/Api/Subsonic/SubsonicServerApi.swift index ee013a30..86232974 100644 --- a/Amperfy/Api/Subsonic/SubsonicServerApi.swift +++ b/Amperfy/Api/Subsonic/SubsonicServerApi.swift @@ -213,6 +213,13 @@ class SubsonicServerApi { request(fromUrlComponent: urlComp, viaXmlParser: parserDelegate) } + func requestLatestAlbums(parserDelegate: SsXmlParser) { + guard var urlComp = createAuthenticatedApiUrlComponent(forAction: "getAlbumList2") else { return } + urlComp.addQueryItem(name: "type", value: "newest") + urlComp.addQueryItem(name: "size", value: 50) + request(fromUrlComponent: urlComp, viaXmlParser: parserDelegate) + } + func requestRandomSongs(parserDelegate: SsXmlParser, count: Int) { guard var urlComp = createAuthenticatedApiUrlComponent(forAction: "getRandomSongs") else { return } urlComp.addQueryItem(name: "size", value: count) diff --git a/Amperfy/Screens/Base.lproj/Main.storyboard b/Amperfy/Screens/Base.lproj/Main.storyboard index c9d5e69f..0ddeffb1 100644 --- a/Amperfy/Screens/Base.lproj/Main.storyboard +++ b/Amperfy/Screens/Base.lproj/Main.storyboard @@ -266,6 +266,9 @@ + + + @@ -310,6 +313,9 @@ + + + @@ -352,6 +358,9 @@ + + + @@ -2429,6 +2438,9 @@ + + + @@ -2730,9 +2742,9 @@ - - - + + + diff --git a/Amperfy/Screens/ViewController/AlbumsVC.swift b/Amperfy/Screens/ViewController/AlbumsVC.swift index 35c5fada..43fb8c98 100644 --- a/Amperfy/Screens/ViewController/AlbumsVC.swift +++ b/Amperfy/Screens/ViewController/AlbumsVC.swift @@ -15,6 +15,7 @@ class AlbumsVC: SingleFetchedResultsTableViewController { configureSearchController(placeholder: "Search in \"Albums\"", scopeButtonTitles: ["All", "Cached"], showSearchBarAtEnter: false) tableView.register(nibName: AlbumTableCell.typeName) tableView.rowHeight = AlbumTableCell.rowHeight + self.refreshControl?.addTarget(self, action: #selector(Self.handleRefresh), for: UIControl.Event.valueChanged) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -50,5 +51,23 @@ class AlbumsVC: SingleFetchedResultsTableViewController { tableView.reloadData() } + @objc func handleRefresh(refreshControl: UIRefreshControl) { + appDelegate.persistentStorage.persistentContainer.performBackgroundTask() { (context) in + if self.appDelegate.persistentStorage.settings.isOnlineMode { + let syncLibrary = LibraryStorage(context: context) + let syncer = self.appDelegate.backendApi.createLibrarySyncer() + syncer.syncLatestLibraryElements(library: syncLibrary) + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + } else { + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + } + + } + } + } diff --git a/Amperfy/Screens/ViewController/ArtistsVC.swift b/Amperfy/Screens/ViewController/ArtistsVC.swift index 9fb211a8..2d0e3154 100644 --- a/Amperfy/Screens/ViewController/ArtistsVC.swift +++ b/Amperfy/Screens/ViewController/ArtistsVC.swift @@ -15,6 +15,7 @@ class ArtistsVC: SingleFetchedResultsTableViewController { configureSearchController(placeholder: "Search in \"Artists\"", scopeButtonTitles: ["All", "Cached"], showSearchBarAtEnter: false) tableView.register(nibName: ArtistTableCell.typeName) tableView.rowHeight = ArtistTableCell.rowHeight + self.refreshControl?.addTarget(self, action: #selector(Self.handleRefresh), for: UIControl.Event.valueChanged) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -49,6 +50,24 @@ class ArtistsVC: SingleFetchedResultsTableViewController { fetchedResultsController.search(searchText: searchText, onlyCached: searchController.searchBar.selectedScopeButtonIndex == 1) tableView.reloadData() } + + @objc func handleRefresh(refreshControl: UIRefreshControl) { + appDelegate.persistentStorage.persistentContainer.performBackgroundTask() { (context) in + if self.appDelegate.persistentStorage.settings.isOnlineMode { + let syncLibrary = LibraryStorage(context: context) + let syncer = self.appDelegate.backendApi.createLibrarySyncer() + syncer.syncLatestLibraryElements(library: syncLibrary) + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + } else { + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + } + + } + } } diff --git a/Amperfy/Screens/ViewController/GenresVC.swift b/Amperfy/Screens/ViewController/GenresVC.swift index 5ec1b4db..12ade6f7 100644 --- a/Amperfy/Screens/ViewController/GenresVC.swift +++ b/Amperfy/Screens/ViewController/GenresVC.swift @@ -15,6 +15,7 @@ class GenresVC: SingleFetchedResultsTableViewController { configureSearchController(placeholder: "Search in \"Genres\"", scopeButtonTitles: ["All", "Cached"]) tableView.register(nibName: GenreTableCell.typeName) tableView.rowHeight = GenreTableCell.rowHeight + self.refreshControl?.addTarget(self, action: #selector(Self.handleRefresh), for: UIControl.Event.valueChanged) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -42,6 +43,24 @@ class GenresVC: SingleFetchedResultsTableViewController { fetchedResultsController.search(searchText: searchText, onlyCached: searchController.searchBar.selectedScopeButtonIndex == 1) tableView.reloadData() } + + @objc func handleRefresh(refreshControl: UIRefreshControl) { + appDelegate.persistentStorage.persistentContainer.performBackgroundTask() { (context) in + if self.appDelegate.persistentStorage.settings.isOnlineMode { + let syncLibrary = LibraryStorage(context: context) + let syncer = self.appDelegate.backendApi.createLibrarySyncer() + syncer.syncLatestLibraryElements(library: syncLibrary) + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + } else { + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + } + + } + } } diff --git a/Amperfy/Screens/ViewController/SongsVC.swift b/Amperfy/Screens/ViewController/SongsVC.swift index 89ac8fa1..d591d0f8 100644 --- a/Amperfy/Screens/ViewController/SongsVC.swift +++ b/Amperfy/Screens/ViewController/SongsVC.swift @@ -18,6 +18,7 @@ class SongsVC: SingleFetchedResultsTableViewController { tableView.rowHeight = SongTableCell.rowHeight optionsButton = UIBarButtonItem(title: "\(CommonString.threeMiddleDots)", style: .plain, target: self, action: #selector(optionsPressed)) + self.refreshControl?.addTarget(self, action: #selector(Self.handleRefresh), for: UIControl.Event.valueChanged) } override func viewWillAppear(_ animated: Bool) { @@ -78,5 +79,23 @@ class SongsVC: SingleFetchedResultsTableViewController { present(alert, animated: true, completion: nil) } + @objc func handleRefresh(refreshControl: UIRefreshControl) { + appDelegate.persistentStorage.persistentContainer.performBackgroundTask() { (context) in + if self.appDelegate.persistentStorage.settings.isOnlineMode { + let syncLibrary = LibraryStorage(context: context) + let syncer = self.appDelegate.backendApi.createLibrarySyncer() + syncer.syncLatestLibraryElements(library: syncLibrary) + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + } else { + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } + } + + } + } + }