diff --git a/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/App/App/AppDelegate.swift b/ios/App/App/AppDelegate.swift index 105aefab..5ffa1b7e 100644 --- a/ios/App/App/AppDelegate.swift +++ b/ios/App/App/AppDelegate.swift @@ -15,7 +15,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { migrationBlock: { migration, oldSchemaVersion in if (oldSchemaVersion < 1) { NSLog("Realm schema version was \(oldSchemaVersion)") - migration.enumerateObjects(ofType: DeviceSettings.className()) { oldObject, newObject in + migration.enumerateObjects(ofType: DeviceSettings.rlmClassName()) { oldObject, newObject in newObject?["enableAltView"] = false } } @@ -23,6 +23,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ) Realm.Configuration.defaultConfiguration = configuration + Realm.registerRealmables(DeviceSettings.self) + Realm.registerRealmables(ServerConnectionConfig.self) + Realm.registerRealmables(ServerConnectionConfigActiveIndex.self) + + // Data classes + Realm.registerRealmables(LibraryItem.self) + Realm.registerRealmables(MediaType.self) + Realm.registerRealmables(Metadata.self) + Realm.registerRealmables(PodcastEpisode.self) + Realm.registerRealmables(AudioFile.self) + Realm.registerRealmables(Author.self) + Realm.registerRealmables(Chapter.self) + Realm.registerRealmables(AudioTrack.self) + Realm.registerRealmables(FileMetadata.self) + Realm.registerRealmables(Library.self) + Realm.registerRealmables(Folder.self) + Realm.registerRealmables(LibraryFile.self) + Realm.registerRealmables(MediaProgress.self) + Realm.registerRealmables(PlaybackMetadata.self) + + // Local library + Realm.registerRealmables(LocalLibraryItem.self) + Realm.registerRealmables(LocalPodcastEpisode.self) + Realm.registerRealmables(LocalFile.self) + Realm.registerRealmables(LocalMediaProgress.self) + return true } diff --git a/ios/App/App/plugins/AbsDatabase.swift b/ios/App/App/plugins/AbsDatabase.swift index e7ed178c..518f79ff 100644 --- a/ios/App/App/plugins/AbsDatabase.swift +++ b/ios/App/App/plugins/AbsDatabase.swift @@ -35,18 +35,12 @@ public class AbsDatabase: CAPPlugin { let token = call.getString("token", "") let name = "\(address) (\(username))" - let config = ServerConnectionConfig() if id == nil { id = "\(address)@\(username)".toBase64() } - config.id = id! - config.name = name - config.address = address - config.userId = userId - config.username = username - config.token = token + let config = ServerConnectionConfig(id: id!, index: 0, name: name, address: address, userId: userId, username: username, token: token) Store.serverConfig = config call.resolve(convertServerConnectionConfigToJSON(config: config)) @@ -77,7 +71,7 @@ public class AbsDatabase: CAPPlugin { @objc func getLocalLibraryItems(_ call: CAPPluginCall) { do { let items = Database.shared.getLocalLibraryItems() - call.resolve([ "value": try items.asDictionaryArray() ]) + call.resolve([ "value": try items.asDictionaryArray()]) } catch(let exception) { NSLog("error while readling local library items") debugPrint(exception) @@ -128,11 +122,7 @@ public class AbsDatabase: CAPPlugin { let enableAltView = call.getBool("enableAltView") ?? false let jumpBackwardsTime = call.getInt("jumpBackwardsTime") ?? 10 let jumpForwardTime = call.getInt("jumpForwardTime") ?? 10 - let settings = DeviceSettings() - settings.disableAutoRewind = disableAutoRewind - settings.enableAltView = enableAltView - settings.jumpBackwardsTime = jumpBackwardsTime - settings.jumpForwardTime = jumpForwardTime + let settings = DeviceSettings(disableAutoRewind: disableAutoRewind, enableAltView: enableAltView, jumpBackwardsTime: jumpBackwardsTime, jumpForwardTime: jumpForwardTime) Database.shared.setDeviceSettings(deviceSettings: settings) diff --git a/ios/App/Shared/models/DataClasses.swift b/ios/App/Shared/models/DataClasses.swift index b29b7a6f..4130c5b6 100644 --- a/ios/App/Shared/models/DataClasses.swift +++ b/ios/App/Shared/models/DataClasses.swift @@ -7,22 +7,22 @@ import Foundation import CoreMedia -import RealmSwift +import Unrealm -struct LibraryItem: Codable { +struct LibraryItem: Realmable, Codable { var id: String - var ino:String + var ino: String var libraryId: String var folderId: String var path: String var relPath: String var isFile: Bool - var mtimeMs: Int64 - var ctimeMs: Int64 - var birthtimeMs: Int64 - var addedAt: Int64 - var updatedAt: Int64 - var lastScan: Int64? + var mtimeMs: Int + var ctimeMs: Int + var birthtimeMs: Int + var addedAt: Int + var updatedAt: Int + var lastScan: Int? var scanVersion: String? var isMissing: Bool var isInvalid: Bool @@ -30,9 +30,29 @@ struct LibraryItem: Codable { var media: MediaType var libraryFiles: [LibraryFile] var userMediaProgress: MediaProgress? + + init() { + id = "" + ino = "" + libraryId = "" + folderId = "" + path = "" + relPath = "" + isFile = true + mtimeMs = 0 + ctimeMs = 0 + birthtimeMs = 0 + addedAt = 0 + updatedAt = 0 + isMissing = false + isInvalid = false + mediaType = "" + media = MediaType() + libraryFiles = [] + } } -struct MediaType: Codable { +struct MediaType: Realmable, Codable { var libraryItemId: String? var metadata: Metadata var coverPath: String? @@ -40,13 +60,17 @@ struct MediaType: Codable { var audioFiles: [AudioFile]? var chapters: [Chapter]? var tracks: [AudioTrack]? - var size: Int64? + var size: Int? var duration: Double? var episodes: [PodcastEpisode]? var autoDownloadEpisodes: Bool? + + init() { + metadata = Metadata() + } } -struct Metadata: Codable { +struct Metadata: Realmable, Codable { var title: String var subtitle: String? var authors: [Author]? @@ -65,9 +89,15 @@ struct Metadata: Codable { var narratorName: String? var seriesName: String? var feedUrl: String? + + init() { + title = "Unknown" + genres = [] + explicit = false + } } -struct PodcastEpisode: Codable { +struct PodcastEpisode: Realmable, Codable { var id: String var index: Int var episode: String? @@ -78,30 +108,55 @@ struct PodcastEpisode: Codable { var audioFile: AudioFile? var audioTrack: AudioTrack? var duration: Double - var size: Int64 + var size: Int // var serverEpisodeId: String? + + init() { + id = "" + index = 0 + title = "Unknown" + duration = 0 + size = 0 + } } -struct AudioFile: Codable { +struct AudioFile: Realmable, Codable { var index: Int var ino: String var metadata: FileMetadata + + init() { + index = 0 + ino = "" + metadata = FileMetadata() + } } -struct Author: Codable { +struct Author: Realmable, Codable { var id: String var name: String var coverPath: String? + + init() { + id = "" + name = "Unknown" + } } -struct Chapter: Codable { +struct Chapter: Realmable, Codable { var id: Int var start: Double var end: Double var title: String? + + init() { + id = 0 + start = 0 + end = 0 + } } -struct AudioTrack: Codable { +struct AudioTrack: Realmable, Codable { var index: Int? var startOffset: Double? var duration: Double @@ -113,50 +168,101 @@ struct AudioTrack: Codable { // var localFileId: String? // var audioProbeResult: AudioProbeResult? Needed for local playback var serverIndex: Int? + + init() { + duration = 0 + mimeType = "" + } } -struct FileMetadata: Codable { +struct FileMetadata: Realmable, Codable { var filename: String var ext: String var path: String var relPath: String + + init() { + filename = "" + ext = "" + path = "" + relPath = "" + } } -struct Library: Codable { +struct Library: Realmable, Codable { var id: String var name: String var folders: [Folder] var icon: String var mediaType: String + + init() { + id = "" + name = "Unknown" + folders = [] + icon = "" + mediaType = "" + } } -struct Folder: Codable { +struct Folder: Realmable, Codable { var id: String var fullPath: String + + init() { + id = "" + fullPath = "" + } } -struct LibraryFile: Codable { +struct LibraryFile: Realmable, Codable { var ino: String var metadata: FileMetadata + + init() { + ino = "" + metadata = FileMetadata() + } } -struct MediaProgress:Codable { - var id:String - var libraryItemId:String - var episodeId:String? - var duration:Double - var progress:Double - var currentTime:Double - var isFinished:Bool - var lastUpdate:Int64 - var startedAt:Int64 - var finishedAt:Int64? +struct MediaProgress: Realmable, Codable { + var id: String + var libraryItemId: String + var episodeId: String? + var duration: Double + var progress: Double + var currentTime: Double + var isFinished: Bool + var lastUpdate: Int + var startedAt: Int + var finishedAt: Int? + + init() { + id = "" + libraryItemId = "" + duration = 0 + progress = 0 + currentTime = 0 + isFinished = false + lastUpdate = 0 + startedAt = 0 + } } -struct PlaybackMetadata: Codable { +struct PlaybackMetadata: Realmable, Codable { var duration: Double var currentTime: Double var playerState: PlayerState + + init() { + duration = 0 + currentTime = 0 + playerState = PlayerState.IDLE + } + + static func ignoredProperties() -> [String] { + return ["playerState"] + } } enum PlayerState: Codable { diff --git a/ios/App/Shared/models/DeviceSettings.swift b/ios/App/Shared/models/DeviceSettings.swift index cd823916..34d29bd6 100644 --- a/ios/App/Shared/models/DeviceSettings.swift +++ b/ios/App/Shared/models/DeviceSettings.swift @@ -7,21 +7,17 @@ import Foundation import RealmSwift +import Unrealm -class DeviceSettings: Object { - @Persisted var disableAutoRewind: Bool - @Persisted var enableAltView: Bool - @Persisted var jumpBackwardsTime: Int - @Persisted var jumpForwardTime: Int +struct DeviceSettings: Realmable { + var disableAutoRewind: Bool = false + var enableAltView: Bool = false + var jumpBackwardsTime: Int = 10 + var jumpForwardTime: Int = 10 } func getDefaultDeviceSettings() -> DeviceSettings { - let settings = DeviceSettings() - settings.disableAutoRewind = false - settings.enableAltView = false - settings.jumpForwardTime = 10 - settings.jumpBackwardsTime = 10 - return settings + return DeviceSettings() } func deviceSettingsToJSON(settings: DeviceSettings) -> Dictionary { diff --git a/ios/App/Shared/models/LocalLibrary.swift b/ios/App/Shared/models/LocalLibrary.swift index fb5c4f36..f0e49887 100644 --- a/ios/App/Shared/models/LocalLibrary.swift +++ b/ios/App/Shared/models/LocalLibrary.swift @@ -6,139 +6,81 @@ // import Foundation -import RealmSwift +import Unrealm -class LocalLibraryItem: Object, Encodable { - @Persisted(primaryKey: true) var id: String = "local_\(UUID().uuidString)" - @Persisted var basePath: String = "" - @Persisted var absolutePath: String = "" - @Persisted var contentUrl: String - @Persisted var isInvalid: Bool = false - @Persisted var mediaType: String - @Persisted var media: LocalMediaType? - @Persisted var localFiles: List - @Persisted var coverContentUrl: String? = nil - @Persisted var coverAbsolutePath: String? = nil - @Persisted var isLocal: Bool = true - @Persisted var serverConnectionConfigId: String? = nil - @Persisted var serverAddress: String? = nil - @Persisted var serverUserId: String? = nil - @Persisted var libraryItemId: String? = nil +struct LocalLibraryItem: Realmable, Codable { + var id: String = "local_\(UUID().uuidString)" + var basePath: String = "" + var absolutePath: String = "" + var contentUrl: String = "" + var isInvalid: Bool = false + var mediaType: String = "" + var media: MediaType? + var localFiles: [LocalFile] = [] + var coverContentUrl: String? + var coverAbsolutePath: String? + var isLocal: Bool = true + var serverConnectionConfigId: String? + var serverAddress: String? + var serverUserId: String? + var libraryItemId: String? + + static func primaryKey() -> String? { + return "id" + } } -class LocalMediaType: Object, Encodable { - @Persisted var libraryItemId: String? = "" - @Persisted var metadata: LocalMetadata? - @Persisted var coverPath: String? = "" - @Persisted var tags: List - @Persisted var audioFiles: List - @Persisted var chapters: List - @Persisted var tracks: List - @Persisted var size: Int64? = nil - @Persisted var duration: Double? = nil - @Persisted var episodes: List - @Persisted var autoDownloadEpisodes: Bool? = nil +struct LocalPodcastEpisode: Realmable, Codable { + var id: String = UUID().uuidString + var index: Int = 0 + var episode: String? + var episodeType: String? + var title: String = "Unknown" + var subtitle: String? + var desc: String? + var audioFile: AudioFile? + var audioTrack: AudioTrack? + var duration: Double = 0 + var size: Int = 0 + var serverEpisodeId: String? + + static func primaryKey() -> String? { + return "id" + } } -class LocalMetadata: Object, Encodable { - @Persisted var title: String - @Persisted var subtitle: String? = "" - @Persisted var authors: List - @Persisted var narrators: List - @Persisted var genres: List - @Persisted var publishedYear: String? = "" - @Persisted var publishedDate: String? = "" - @Persisted var publisher: String? = "" - @Persisted var desc: String? = "" - @Persisted var isbn: String? = "" - @Persisted var asin: String? = "" - @Persisted var language: String? = "" - @Persisted var explicit: Bool - @Persisted var authorName: String? = "" - @Persisted var authorNameLF: String? = "" - @Persisted var narratorName: String? = "" - @Persisted var seriesName: String? = "" - @Persisted var feedUrl: String? = "" +struct LocalFile: Realmable, Codable { + var id: String = UUID().uuidString + var filename: String? + var contentUrl: String = "" + var absolutePath: String = "" + var mimeType: String? + var size: Int = 0 + + static func primaryKey() -> String? { + return "id" + } } -class LocalPodcastEpisode: Object, Encodable { - @Persisted var id: String = UUID().uuidString - @Persisted var index: Int - @Persisted var episode: String? = "" - @Persisted var episodeType: String? = "" - @Persisted var title: String - @Persisted var subtitle: String? = "" - @Persisted var desc: String? = "" - @Persisted var audioFile: LocalAudioFile? = nil - @Persisted var audioTrack: LocalAudioTrack? = nil - @Persisted var duration: Double - @Persisted var size: Int64 - @Persisted var serverEpisodeId: String? -} - -class LocalAudioFile: Object, Encodable { - @Persisted var index: Int - @Persisted var ino: String - @Persisted var metadata: LocalFileMetadata? -} - -class LocalAuthor: Object, Encodable { - @Persisted var id: String = UUID().uuidString - @Persisted var name: String - @Persisted var coverPath: String? = "" -} - -class LocalChapter: Object, Encodable { - @Persisted var id: Int - @Persisted var start: Double - @Persisted var end: Double - @Persisted var title: String? = nil -} - -class LocalAudioTrack: Object, Encodable { - @Persisted var index: Int? = nil - @Persisted var startOffset: Double? = nil - @Persisted var duration: Double - @Persisted var title: String? = "" - @Persisted var contentUrl: String? = "" - @Persisted var mimeType: String - @Persisted var metadata: LocalFileMetadata? = nil - @Persisted var isLocal: Bool = true - @Persisted var localFileId: String? = "" - @Persisted var serverIndex: Int? = nil -} - -class LocalFileMetadata: Object, Encodable { - @Persisted var filename: String - @Persisted var ext: String - @Persisted var path: String - @Persisted var relPath: String -} - -class LocalFile: Object, Encodable { - @Persisted var id: String = UUID().uuidString - @Persisted var filename: String? = "" - @Persisted var contentUrl: String - @Persisted var absolutePath: String - @Persisted var mimeType: String? = "" - @Persisted var size: Int64 -} - -class LocalMediaProgress: Object, Encodable { - @Persisted(primaryKey: true) var id: String = UUID().uuidString - @Persisted var localLibraryItemId: String - @Persisted var localEpisodeId: String? = "" - @Persisted var duration: Double - @Persisted var progress: Double // 0 to 1 - @Persisted var currentTime: Double - @Persisted var isFinished: Bool - @Persisted var lastUpdate: Int64 - @Persisted var startedAt: Int64 - @Persisted var finishedAt: Int64? = nil +struct LocalMediaProgress: Realmable, Codable { + var id: String = UUID().uuidString + var localLibraryItemId: String = "" + var localEpisodeId: String? + var duration: Double = 0 + var progress: Double = 0 + var currentTime: Double = 0 + var isFinished: Bool = false + var lastUpdate: Int = 0 + var startedAt: Int = 0 + var finishedAt: Int? // For local lib items from server to support server sync - @Persisted var serverConnectionConfigId: String? = "" - @Persisted var serverAddress: String? = "" - @Persisted var serverUserId: String? = "" - @Persisted var libraryItemId: String? = "" - @Persisted var episodeId: String? = "" + var serverConnectionConfigId: String? + var serverAddress: String? + var serverUserId: String? + var libraryItemId: String? + var episodeId: String? + + static func primaryKey() -> String? { + return "id" + } } diff --git a/ios/App/Shared/models/LocalLibraryExtensions.swift b/ios/App/Shared/models/LocalLibraryExtensions.swift index c08337ce..e05c6be0 100644 --- a/ios/App/Shared/models/LocalLibraryExtensions.swift +++ b/ios/App/Shared/models/LocalLibraryExtensions.swift @@ -8,49 +8,12 @@ import Foundation extension LocalLibraryItem { - enum CodingKeys: CodingKey { - case id - case basePath - case absolutePath - case contentUrl - case isInvalid - case mediaType - case media - case localFiles - case coverContentUrl - case coverAbsolutePath - case isLocal - case serverConnectionConfigId - case serverAddress - case serverUserId - case libraryItemId - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(basePath, forKey: .basePath) - try container.encode(absolutePath, forKey: .absolutePath) - try container.encode(contentUrl, forKey: .contentUrl) - try container.encode(isInvalid, forKey: .isInvalid) - try container.encode(mediaType, forKey: .mediaType) - try container.encode(media, forKey: .media) - try container.encode(localFiles, forKey: .localFiles) - try container.encode(coverContentUrl, forKey: .coverContentUrl) - try container.encode(coverAbsolutePath, forKey: .coverAbsolutePath) - try container.encode(isLocal, forKey: .isLocal) - try container.encode(serverConnectionConfigId, forKey: .serverConnectionConfigId) - try container.encode(serverAddress, forKey: .serverAddress) - try container.encode(serverUserId, forKey: .serverUserId) - try container.encode(libraryItemId, forKey: .libraryItemId) - } - - convenience init(_ item: LibraryItem, localUrl: URL, server: ServerConnectionConfig, files: [LocalFile]) { + init(_ item: LibraryItem, localUrl: URL, server: ServerConnectionConfig, files: [LocalFile]) { self.init() self.contentUrl = localUrl.absoluteString self.mediaType = item.mediaType - self.media = LocalMediaType(item.media, coverPath: "", files: files) - self.localFiles.append(objectsIn: files) + self.media = item.media + self.localFiles = files // TODO: self.coverContentURL // TODO: self.converAbsolutePath self.libraryItemId = item.id @@ -61,7 +24,7 @@ extension LocalLibraryItem { func getDuration() -> Double { var total = 0.0 - self.media?.tracks.forEach { track in total += track.duration } + self.media?.tracks?.forEach { track in total += track.duration } return total } @@ -107,315 +70,14 @@ extension LocalLibraryItem { } } -extension LocalMediaType { - enum CodingKeys: CodingKey { - case libraryItemId - case metadata - case coverPath - case tags - case audioFiles - case chapters - case tracks - case size - case duration - case episodes - case autoDownloadEpisodes - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(libraryItemId, forKey: .libraryItemId) - try container.encode(metadata, forKey: .metadata) - try container.encode(coverPath, forKey: .coverPath) - try container.encode(tags, forKey: .tags) - try container.encode(audioFiles, forKey: .audioFiles) - try container.encode(chapters, forKey: .chapters) - try container.encode(tracks, forKey: .tracks) - try container.encode(size, forKey: .size) - try container.encode(duration, forKey: .duration) - try container.encode(episodes, forKey: .episodes) - try container.encode(autoDownloadEpisodes, forKey: .autoDownloadEpisodes) - } - - convenience init(_ mediaType: MediaType, coverPath: String, files: [LocalFile]) { - self.init() - self.libraryItemId = mediaType.libraryItemId - self.metadata = LocalMetadata(mediaType.metadata) - self.coverPath = coverPath - self.tags.append(objectsIn: mediaType.tags ?? []) - self.audioFiles.append(objectsIn: mediaType.audioFiles!.enumerated().map() { - i, audioFile -> LocalAudioFile in LocalAudioFile(audioFile) - }) - self.chapters.append(objectsIn: mediaType.chapters!.enumerated().map() { - i, chapter -> LocalChapter in LocalChapter(chapter) - }) - self.tracks.append(objectsIn: mediaType.tracks!.enumerated().map() { - i, track in LocalAudioTrack(track, libraryItemId: self.libraryItemId ?? "", filename: files[i].filename ?? "") - }) - self.size = mediaType.size - self.duration = mediaType.duration - // TODO: self.episodes - // TODO: Handle podcast auto downloads - self.autoDownloadEpisodes = mediaType.autoDownloadEpisodes - } -} - -extension LocalMetadata { - enum CodingKeys: CodingKey { - case title - case subtitle - case authors - case narrators - case genres - case publishedYear - case publishedDate - case publisher - case desc - case isbn - case asin - case language - case explicit - case authorName - case authorNameLF - case narratorName - case seriesName - case feedUrl - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(title, forKey: .title) - try container.encode(subtitle, forKey: .subtitle) - try container.encode(authors, forKey: .authors) - try container.encode(narrators, forKey: .narrators) - try container.encode(genres, forKey: .genres) - try container.encode(publishedYear, forKey: .publishedYear) - try container.encode(publishedDate, forKey: .publishedDate) - try container.encode(publisher, forKey: .publisher) - try container.encode(desc, forKey: .desc) - try container.encode(isbn, forKey: .isbn) - try container.encode(asin, forKey: .asin) - try container.encode(language, forKey: .language) - try container.encode(explicit, forKey: .explicit) - try container.encode(authorName, forKey: .authorName) - try container.encode(authorNameLF, forKey: .authorNameLF) - try container.encode(narratorName, forKey: .narratorName) - try container.encode(seriesName, forKey: .seriesName) - try container.encode(feedUrl, forKey: .feedUrl) - } - - convenience init(_ metadata: Metadata) { - self.init() - self.title = metadata.title - self.subtitle = metadata.subtitle - self.narrators.append(objectsIn: metadata.narrators ?? []) - self.genres.append(objectsIn: metadata.genres) - self.publishedYear = metadata.publishedYear - self.publishedDate = metadata.publishedDate - self.publisher = metadata.publisher - self.desc = metadata.description - self.isbn = metadata.isbn - self.asin = metadata.asin - self.language = metadata.language - self.explicit = metadata.explicit - self.authorName = metadata.authorName - self.authorNameLF = metadata.authorNameLF - self.narratorName = metadata.narratorName - self.seriesName = metadata.seriesName - self.feedUrl = metadata.feedUrl - } -} - -extension LocalPodcastEpisode { - enum CodingKeys: CodingKey { - case id - case index - case episode - case episodeType - case title - case subtitle - case desc - case audioFile - case audioTrack - case duration - case size - case serverEpisodeId - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(index, forKey: .index) - try container.encode(episode, forKey: .episode) - try container.encode(episodeType, forKey: .episodeType) - try container.encode(title, forKey: .title) - try container.encode(subtitle, forKey: .subtitle) - try container.encode(desc, forKey: .desc) - try container.encode(audioFile, forKey: .audioFile) - try container.encode(audioTrack, forKey: .audioTrack) - try container.encode(duration, forKey: .duration) - try container.encode(size, forKey: .size) - try container.encode(serverEpisodeId, forKey: .serverEpisodeId) - } -} - -extension LocalAudioFile { - enum CodingKeys: CodingKey { - case index - case ino - case metadata - } - - convenience init(_ audioFile: AudioFile) { - self.init() - self.index = audioFile.index - self.ino = audioFile.ino - // self.metadata - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(index, forKey: .index) - try container.encode(ino, forKey: .ino) - try container.encode(metadata, forKey: .metadata) - } -} - -extension LocalAuthor { - enum CodingKeys: CodingKey { - case id - case name - case coverPath - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(name, forKey: .name) - try container.encode(coverPath, forKey: .coverPath) - } - - convenience init(_ author: Author) { - self.init() - self.id = author.id - self.name = author.name - // self.coverPath - } -} - -extension LocalChapter { - enum CodingKeys: CodingKey { - case id - case start - case end - case title - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(start, forKey: .start) - try container.encode(end, forKey: .end) - try container.encode(title, forKey: .title) - } - - convenience init(_ chapter: Chapter) { - self.init() - self.id = chapter.id - self.start = chapter.start - self.end = chapter.end - self.title = chapter.title - } -} - -extension LocalAudioTrack { - enum CodingKeys: CodingKey { - case index - case startOffset - case duration - case title - case contentUrl - case mimeType - case metadata - case isLocal - case localFileId - case serverIndex - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(index, forKey: .index) - try container.encode(startOffset, forKey: .startOffset) - try container.encode(duration, forKey: .duration) - try container.encode(title, forKey: .title) - try container.encode(contentUrl, forKey: .contentUrl) - try container.encode(mimeType, forKey: .mimeType) - try container.encode(metadata, forKey: .metadata) - try container.encode(isLocal, forKey: .isLocal) - try container.encode(localFileId, forKey: .localFileId) - try container.encode(serverIndex, forKey: .serverIndex) - } - - convenience init(_ track: AudioTrack, libraryItemId: String, filename: String) { - self.init() - self.index = track.index - self.startOffset = track.startOffset - self.duration = track.duration - self.title = track.title - self.contentUrl = "" // TODO: Different URL - self.mimeType = track.mimeType - // TODO: self.metadata - self.localFileId = "\(libraryItemId)_\(filename.toBase64())" - self.serverIndex = track.serverIndex - } -} - -extension LocalFileMetadata { - enum CodingKeys: CodingKey { - case filename - case ext - case path - case relPath - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(filename, forKey: .filename) - try container.encode(ext, forKey: .ext) - try container.encode(path, forKey: .path) - try container.encode(relPath, forKey: .relPath) - } - - /* TODO: Can we skip this object? */ -} - extension LocalFile { - enum CodingKeys: CodingKey { - case id - case filename - case contentUrl - case absolutePath - case mimeType - case size - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(filename, forKey: .filename) - try container.encode(contentUrl, forKey: .contentUrl) - try container.encode(absolutePath, forKey: .absolutePath) - try container.encode(mimeType, forKey: .mimeType) - try container.encode(size, forKey: .size) - } - - convenience init(_ libraryItemId: String, _ filename: String, _ mimeType: String, _ localUrl: URL) { + init(_ libraryItemId: String, _ filename: String, _ mimeType: String, _ localUrl: URL) { self.init() self.id = "\(libraryItemId)_\(filename.toBase64())" self.filename = filename self.contentUrl = localUrl.absoluteString self.absolutePath = localUrl.path - self.size = localUrl.fileSize + self.size = Int(localUrl.fileSize) } func isAudioFile() -> Bool { @@ -428,42 +90,3 @@ extension LocalFile { } } } - -extension LocalMediaProgress { - enum CodingKeys: CodingKey { - case id - case localLibraryItemId - case localEpisodeId - case duration - case progress - case currentTime - case isFinished - case lastUpdate - case startedAt - case finishedAt - case serverConnectionConfigId - case serverAddress - case serverUserId - case libraryItemId - case episodeId - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(localLibraryItemId, forKey: .localLibraryItemId) - try container.encode(localEpisodeId, forKey: .localEpisodeId) - try container.encode(duration, forKey: .duration) - try container.encode(progress, forKey: .progress) - try container.encode(currentTime, forKey: .currentTime) - try container.encode(isFinished, forKey: .isFinished) - try container.encode(lastUpdate, forKey: .lastUpdate) - try container.encode(startedAt, forKey: .startedAt) - try container.encode(finishedAt, forKey: .finishedAt) - try container.encode(serverConnectionConfigId, forKey: .serverConnectionConfigId) - try container.encode(serverAddress, forKey: .serverAddress) - try container.encode(serverUserId, forKey: .serverUserId) - try container.encode(libraryItemId, forKey: .libraryItemId) - try container.encode(episodeId, forKey: .episodeId) - } -} diff --git a/ios/App/Shared/models/ServerConnectionConfig.swift b/ios/App/Shared/models/ServerConnectionConfig.swift index c8ace3e1..95ed644f 100644 --- a/ios/App/Shared/models/ServerConnectionConfig.swift +++ b/ios/App/Shared/models/ServerConnectionConfig.swift @@ -7,20 +7,29 @@ import Foundation import RealmSwift +import Unrealm -class ServerConnectionConfig: Object { - @Persisted(primaryKey: true) var id: String - @Persisted(indexed: true) var index: Int - @Persisted var name: String - @Persisted var address: String - @Persisted var userId: String - @Persisted var username: String - @Persisted var token: String +struct ServerConnectionConfig: Realmable { + var id: String = UUID().uuidString + var index: Int = 0 + var name: String = "" + var address: String = "" + var userId: String = "" + var username: String = "" + var token: String = "" + + static func primaryKey() -> String? { + return "id" + } + + static func indexedProperties() -> [String] { + return ["index"] + } } -class ServerConnectionConfigActiveIndex: Object { - // This could overflow, but you really would have to try - @Persisted var index: Int? +struct ServerConnectionConfigActiveIndex: Realmable { + // This could overflow, but you really would have to try + var index: Int? } func convertServerConnectionConfigToJSON(config: ServerConnectionConfig) -> Dictionary { diff --git a/ios/App/Shared/util/ApiClient.swift b/ios/App/Shared/util/ApiClient.swift index f25e4bb9..c9ed9e97 100644 --- a/ios/App/Shared/util/ApiClient.swift +++ b/ios/App/Shared/util/ApiClient.swift @@ -114,9 +114,11 @@ class ApiClient { callback(session) } } + public static func reportPlaybackProgress(report: PlaybackReport, sessionId: String) { try? postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report.asDictionary().mapValues({ value in "\(value)" }), callback: nil) } + public static func getLibraryItemWithProgress(libraryItemId:String, episodeId:String?, callback: @escaping (_ param: LibraryItem?) -> Void) { var endpoint = "api/items/\(libraryItemId)?expanded=1&include=progress" if episodeId != nil { diff --git a/ios/App/Shared/util/Database.swift b/ios/App/Shared/util/Database.swift index 0ec073d9..7bc6afc1 100644 --- a/ios/App/Shared/util/Database.swift +++ b/ios/App/Shared/util/Database.swift @@ -23,16 +23,12 @@ class Database { } public func setServerConnectionConfig(config: ServerConnectionConfig) { - var refrence: ThreadSafeReference? - if config.realm != nil { - refrence = ThreadSafeReference(to: config) - } - + var config = config Database.realmQueue.sync { - let existing: ServerConnectionConfig? = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: config.id) + var existing: ServerConnectionConfig? = instance.object(ofType: ServerConnectionConfig.self, forPrimaryKey: config.id) if config.index == 0 { - let lastConfig: ServerConnectionConfig? = instance.objects(ServerConnectionConfig.self).last + var lastConfig: ServerConnectionConfig? = instance.objects(ServerConnectionConfig.self).last if lastConfig != nil { config.index = lastConfig!.index + 1 @@ -46,15 +42,7 @@ class Database { if existing != nil { instance.delete(existing!) } - if refrence == nil { - instance.add(config) - } else { - guard let resolved = instance.resolve(refrence!) else { - throw "unable to resolve refrence" - } - - instance.add(resolved); - } + instance.add(config) } } catch(let exception) { NSLog("failed to save server config") @@ -83,25 +71,8 @@ class Database { } public func getServerConnectionConfigs() -> [ServerConnectionConfig] { - var refrences: [ThreadSafeReference] = [] - Database.realmQueue.sync { - let configs = instance.objects(ServerConnectionConfig.self) - refrences = configs.map { config in - return ThreadSafeReference(to: config) - } - } - - do { - let realm = try Realm() - - return refrences.map { refrence in - return realm.resolve(refrence)! - } - } catch(let exception) { - NSLog("error while readling configs") - debugPrint(exception) - return [] + return Array(instance.objects(ServerConnectionConfig.self)) } } @@ -113,7 +84,7 @@ class Database { public func setLastActiveConfigIndex(index: Int?) { let existing = instance.objects(ServerConnectionConfigActiveIndex.self) - let obj = ServerConnectionConfigActiveIndex() + var obj = ServerConnectionConfigActiveIndex() obj.index = index do { @@ -149,25 +120,8 @@ class Database { } public func getLocalLibraryItems(mediaType: MediaType? = nil) -> [LocalLibraryItem] { - var localLibraryItems: [ThreadSafeReference] = [] - Database.realmQueue.sync { - let items = instance.objects(LocalLibraryItem.self) - localLibraryItems = items.map { item in - return ThreadSafeReference(to: item) - } - } - - do { - let realm = try Realm() - - return localLibraryItems.map { item in - return realm.resolve(item)! - } - } catch(let exception) { - NSLog("error while readling local library items") - debugPrint(exception) - return [] + Array(instance.objects(LocalLibraryItem.self)) } } @@ -183,26 +137,14 @@ class Database { } public func getLocalLibraryItem(localLibraryItem: String) -> LocalLibraryItem? { - let items = getLocalLibraryItems() - for item in items { - if (item.id == localLibraryItem) { - return item - } + Database.realmQueue.sync { + instance.object(ofType: LocalLibraryItem.self, forPrimaryKey: localLibraryItem) } - NSLog("Local library item with id \(localLibraryItem) not found") - return nil } public func saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) { Database.realmQueue.sync { - do { - try instance.write { - instance.add(localLibraryItem); - } - } catch(let exception) { - NSLog("Unable to save local library item") - debugPrint(exception) - } + try! instance.write { instance.add(localLibraryItem) } } } @@ -213,19 +155,10 @@ class Database { } public func removeLocalLibraryItem(localLibraryItemId: String) { - let item = getLocalLibraryItemByLLId(libraryItem: localLibraryItemId) Database.realmQueue.sync { - do { - try instance.write { - if item != nil { - instance.delete(item!) - } else { - NSLog("Unable to find local library item to delete") - } - } - } catch (let exception) { - NSLog("Unable to delete local library item") - debugPrint(exception) + try! instance.write { + let item = getLocalLibraryItemByLLId(libraryItem: localLibraryItemId) + instance.delete(item!) } } } diff --git a/ios/App/Shared/util/Store.swift b/ios/App/Shared/util/Store.swift index 52fbd059..210664e1 100644 --- a/ios/App/Shared/util/Store.swift +++ b/ios/App/Shared/util/Store.swift @@ -9,7 +9,7 @@ import Foundation import RealmSwift class Store { - @ThreadSafe private static var _serverConfig: ServerConnectionConfig? + private static var _serverConfig: ServerConnectionConfig? public static var serverConfig: ServerConnectionConfig? { get { return _serverConfig diff --git a/package-lock.json b/package-lock.json index 13c38711..125a8b38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32982,4 +32982,4 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } -} \ No newline at end of file +}