Skip to content

Commit

Permalink
Ampache: support directories
Browse files Browse the repository at this point in the history
  • Loading branch information
BLeeEZ committed Oct 21, 2021
1 parent 55e1f5a commit 5823b1e
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 5 deletions.
12 changes: 12 additions & 0 deletions Amperfy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@
508FDE5021F64ED6005A0724 /* TabBarVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508FDE4F21F64ED6005A0724 /* TabBarVC.swift */; };
509001B62716C7F600A8056D /* AppDelegateAlertExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509001B52716C7F600A8056D /* AppDelegateAlertExtensions.swift */; };
509001B82716C8F400A8056D /* AppDelegateNotificationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509001B72716C8F400A8056D /* AppDelegateNotificationExtensions.swift */; };
509001BF271829E200A8056D /* catalogs.xml in Resources */ = {isa = PBXBuildFile; fileRef = 509001BE271829E200A8056D /* catalogs.xml */; };
509001C127182A1300A8056D /* CatalogParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509001C027182A1300A8056D /* CatalogParserTest.swift */; };
509001C327182A4700A8056D /* CatalogParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509001C227182A4700A8056D /* CatalogParserDelegate.swift */; };
5090963826496A9500DD9826 /* UtilitiesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5090963726496A9500DD9826 /* UtilitiesTest.swift */; };
509121032625EF14008C57EC /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509121022625EF14008C57EC /* MarqueeLabel.swift */; };
5095F98C23C8389E008B0805 /* MusicPlayerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5095F98B23C8389E008B0805 /* MusicPlayerTest.swift */; };
Expand Down Expand Up @@ -500,6 +503,9 @@
509001B52716C7F600A8056D /* AppDelegateAlertExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateAlertExtensions.swift; sourceTree = "<group>"; };
509001B72716C8F400A8056D /* AppDelegateNotificationExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateNotificationExtensions.swift; sourceTree = "<group>"; };
509001BD2717120900A8056D /* Amperfy v14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Amperfy v14.xcdatamodel"; sourceTree = "<group>"; };
509001BE271829E200A8056D /* catalogs.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = catalogs.xml; sourceTree = "<group>"; };
509001C027182A1300A8056D /* CatalogParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogParserTest.swift; sourceTree = "<group>"; };
509001C227182A4700A8056D /* CatalogParserDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogParserDelegate.swift; sourceTree = "<group>"; };
5090963726496A9500DD9826 /* UtilitiesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilitiesTest.swift; sourceTree = "<group>"; };
509121022625EF14008C57EC /* MarqueeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MarqueeLabel.swift; path = Carthage/Checkouts/MarqueeLabel/Sources/MarqueeLabel.swift; sourceTree = SOURCE_ROOT; };
5095F98B23C8389E008B0805 /* MusicPlayerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicPlayerTest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1035,6 +1041,7 @@
50BA930721D75C5600E5901D /* AuthentificationHandshake.swift */,
501267B8264C187600E13F08 /* AmpacheXmlParser.swift */,
5083861921C827E600C4BB32 /* AuthParserDelegate.swift */,
509001C227182A4700A8056D /* CatalogParserDelegate.swift */,
507361C72632AB08005F151D /* GenreParserDelegate.swift */,
50BA92ED21CBF45D00E5901D /* ArtistParserDelegate.swift */,
50BA92EC21CBF45D00E5901D /* AlbumParserDelegate.swift */,
Expand Down Expand Up @@ -1168,6 +1175,7 @@
50DB11A6266533720033BFFA /* ErrorParserTest.swift */,
50DB118E266521610033BFFA /* GenreParserTest.swift */,
50DB1181266502100033BFFA /* ArtistParserTest.swift */,
509001C027182A1300A8056D /* CatalogParserTest.swift */,
50DB118A266511030033BFFA /* AlbumParserTest.swift */,
50DB1192266522F60033BFFA /* SongParserTest.swift */,
50DB119E26652E090033BFFA /* PlaylistsParserTest.swift */,
Expand All @@ -1183,6 +1191,7 @@
children = (
50DB11A026652FCB0033BFFA /* handshake.xml */,
50DB11A42665336B0033BFFA /* error-4700.xml */,
509001BE271829E200A8056D /* catalogs.xml */,
50DB118C266521290033BFFA /* genres.xml */,
50DB1184266502C00033BFFA /* artists.xml */,
50DB1188266510FF0033BFFA /* albums.xml */,
Expand Down Expand Up @@ -1432,6 +1441,7 @@
50AB92DC2666C53F00DCE45C /* directory_example_2.xml in Resources */,
50AB92C826662FCA00DCE45C /* album_example_2.xml in Resources */,
50DB119926652D070033BFFA /* playlist_songs.xml in Resources */,
509001BF271829E200A8056D /* catalogs.xml in Resources */,
50DB119D26652DFA0033BFFA /* playlists.xml in Resources */,
50AB92D22666C19500DCE45C /* musicFolders_example_1.xml in Resources */,
50AB92CE2666BC0B00DCE45C /* playlist_example_1.xml in Resources */,
Expand Down Expand Up @@ -1489,6 +1499,7 @@
50D92CCF25E3FE560017E91D /* MigrationPolicyV4toV5.swift in Sources */,
50C16C1E21E7A35C00F086F0 /* AlbumTableCell.swift in Sources */,
50120A2526281C1900B037B1 /* BasicTableViewController.swift in Sources */,
509001C327182A4700A8056D /* CatalogParserDelegate.swift in Sources */,
50F81EE223BF6B1E00EAAC3E /* PlayableFileMO+CoreDataClass.swift in Sources */,
50C16C1421E687AB00F086F0 /* ArtistTableCell.swift in Sources */,
501A7D4B2681E6DD0055A51B /* PodcastEpisodeParserDelegate.swift in Sources */,
Expand Down Expand Up @@ -1734,6 +1745,7 @@
50B798B223B7F51000551E62 /* PlaylistTest.swift in Sources */,
50DB118B266511030033BFFA /* AlbumParserTest.swift in Sources */,
50DB119F26652E090033BFFA /* PlaylistsParserTest.swift in Sources */,
509001C127182A1300A8056D /* CatalogParserTest.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
74 changes: 71 additions & 3 deletions Amperfy/Api/Ampache/AmpacheLibrarySyncer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,83 @@ class AmpacheLibrarySyncer: LibrarySyncer {
}

func syncMusicFolders(library: LibraryStorage) {
ampacheXmlServerApi.eventLogger.error(topic: "Internal Error", statusCode: .internalError, message: "GetMusicFolders API function is not support by Ampache")
guard let syncWave = library.getLatestSyncWave() else { return }
let catalogParser = CatalogParserDelegate(library: library, syncWave: syncWave)
self.ampacheXmlServerApi.requestCatalogs(parserDelegate: catalogParser)
library.saveContext()
}

func syncIndexes(musicFolder: MusicFolder, library: LibraryStorage) {
ampacheXmlServerApi.eventLogger.error(topic: "Internal Error", statusCode: .internalError, message: "GetIndexes API function is not support by Ampache")
guard let syncWave = library.getLatestSyncWave() else { return }
let artistParser = ArtistParserDelegate(library: library, syncWave: syncWave)
self.ampacheXmlServerApi.requestArtistWithinCatalog(of: musicFolder, parserDelegate: artistParser)

let directoriesBeforeFetch = Set(musicFolder.directories)
var directoriesAfterFetch: Set<Directory> = Set()
for artist in artistParser.artistsParsed {
let artistDirId = "artist-\(artist.id)"
var curDir: Directory!
if let foundDir = library.getDirectory(id: artistDirId) {
curDir = foundDir
} else {
curDir = library.createDirectory()
curDir.id = artistDirId
}
curDir.name = artist.name
musicFolder.managedObject.addToDirectories(curDir.managedObject)
directoriesAfterFetch.insert(curDir)
}

let removedDirectories = directoriesBeforeFetch.subtracting(directoriesAfterFetch)
removedDirectories.forEach{ library.deleteDirectory(directory: $0) }

library.saveContext()
}

func sync(directory: Directory, library: LibraryStorage) {
ampacheXmlServerApi.eventLogger.error(topic: "Internal Error", statusCode: .internalError, message: "GetMusicDirectory API function is not support by Ampache")
if directory.id.starts(with: "album-") {
let albumId = String(directory.id.dropFirst("album-".count))
guard let album = library.getAlbum(id: albumId) else { return }
let songsBeforeFetch = Set(directory.songs)
sync(album: album, library: library)
directory.songs.forEach { directory.managedObject.removeFromSongs($0.managedObject) }
let songsToRemove = songsBeforeFetch.subtracting(Set(album.songs.compactMap{$0.asSong}))
songsToRemove.lazy.compactMap{$0.asSong}.forEach{
directory.managedObject.removeFromSongs($0.managedObject)
}
album.songs.compactMap{$0.asSong}.forEach{
directory.managedObject.addToSongs($0.managedObject)
}
library.saveContext()
} else if directory.id.starts(with: "artist-"){
let artistId = String(directory.id.dropFirst("artist-".count))
guard let artist = library.getArtist(id: artistId) else { return }
let directoriesBeforeFetch = Set(directory.subdirectories)
sync(artist: artist, library: library)

var directoriesAfterFetch: Set<Directory> = Set()
let artistAlbums = library.getAlbums(whichContainsSongsWithArtist: artist)
for album in artistAlbums {
let albumDirId = "album-\(album.id)"
var albumDir: Directory!
if let foundDir = library.getDirectory(id: albumDirId) {
albumDir = foundDir
} else {
albumDir = library.createDirectory()
albumDir.id = albumDirId
}
albumDir.name = album.name
albumDir.artwork = album.artwork
directory.managedObject.addToSubdirectories(albumDir.managedObject)
directoriesAfterFetch.insert(albumDir)
}

let directoriesToRemove = directoriesBeforeFetch.subtracting(directoriesAfterFetch)
directoriesToRemove.forEach{
directory.managedObject.removeFromSubdirectories($0.managedObject)
}
library.saveContext()
}
}

func syncRecentSongs(library: LibraryStorage) {
Expand Down
16 changes: 16 additions & 0 deletions Amperfy/Api/Ampache/AmpacheXmlServerApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ class AmpacheXmlServerApi {
}
}

func requestCatalogs(parserDelegate: AmpacheXmlParser) {
guard var apiUrlComponent = createAuthenticatedApiUrlComponent() else { return }
apiUrlComponent.addQueryItem(name: "action", value: "catalogs")
request(fromUrlComponent: apiUrlComponent, viaXmlParser: parserDelegate)
}

func requestGenres(parserDelegate: AmpacheXmlParser) {
guard var apiUrlComponent = createAuthenticatedApiUrlComponent() else { return }
apiUrlComponent.addQueryItem(name: "action", value: "genres")
Expand Down Expand Up @@ -223,6 +229,16 @@ class AmpacheXmlServerApi {
request(fromUrlComponent: apiUrlComponent, viaXmlParser: parserDelegate)
}

func requestArtistWithinCatalog(of catalog: MusicFolder, parserDelegate: AmpacheXmlParser) {
guard var apiUrlComponent = createAuthenticatedApiUrlComponent() else { return }
apiUrlComponent.addQueryItem(name: "action", value: "advanced_search")
apiUrlComponent.addQueryItem(name: "rule_1", value: "catalog")
apiUrlComponent.addQueryItem(name: "rule_1_operator", value: 0)
apiUrlComponent.addQueryItem(name: "rule_1_input", value: Int(catalog.id) ?? 0)
apiUrlComponent.addQueryItem(name: "type", value: "artist")
request(fromUrlComponent: apiUrlComponent, viaXmlParser: parserDelegate)
}

func requestArtistAlbums(of artist: Artist, parserDelegate: AmpacheXmlParser) {
guard var apiUrlComponent = createAuthenticatedApiUrlComponent() else { return }
apiUrlComponent.addQueryItem(name: "action", value: "artist_albums")
Expand Down
4 changes: 4 additions & 0 deletions Amperfy/Api/Ampache/ArtistParserDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import os.log

class ArtistParserDelegate: AmpacheXmlLibParser {

var artistsParsed = Set<Artist>()
var artistBuffer: Artist?
var genreIdToCreate: String?

Expand Down Expand Up @@ -55,6 +56,9 @@ class ArtistParserDelegate: AmpacheXmlLibParser {
case "artist":
parsedCount += 1
parseNotifier?.notifyParsedObject(ofType: .artist)
if let parsedArtist = artistBuffer {
artistsParsed.insert(parsedArtist)
}
artistBuffer = nil
default:
break
Expand Down
54 changes: 54 additions & 0 deletions Amperfy/Api/Ampache/CatalogParserDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Foundation
import UIKit
import CoreData
import os.log

class CatalogParserDelegate: AmpacheXmlLibParser {

let musicFoldersBeforeFetch: Set<MusicFolder>
var musicFoldersParsed = Set<MusicFolder>()
var musicFolderBuffer: MusicFolder?

init(library: LibraryStorage, syncWave: SyncWave) {
musicFoldersBeforeFetch = Set(library.getMusicFolders())
super.init(library: library, syncWave: syncWave)
}

override func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
super.parser(parser, didStartElement: elementName, namespaceURI: namespaceURI, qualifiedName: qName, attributes: attributeDict)

if(elementName == "catalog") {
guard let id = attributeDict["id"] else {
os_log("Found catalog with no id", log: log, type: .error)
return
}
if let fetchedMusicFolder = library.getMusicFolder(id: id) {
musicFolderBuffer = fetchedMusicFolder
} else {
musicFolderBuffer = library.createMusicFolder()
musicFolderBuffer?.id = id
}
}
}

override func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
switch(elementName) {
case "name":
musicFolderBuffer?.name = buffer
case "catalog":
parsedCount += 1
if let parsedmusicFolder = musicFolderBuffer {
musicFoldersParsed.insert(parsedmusicFolder)
}
musicFolderBuffer = nil
case "root":
let removedMusicFolders = musicFoldersBeforeFetch.subtracting(musicFoldersParsed)
removedMusicFolders.forEach{ library.deleteMusicFolder(musicFolder: $0) }
default:
break
}

super.parser(parser, didEndElement: elementName, namespaceURI: namespaceURI, qualifiedName: qName)
}

}
2 changes: 0 additions & 2 deletions Amperfy/Screens/ViewController/LibraryVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ class LibraryVC: UITableViewController {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
if cell == genreTableViewCell, appDelegate.persistentStorage.librarySyncVersion < .v7 {
return 0
} else if cell == directoriesTableViewCell, appDelegate.backendProxy.selectedApi == .ampache {
return 0
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
Expand Down
11 changes: 11 additions & 0 deletions Amperfy/Storage/LibraryStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,17 @@ class LibraryStorage: PlayableFileCachable {
return albums ?? [Album]()
}

func getAlbums(whichContainsSongsWithArtist artist: Artist) -> [Album] {
let fetchRequest = AlbumMO.identifierSortedFetchRequest
fetchRequest.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [
self.getFetchPredicate(forArtist: artist),
AlbumMO.getFetchPredicateForAlbumsWhoseSongsHave(artist: artist)
])
let foundAlbums = try? context.fetch(fetchRequest)
let albums = foundAlbums?.compactMap{ Album(managedObject: $0) }
return albums ?? [Album]()
}

func getPodcasts() -> [Podcast] {
let fetchRequest = PodcastMO.identifierSortedFetchRequest
let foundPodcasts = try? context.fetch(fetchRequest)
Expand Down
30 changes: 30 additions & 0 deletions AmperfyTests/Cases/API/Ampache/CatalogParserTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import XCTest
@testable import Amperfy

class CatalogParserTest: AbstractAmpacheTest {

override func setUp() {
super.setUp()
xmlData = getTestFileData(name: "catalogs")
recreateParserDelegate()
}

override func recreateParserDelegate() {
parserDelegate = CatalogParserDelegate(library: library, syncWave: syncWave)
}

override func checkCorrectParsing() {
XCTAssertEqual(library.musicFolderCount, 4)

let musicFolders = library.getMusicFolders().sorted(by: {Int($0.id)! < Int($1.id)!} )
XCTAssertEqual(musicFolders[0].id, "1")
XCTAssertEqual(musicFolders[0].name, "music")
XCTAssertEqual(musicFolders[1].id, "2")
XCTAssertEqual(musicFolders[1].name, "video")
XCTAssertEqual(musicFolders[2].id, "3")
XCTAssertEqual(musicFolders[2].name, "podcast")
XCTAssertEqual(musicFolders[3].id, "4")
XCTAssertEqual(musicFolders[3].name, "upload")
}

}
52 changes: 52 additions & 0 deletions AmperfyTests/Cases/API/Ampache/Samples/catalogs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<total_count>4</total_count>
<catalog id="1">
<name><![CDATA[music]]></name>
<type><![CDATA[local]]></type>
<gather_types><![CDATA[music]]></gather_types>
<enabled>1</enabled>
<last_add>1627949046</last_add>
<last_clean>1627949154</last_clean>
<last_update>1626835896</last_update>
<path><![CDATA[/mnt/files-music/ampache-test/music]]></path>
<rename_pattern><![CDATA[%T - %t]]></rename_pattern>
<sort_pattern><![CDATA[%a/%A]]></sort_pattern>
</catalog>
<catalog id="3">
<name><![CDATA[podcast]]></name>
<type><![CDATA[local]]></type>
<gather_types><![CDATA[podcast]]></gather_types>
<enabled>1</enabled>
<last_add>1617260634</last_add>
<last_clean>1617260599</last_clean>
<last_update>0</last_update>
<path><![CDATA[/mnt/files-music/ampache-test/podcast]]></path>
<rename_pattern><![CDATA[%T - %t]]></rename_pattern>
<sort_pattern><![CDATA[%a/%A]]></sort_pattern>
</catalog>
<catalog id="4">
<name><![CDATA[upload]]></name>
<type><![CDATA[local]]></type>
<gather_types><![CDATA[music]]></gather_types>
<enabled>1</enabled>
<last_add>1617260634</last_add>
<last_clean>1617260634</last_clean>
<last_update>0</last_update>
<path><![CDATA[/mnt/files-music/ampache-test/upload]]></path>
<rename_pattern><![CDATA[%T - %t]]></rename_pattern>
<sort_pattern><![CDATA[%a/%A]]></sort_pattern>
</catalog>
<catalog id="2">
<name><![CDATA[video]]></name>
<type><![CDATA[local]]></type>
<gather_types><![CDATA[clip]]></gather_types>
<enabled>1</enabled>
<last_add>1627949054</last_add>
<last_clean>1627949144</last_clean>
<last_update>0</last_update>
<path><![CDATA[/mnt/files-music/ampache-test/video]]></path>
<rename_pattern><![CDATA[%a - %t (%y)]]></rename_pattern>
<sort_pattern><![CDATA[]]></sort_pattern>
</catalog>
</root>

0 comments on commit 5823b1e

Please sign in to comment.