mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-15 16:34:43 +02:00
Rewrite of object model to use Unrealm
This addresses issues with JSON serialization
This commit is contained in:
parent
0b46a9c9b1
commit
f6c43e479d
12 changed files with 297 additions and 662 deletions
|
@ -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>
|
|
@ -15,7 +15,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
migrationBlock: { migration, oldSchemaVersion in
|
migrationBlock: { migration, oldSchemaVersion in
|
||||||
if (oldSchemaVersion < 1) {
|
if (oldSchemaVersion < 1) {
|
||||||
NSLog("Realm schema version was \(oldSchemaVersion)")
|
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
|
newObject?["enableAltView"] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
)
|
)
|
||||||
Realm.Configuration.defaultConfiguration = configuration
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,18 +35,12 @@ public class AbsDatabase: CAPPlugin {
|
||||||
let token = call.getString("token", "")
|
let token = call.getString("token", "")
|
||||||
|
|
||||||
let name = "\(address) (\(username))"
|
let name = "\(address) (\(username))"
|
||||||
let config = ServerConnectionConfig()
|
|
||||||
|
|
||||||
if id == nil {
|
if id == nil {
|
||||||
id = "\(address)@\(username)".toBase64()
|
id = "\(address)@\(username)".toBase64()
|
||||||
}
|
}
|
||||||
|
|
||||||
config.id = id!
|
let config = ServerConnectionConfig(id: id!, index: 0, name: name, address: address, userId: userId, username: username, token: token)
|
||||||
config.name = name
|
|
||||||
config.address = address
|
|
||||||
config.userId = userId
|
|
||||||
config.username = username
|
|
||||||
config.token = token
|
|
||||||
|
|
||||||
Store.serverConfig = config
|
Store.serverConfig = config
|
||||||
call.resolve(convertServerConnectionConfigToJSON(config: config))
|
call.resolve(convertServerConnectionConfigToJSON(config: config))
|
||||||
|
@ -77,7 +71,7 @@ public class AbsDatabase: CAPPlugin {
|
||||||
@objc func getLocalLibraryItems(_ call: CAPPluginCall) {
|
@objc func getLocalLibraryItems(_ call: CAPPluginCall) {
|
||||||
do {
|
do {
|
||||||
let items = Database.shared.getLocalLibraryItems()
|
let items = Database.shared.getLocalLibraryItems()
|
||||||
call.resolve([ "value": try items.asDictionaryArray() ])
|
call.resolve([ "value": try items.asDictionaryArray()])
|
||||||
} catch(let exception) {
|
} catch(let exception) {
|
||||||
NSLog("error while readling local library items")
|
NSLog("error while readling local library items")
|
||||||
debugPrint(exception)
|
debugPrint(exception)
|
||||||
|
@ -128,11 +122,7 @@ public class AbsDatabase: CAPPlugin {
|
||||||
let enableAltView = call.getBool("enableAltView") ?? false
|
let enableAltView = call.getBool("enableAltView") ?? false
|
||||||
let jumpBackwardsTime = call.getInt("jumpBackwardsTime") ?? 10
|
let jumpBackwardsTime = call.getInt("jumpBackwardsTime") ?? 10
|
||||||
let jumpForwardTime = call.getInt("jumpForwardTime") ?? 10
|
let jumpForwardTime = call.getInt("jumpForwardTime") ?? 10
|
||||||
let settings = DeviceSettings()
|
let settings = DeviceSettings(disableAutoRewind: disableAutoRewind, enableAltView: enableAltView, jumpBackwardsTime: jumpBackwardsTime, jumpForwardTime: jumpForwardTime)
|
||||||
settings.disableAutoRewind = disableAutoRewind
|
|
||||||
settings.enableAltView = enableAltView
|
|
||||||
settings.jumpBackwardsTime = jumpBackwardsTime
|
|
||||||
settings.jumpForwardTime = jumpForwardTime
|
|
||||||
|
|
||||||
Database.shared.setDeviceSettings(deviceSettings: settings)
|
Database.shared.setDeviceSettings(deviceSettings: settings)
|
||||||
|
|
||||||
|
|
|
@ -7,22 +7,22 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreMedia
|
import CoreMedia
|
||||||
import RealmSwift
|
import Unrealm
|
||||||
|
|
||||||
struct LibraryItem: Codable {
|
struct LibraryItem: Realmable, Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var ino:String
|
var ino: String
|
||||||
var libraryId: String
|
var libraryId: String
|
||||||
var folderId: String
|
var folderId: String
|
||||||
var path: String
|
var path: String
|
||||||
var relPath: String
|
var relPath: String
|
||||||
var isFile: Bool
|
var isFile: Bool
|
||||||
var mtimeMs: Int64
|
var mtimeMs: Int
|
||||||
var ctimeMs: Int64
|
var ctimeMs: Int
|
||||||
var birthtimeMs: Int64
|
var birthtimeMs: Int
|
||||||
var addedAt: Int64
|
var addedAt: Int
|
||||||
var updatedAt: Int64
|
var updatedAt: Int
|
||||||
var lastScan: Int64?
|
var lastScan: Int?
|
||||||
var scanVersion: String?
|
var scanVersion: String?
|
||||||
var isMissing: Bool
|
var isMissing: Bool
|
||||||
var isInvalid: Bool
|
var isInvalid: Bool
|
||||||
|
@ -30,9 +30,29 @@ struct LibraryItem: Codable {
|
||||||
var media: MediaType
|
var media: MediaType
|
||||||
var libraryFiles: [LibraryFile]
|
var libraryFiles: [LibraryFile]
|
||||||
var userMediaProgress: MediaProgress?
|
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 libraryItemId: String?
|
||||||
var metadata: Metadata
|
var metadata: Metadata
|
||||||
var coverPath: String?
|
var coverPath: String?
|
||||||
|
@ -40,13 +60,17 @@ struct MediaType: Codable {
|
||||||
var audioFiles: [AudioFile]?
|
var audioFiles: [AudioFile]?
|
||||||
var chapters: [Chapter]?
|
var chapters: [Chapter]?
|
||||||
var tracks: [AudioTrack]?
|
var tracks: [AudioTrack]?
|
||||||
var size: Int64?
|
var size: Int?
|
||||||
var duration: Double?
|
var duration: Double?
|
||||||
var episodes: [PodcastEpisode]?
|
var episodes: [PodcastEpisode]?
|
||||||
var autoDownloadEpisodes: Bool?
|
var autoDownloadEpisodes: Bool?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
metadata = Metadata()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Metadata: Codable {
|
struct Metadata: Realmable, Codable {
|
||||||
var title: String
|
var title: String
|
||||||
var subtitle: String?
|
var subtitle: String?
|
||||||
var authors: [Author]?
|
var authors: [Author]?
|
||||||
|
@ -65,9 +89,15 @@ struct Metadata: Codable {
|
||||||
var narratorName: String?
|
var narratorName: String?
|
||||||
var seriesName: String?
|
var seriesName: String?
|
||||||
var feedUrl: String?
|
var feedUrl: String?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
title = "Unknown"
|
||||||
|
genres = []
|
||||||
|
explicit = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PodcastEpisode: Codable {
|
struct PodcastEpisode: Realmable, Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var index: Int
|
var index: Int
|
||||||
var episode: String?
|
var episode: String?
|
||||||
|
@ -78,30 +108,55 @@ struct PodcastEpisode: Codable {
|
||||||
var audioFile: AudioFile?
|
var audioFile: AudioFile?
|
||||||
var audioTrack: AudioTrack?
|
var audioTrack: AudioTrack?
|
||||||
var duration: Double
|
var duration: Double
|
||||||
var size: Int64
|
var size: Int
|
||||||
// var serverEpisodeId: String?
|
// var serverEpisodeId: String?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
id = ""
|
||||||
|
index = 0
|
||||||
|
title = "Unknown"
|
||||||
|
duration = 0
|
||||||
|
size = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AudioFile: Codable {
|
struct AudioFile: Realmable, Codable {
|
||||||
var index: Int
|
var index: Int
|
||||||
var ino: String
|
var ino: String
|
||||||
var metadata: FileMetadata
|
var metadata: FileMetadata
|
||||||
|
|
||||||
|
init() {
|
||||||
|
index = 0
|
||||||
|
ino = ""
|
||||||
|
metadata = FileMetadata()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Author: Codable {
|
struct Author: Realmable, Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var name: String
|
var name: String
|
||||||
var coverPath: String?
|
var coverPath: String?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
id = ""
|
||||||
|
name = "Unknown"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Chapter: Codable {
|
struct Chapter: Realmable, Codable {
|
||||||
var id: Int
|
var id: Int
|
||||||
var start: Double
|
var start: Double
|
||||||
var end: Double
|
var end: Double
|
||||||
var title: String?
|
var title: String?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
id = 0
|
||||||
|
start = 0
|
||||||
|
end = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AudioTrack: Codable {
|
struct AudioTrack: Realmable, Codable {
|
||||||
var index: Int?
|
var index: Int?
|
||||||
var startOffset: Double?
|
var startOffset: Double?
|
||||||
var duration: Double
|
var duration: Double
|
||||||
|
@ -113,50 +168,101 @@ struct AudioTrack: Codable {
|
||||||
// var localFileId: String?
|
// var localFileId: String?
|
||||||
// var audioProbeResult: AudioProbeResult? Needed for local playback
|
// var audioProbeResult: AudioProbeResult? Needed for local playback
|
||||||
var serverIndex: Int?
|
var serverIndex: Int?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
duration = 0
|
||||||
|
mimeType = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FileMetadata: Codable {
|
struct FileMetadata: Realmable, Codable {
|
||||||
var filename: String
|
var filename: String
|
||||||
var ext: String
|
var ext: String
|
||||||
var path: String
|
var path: String
|
||||||
var relPath: String
|
var relPath: String
|
||||||
|
|
||||||
|
init() {
|
||||||
|
filename = ""
|
||||||
|
ext = ""
|
||||||
|
path = ""
|
||||||
|
relPath = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Library: Codable {
|
struct Library: Realmable, Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var name: String
|
var name: String
|
||||||
var folders: [Folder]
|
var folders: [Folder]
|
||||||
var icon: String
|
var icon: String
|
||||||
var mediaType: String
|
var mediaType: String
|
||||||
|
|
||||||
|
init() {
|
||||||
|
id = ""
|
||||||
|
name = "Unknown"
|
||||||
|
folders = []
|
||||||
|
icon = ""
|
||||||
|
mediaType = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Folder: Codable {
|
struct Folder: Realmable, Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var fullPath: String
|
var fullPath: String
|
||||||
|
|
||||||
|
init() {
|
||||||
|
id = ""
|
||||||
|
fullPath = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LibraryFile: Codable {
|
struct LibraryFile: Realmable, Codable {
|
||||||
var ino: String
|
var ino: String
|
||||||
var metadata: FileMetadata
|
var metadata: FileMetadata
|
||||||
|
|
||||||
|
init() {
|
||||||
|
ino = ""
|
||||||
|
metadata = FileMetadata()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MediaProgress:Codable {
|
struct MediaProgress: Realmable, Codable {
|
||||||
var id:String
|
var id: String
|
||||||
var libraryItemId:String
|
var libraryItemId: String
|
||||||
var episodeId:String?
|
var episodeId: String?
|
||||||
var duration:Double
|
var duration: Double
|
||||||
var progress:Double
|
var progress: Double
|
||||||
var currentTime:Double
|
var currentTime: Double
|
||||||
var isFinished:Bool
|
var isFinished: Bool
|
||||||
var lastUpdate:Int64
|
var lastUpdate: Int
|
||||||
var startedAt:Int64
|
var startedAt: Int
|
||||||
var finishedAt:Int64?
|
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 duration: Double
|
||||||
var currentTime: Double
|
var currentTime: Double
|
||||||
var playerState: PlayerState
|
var playerState: PlayerState
|
||||||
|
|
||||||
|
init() {
|
||||||
|
duration = 0
|
||||||
|
currentTime = 0
|
||||||
|
playerState = PlayerState.IDLE
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ignoredProperties() -> [String] {
|
||||||
|
return ["playerState"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlayerState: Codable {
|
enum PlayerState: Codable {
|
||||||
|
|
|
@ -7,21 +7,17 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
|
import Unrealm
|
||||||
|
|
||||||
class DeviceSettings: Object {
|
struct DeviceSettings: Realmable {
|
||||||
@Persisted var disableAutoRewind: Bool
|
var disableAutoRewind: Bool = false
|
||||||
@Persisted var enableAltView: Bool
|
var enableAltView: Bool = false
|
||||||
@Persisted var jumpBackwardsTime: Int
|
var jumpBackwardsTime: Int = 10
|
||||||
@Persisted var jumpForwardTime: Int
|
var jumpForwardTime: Int = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultDeviceSettings() -> DeviceSettings {
|
func getDefaultDeviceSettings() -> DeviceSettings {
|
||||||
let settings = DeviceSettings()
|
return DeviceSettings()
|
||||||
settings.disableAutoRewind = false
|
|
||||||
settings.enableAltView = false
|
|
||||||
settings.jumpForwardTime = 10
|
|
||||||
settings.jumpBackwardsTime = 10
|
|
||||||
return settings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func deviceSettingsToJSON(settings: DeviceSettings) -> Dictionary<String, Any> {
|
func deviceSettingsToJSON(settings: DeviceSettings) -> Dictionary<String, Any> {
|
||||||
|
|
|
@ -6,139 +6,81 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RealmSwift
|
import Unrealm
|
||||||
|
|
||||||
class LocalLibraryItem: Object, Encodable {
|
struct LocalLibraryItem: Realmable, Codable {
|
||||||
@Persisted(primaryKey: true) var id: String = "local_\(UUID().uuidString)"
|
var id: String = "local_\(UUID().uuidString)"
|
||||||
@Persisted var basePath: String = ""
|
var basePath: String = ""
|
||||||
@Persisted var absolutePath: String = ""
|
var absolutePath: String = ""
|
||||||
@Persisted var contentUrl: String
|
var contentUrl: String = ""
|
||||||
@Persisted var isInvalid: Bool = false
|
var isInvalid: Bool = false
|
||||||
@Persisted var mediaType: String
|
var mediaType: String = ""
|
||||||
@Persisted var media: LocalMediaType?
|
var media: MediaType?
|
||||||
@Persisted var localFiles: List<LocalFile>
|
var localFiles: [LocalFile] = []
|
||||||
@Persisted var coverContentUrl: String? = nil
|
var coverContentUrl: String?
|
||||||
@Persisted var coverAbsolutePath: String? = nil
|
var coverAbsolutePath: String?
|
||||||
@Persisted var isLocal: Bool = true
|
var isLocal: Bool = true
|
||||||
@Persisted var serverConnectionConfigId: String? = nil
|
var serverConnectionConfigId: String?
|
||||||
@Persisted var serverAddress: String? = nil
|
var serverAddress: String?
|
||||||
@Persisted var serverUserId: String? = nil
|
var serverUserId: String?
|
||||||
@Persisted var libraryItemId: String? = nil
|
var libraryItemId: String?
|
||||||
|
|
||||||
|
static func primaryKey() -> String? {
|
||||||
|
return "id"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalMediaType: Object, Encodable {
|
struct LocalPodcastEpisode: Realmable, Codable {
|
||||||
@Persisted var libraryItemId: String? = ""
|
var id: String = UUID().uuidString
|
||||||
@Persisted var metadata: LocalMetadata?
|
var index: Int = 0
|
||||||
@Persisted var coverPath: String? = ""
|
var episode: String?
|
||||||
@Persisted var tags: List<String?>
|
var episodeType: String?
|
||||||
@Persisted var audioFiles: List<LocalAudioFile>
|
var title: String = "Unknown"
|
||||||
@Persisted var chapters: List<LocalChapter>
|
var subtitle: String?
|
||||||
@Persisted var tracks: List<LocalAudioTrack>
|
var desc: String?
|
||||||
@Persisted var size: Int64? = nil
|
var audioFile: AudioFile?
|
||||||
@Persisted var duration: Double? = nil
|
var audioTrack: AudioTrack?
|
||||||
@Persisted var episodes: List<LocalPodcastEpisode>
|
var duration: Double = 0
|
||||||
@Persisted var autoDownloadEpisodes: Bool? = nil
|
var size: Int = 0
|
||||||
|
var serverEpisodeId: String?
|
||||||
|
|
||||||
|
static func primaryKey() -> String? {
|
||||||
|
return "id"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalMetadata: Object, Encodable {
|
struct LocalFile: Realmable, Codable {
|
||||||
@Persisted var title: String
|
var id: String = UUID().uuidString
|
||||||
@Persisted var subtitle: String? = ""
|
var filename: String?
|
||||||
@Persisted var authors: List<LocalAuthor>
|
var contentUrl: String = ""
|
||||||
@Persisted var narrators: List<String?>
|
var absolutePath: String = ""
|
||||||
@Persisted var genres: List<String?>
|
var mimeType: String?
|
||||||
@Persisted var publishedYear: String? = ""
|
var size: Int = 0
|
||||||
@Persisted var publishedDate: String? = ""
|
|
||||||
@Persisted var publisher: String? = ""
|
static func primaryKey() -> String? {
|
||||||
@Persisted var desc: String? = ""
|
return "id"
|
||||||
@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? = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalPodcastEpisode: Object, Encodable {
|
struct LocalMediaProgress: Realmable, Codable {
|
||||||
@Persisted var id: String = UUID().uuidString
|
var id: String = UUID().uuidString
|
||||||
@Persisted var index: Int
|
var localLibraryItemId: String = ""
|
||||||
@Persisted var episode: String? = ""
|
var localEpisodeId: String?
|
||||||
@Persisted var episodeType: String? = ""
|
var duration: Double = 0
|
||||||
@Persisted var title: String
|
var progress: Double = 0
|
||||||
@Persisted var subtitle: String? = ""
|
var currentTime: Double = 0
|
||||||
@Persisted var desc: String? = ""
|
var isFinished: Bool = false
|
||||||
@Persisted var audioFile: LocalAudioFile? = nil
|
var lastUpdate: Int = 0
|
||||||
@Persisted var audioTrack: LocalAudioTrack? = nil
|
var startedAt: Int = 0
|
||||||
@Persisted var duration: Double
|
var finishedAt: Int?
|
||||||
@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
|
|
||||||
// For local lib items from server to support server sync
|
// For local lib items from server to support server sync
|
||||||
@Persisted var serverConnectionConfigId: String? = ""
|
var serverConnectionConfigId: String?
|
||||||
@Persisted var serverAddress: String? = ""
|
var serverAddress: String?
|
||||||
@Persisted var serverUserId: String? = ""
|
var serverUserId: String?
|
||||||
@Persisted var libraryItemId: String? = ""
|
var libraryItemId: String?
|
||||||
@Persisted var episodeId: String? = ""
|
var episodeId: String?
|
||||||
|
|
||||||
|
static func primaryKey() -> String? {
|
||||||
|
return "id"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,49 +8,12 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension LocalLibraryItem {
|
extension LocalLibraryItem {
|
||||||
enum CodingKeys: CodingKey {
|
init(_ item: LibraryItem, localUrl: URL, server: ServerConnectionConfig, files: [LocalFile]) {
|
||||||
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]) {
|
|
||||||
self.init()
|
self.init()
|
||||||
self.contentUrl = localUrl.absoluteString
|
self.contentUrl = localUrl.absoluteString
|
||||||
self.mediaType = item.mediaType
|
self.mediaType = item.mediaType
|
||||||
self.media = LocalMediaType(item.media, coverPath: "", files: files)
|
self.media = item.media
|
||||||
self.localFiles.append(objectsIn: files)
|
self.localFiles = files
|
||||||
// TODO: self.coverContentURL
|
// TODO: self.coverContentURL
|
||||||
// TODO: self.converAbsolutePath
|
// TODO: self.converAbsolutePath
|
||||||
self.libraryItemId = item.id
|
self.libraryItemId = item.id
|
||||||
|
@ -61,7 +24,7 @@ extension LocalLibraryItem {
|
||||||
|
|
||||||
func getDuration() -> Double {
|
func getDuration() -> Double {
|
||||||
var total = 0.0
|
var total = 0.0
|
||||||
self.media?.tracks.forEach { track in total += track.duration }
|
self.media?.tracks?.forEach { track in total += track.duration }
|
||||||
return total
|
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 {
|
extension LocalFile {
|
||||||
enum CodingKeys: CodingKey {
|
init(_ libraryItemId: String, _ filename: String, _ mimeType: String, _ localUrl: URL) {
|
||||||
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) {
|
|
||||||
self.init()
|
self.init()
|
||||||
self.id = "\(libraryItemId)_\(filename.toBase64())"
|
self.id = "\(libraryItemId)_\(filename.toBase64())"
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.contentUrl = localUrl.absoluteString
|
self.contentUrl = localUrl.absoluteString
|
||||||
self.absolutePath = localUrl.path
|
self.absolutePath = localUrl.path
|
||||||
self.size = localUrl.fileSize
|
self.size = Int(localUrl.fileSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAudioFile() -> Bool {
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,20 +7,29 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
|
import Unrealm
|
||||||
|
|
||||||
class ServerConnectionConfig: Object {
|
struct ServerConnectionConfig: Realmable {
|
||||||
@Persisted(primaryKey: true) var id: String
|
var id: String = UUID().uuidString
|
||||||
@Persisted(indexed: true) var index: Int
|
var index: Int = 0
|
||||||
@Persisted var name: String
|
var name: String = ""
|
||||||
@Persisted var address: String
|
var address: String = ""
|
||||||
@Persisted var userId: String
|
var userId: String = ""
|
||||||
@Persisted var username: String
|
var username: String = ""
|
||||||
@Persisted var token: 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> {
|
func convertServerConnectionConfigToJSON(config: ServerConnectionConfig) -> Dictionary<String, Any> {
|
||||||
|
|
|
@ -114,9 +114,11 @@ class ApiClient {
|
||||||
callback(session)
|
callback(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func reportPlaybackProgress(report: PlaybackReport, sessionId: String) {
|
public static func reportPlaybackProgress(report: PlaybackReport, sessionId: String) {
|
||||||
try? postResource(endpoint: "api/session/\(sessionId)/sync", parameters: report.asDictionary().mapValues({ value in "\(value)" }), callback: nil)
|
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) {
|
public static func getLibraryItemWithProgress(libraryItemId:String, episodeId:String?, callback: @escaping (_ param: LibraryItem?) -> Void) {
|
||||||
var endpoint = "api/items/\(libraryItemId)?expanded=1&include=progress"
|
var endpoint = "api/items/\(libraryItemId)?expanded=1&include=progress"
|
||||||
if episodeId != nil {
|
if episodeId != nil {
|
||||||
|
|
|
@ -23,16 +23,12 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setServerConnectionConfig(config: ServerConnectionConfig) {
|
public func setServerConnectionConfig(config: ServerConnectionConfig) {
|
||||||
var refrence: ThreadSafeReference<ServerConnectionConfig>?
|
var config = config
|
||||||
if config.realm != nil {
|
|
||||||
refrence = ThreadSafeReference(to: config)
|
|
||||||
}
|
|
||||||
|
|
||||||
Database.realmQueue.sync {
|
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 {
|
if config.index == 0 {
|
||||||
let lastConfig: ServerConnectionConfig? = instance.objects(ServerConnectionConfig.self).last
|
var lastConfig: ServerConnectionConfig? = instance.objects(ServerConnectionConfig.self).last
|
||||||
|
|
||||||
if lastConfig != nil {
|
if lastConfig != nil {
|
||||||
config.index = lastConfig!.index + 1
|
config.index = lastConfig!.index + 1
|
||||||
|
@ -46,15 +42,7 @@ class Database {
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
instance.delete(existing!)
|
instance.delete(existing!)
|
||||||
}
|
}
|
||||||
if refrence == nil {
|
instance.add(config)
|
||||||
instance.add(config)
|
|
||||||
} else {
|
|
||||||
guard let resolved = instance.resolve(refrence!) else {
|
|
||||||
throw "unable to resolve refrence"
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.add(resolved);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch(let exception) {
|
} catch(let exception) {
|
||||||
NSLog("failed to save server config")
|
NSLog("failed to save server config")
|
||||||
|
@ -83,25 +71,8 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getServerConnectionConfigs() -> [ServerConnectionConfig] {
|
public func getServerConnectionConfigs() -> [ServerConnectionConfig] {
|
||||||
var refrences: [ThreadSafeReference<ServerConnectionConfig>] = []
|
|
||||||
|
|
||||||
Database.realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
let configs = instance.objects(ServerConnectionConfig.self)
|
return Array(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 []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +84,7 @@ class Database {
|
||||||
|
|
||||||
public func setLastActiveConfigIndex(index: Int?) {
|
public func setLastActiveConfigIndex(index: Int?) {
|
||||||
let existing = instance.objects(ServerConnectionConfigActiveIndex.self)
|
let existing = instance.objects(ServerConnectionConfigActiveIndex.self)
|
||||||
let obj = ServerConnectionConfigActiveIndex()
|
var obj = ServerConnectionConfigActiveIndex()
|
||||||
obj.index = index
|
obj.index = index
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
@ -149,25 +120,8 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getLocalLibraryItems(mediaType: MediaType? = nil) -> [LocalLibraryItem] {
|
public func getLocalLibraryItems(mediaType: MediaType? = nil) -> [LocalLibraryItem] {
|
||||||
var localLibraryItems: [ThreadSafeReference<LocalLibraryItem>] = []
|
|
||||||
|
|
||||||
Database.realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
let items = instance.objects(LocalLibraryItem.self)
|
Array(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 []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,26 +137,14 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getLocalLibraryItem(localLibraryItem: String) -> LocalLibraryItem? {
|
public func getLocalLibraryItem(localLibraryItem: String) -> LocalLibraryItem? {
|
||||||
let items = getLocalLibraryItems()
|
Database.realmQueue.sync {
|
||||||
for item in items {
|
instance.object(ofType: LocalLibraryItem.self, forPrimaryKey: localLibraryItem)
|
||||||
if (item.id == localLibraryItem) {
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
NSLog("Local library item with id \(localLibraryItem) not found")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) {
|
public func saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) {
|
||||||
Database.realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
do {
|
try! instance.write { instance.add(localLibraryItem) }
|
||||||
try instance.write {
|
|
||||||
instance.add(localLibraryItem);
|
|
||||||
}
|
|
||||||
} catch(let exception) {
|
|
||||||
NSLog("Unable to save local library item")
|
|
||||||
debugPrint(exception)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,19 +155,10 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeLocalLibraryItem(localLibraryItemId: String) {
|
public func removeLocalLibraryItem(localLibraryItemId: String) {
|
||||||
let item = getLocalLibraryItemByLLId(libraryItem: localLibraryItemId)
|
|
||||||
Database.realmQueue.sync {
|
Database.realmQueue.sync {
|
||||||
do {
|
try! instance.write {
|
||||||
try instance.write {
|
let item = getLocalLibraryItemByLLId(libraryItem: localLibraryItemId)
|
||||||
if item != nil {
|
instance.delete(item!)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Foundation
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
|
|
||||||
class Store {
|
class Store {
|
||||||
@ThreadSafe private static var _serverConfig: ServerConnectionConfig?
|
private static var _serverConfig: ServerConnectionConfig?
|
||||||
public static var serverConfig: ServerConnectionConfig? {
|
public static var serverConfig: ServerConnectionConfig? {
|
||||||
get {
|
get {
|
||||||
return _serverConfig
|
return _serverConfig
|
||||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -32982,4 +32982,4 @@
|
||||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
|
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue