Rewrite of object model to use Unrealm

This addresses issues with JSON serialization
This commit is contained in:
ronaldheft 2022-08-01 09:40:28 -04:00
parent 0b46a9c9b1
commit f6c43e479d
12 changed files with 297 additions and 662 deletions

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -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
}

View file

@ -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)

View file

@ -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 {

View file

@ -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<String, Any> {

View file

@ -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<LocalFile>
@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<String?>
@Persisted var audioFiles: List<LocalAudioFile>
@Persisted var chapters: List<LocalChapter>
@Persisted var tracks: List<LocalAudioTrack>
@Persisted var size: Int64? = nil
@Persisted var duration: Double? = nil
@Persisted var episodes: List<LocalPodcastEpisode>
@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<LocalAuthor>
@Persisted var narrators: List<String?>
@Persisted var genres: List<String?>
@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"
}
}

View file

@ -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)
}
}

View file

@ -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<String, Any> {

View file

@ -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 {

View file

@ -23,16 +23,12 @@ class Database {
}
public func setServerConnectionConfig(config: ServerConnectionConfig) {
var refrence: ThreadSafeReference<ServerConnectionConfig>?
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);
}
}
} catch(let exception) {
NSLog("failed to save server config")
@ -83,25 +71,8 @@ class Database {
}
public func getServerConnectionConfigs() -> [ServerConnectionConfig] {
var refrences: [ThreadSafeReference<ServerConnectionConfig>] = []
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<LocalLibraryItem>] = []
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 {
try! instance.write {
let item = getLocalLibraryItemByLLId(libraryItem: localLibraryItemId)
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)
}
}
}

View file

@ -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