Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 64 additions & 40 deletions qBitControl.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion qBitControl/Classes/MockTorrentClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,17 @@ class MockTorrentClient: TorrentClientProtocol {
}

func getMainData(rid: Int = 0) async throws -> MainData {
return MainData(rid: rid, full_update: true, server_state: nil)
return MainData(
rid: rid,
full_update: true,
server_state: nil,
torrents: nil,
torrents_removed: nil,
categories: nil,
categories_removed: nil,
tags: nil,
tags_removed: nil
)
}

func getPreferences() async throws -> qBitPreferences {
Expand Down
35 changes: 35 additions & 0 deletions qBitControl/Classes/TorrentCacheManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Foundation
import Combine

@MainActor
class TorrentCacheManager: ObservableObject {
@Published var torrents: [String: Torrent] = [:]

func merge(mainData: MainData) {
// 1. Reset Cache on Full Update
if mainData.full_update == true {
self.torrents.removeAll()
}

// 2. Apply Updates & Adds
if let torrentsUpdate = mainData.torrents {
for (hash, partial) in torrentsUpdate {
if let existing = self.torrents[hash] {
var updated = existing
updated.update(from: partial)
self.torrents[hash] = updated
} else {
let newTorrent = Torrent(from: partial, hash: hash)
self.torrents[hash] = newTorrent
}
}
}

// 3. Prune Deleted Torrents
if let removedHashes = mainData.torrents_removed {
for hash in removedHashes {
self.torrents.removeValue(forKey: hash)
}
}
}
}
3 changes: 3 additions & 0 deletions qBitControl/Classes/qBitDataClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class qBitData: ObservableObject {
@Published var serverState: ServerState?
@Published var dlTransferData: [TransferInfo] = []
@Published var upTransferData: [TransferInfo] = []
let cacheManager = TorrentCacheManager()

private var pollingTask: Task<Void, Never>?
private var fetchInterval: UInt64 = 2_000_000_000 // 2 seconds
Expand Down Expand Up @@ -55,6 +56,8 @@ class qBitData: ObservableObject {
let mainData = try await client.getMainData(rid: rid)
self.rid = mainData.rid

self.cacheManager.merge(mainData: mainData)

if let partialServerState = mainData.server_state {
if let existingServerState = self.serverState {
// Update existing ServerState with new data
Expand Down
6 changes: 6 additions & 0 deletions qBitControl/Models/MainData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ struct MainData: Decodable {
let rid: Int
let full_update: Bool?
let server_state: PartialServerState?
let torrents: [String: PartialTorrent]?
let torrents_removed: [String]?
let categories: [String: Category]?
let categories_removed: [String]?
let tags: [String]?
let tags_removed: [String]?
}
48 changes: 48 additions & 0 deletions qBitControl/Models/PartialTorrent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation

struct PartialTorrent: Decodable, Equatable {
let added_on: Int?
let amount_left: Int?
let auto_tmm: Bool?
let availability: Float?
let category: String?
let completed: Int?
let completion_on: Int?
let content_path: String?
let dl_limit: Int64?
let dlspeed: Int64?
let downloaded: Int64?
let downloaded_session: Int64?
let eta: Int?
let f_l_piece_prio: Bool?
let force_start: Bool?
let last_activity: Int?
let magnet_uri: String?
let max_ratio: Float?
let max_seeding_time: Int?
let name: String?
let num_complete: Int?
let num_incomplete: Int?
let num_leechs: Int?
let num_seeds: Int?
let priority: Int?
let progress: Float?
let ratio: Float?
let ratio_limit: Float?
let save_path: String?
let seeding_time: Int?
let seeding_time_limit: Int?
let seen_complete: Int?
let seq_dl: Bool?
let size: Int64?
let state: String?
let super_seeding: Bool?
let tags: String?
let time_active: Int?
let total_size: Int64?
let tracker: String?
let up_limit: Int64?
let uploaded: Int64?
let uploaded_session: Int64?
let upspeed: Int64?
}
190 changes: 146 additions & 44 deletions qBitControl/Models/Torrent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,152 @@

import Foundation

struct Torrent: Decodable, Hashable {
let added_on: Int
let amount_left: Int
let auto_tmm: Bool
let availability: Float
let category: String
let completed: Int
let completion_on: Int
let content_path: String?
let dl_limit: Int64
let dlspeed: Int64
let downloaded: Int64
let downloaded_session: Int64
let eta: Int
let f_l_piece_prio: Bool
let force_start: Bool
struct Torrent: Codable, Hashable {
var added_on: Int
var amount_left: Int
var auto_tmm: Bool
var availability: Float
var category: String
var completed: Int
var completion_on: Int
var content_path: String?
var dl_limit: Int64
var dlspeed: Int64
var downloaded: Int64
var downloaded_session: Int64
var eta: Int
var f_l_piece_prio: Bool
var force_start: Bool
let hash: String
let last_activity: Int
let magnet_uri: String
let max_ratio: Float
let max_seeding_time: Int
let name: String
let num_complete: Int
let num_incomplete: Int
let num_leechs: Int
let num_seeds: Int
let priority: Int
let progress: Float
let ratio: Float
let ratio_limit: Float
let save_path: String
let seeding_time: Int?
let seeding_time_limit: Int
let seen_complete: Int
let seq_dl: Bool
let size: Int64
var last_activity: Int
var magnet_uri: String
var max_ratio: Float
var max_seeding_time: Int
var name: String
var num_complete: Int
var num_incomplete: Int
var num_leechs: Int
var num_seeds: Int
var priority: Int
var progress: Float
var ratio: Float
var ratio_limit: Float
var save_path: String
var seeding_time: Int?
var seeding_time_limit: Int
var seen_complete: Int
var seq_dl: Bool
var size: Int64
var state: String
let super_seeding: Bool
let tags: String
let time_active: Int
let total_size: Int64
let tracker: String
let up_limit: Int64
let uploaded: Int64
let uploaded_session: Int64
let upspeed: Int64
var super_seeding: Bool
var tags: String
var time_active: Int
var total_size: Int64
var tracker: String
var up_limit: Int64
var uploaded: Int64
var uploaded_session: Int64
var upspeed: Int64

mutating func update(from partial: PartialTorrent) {
if let value = partial.added_on { self.added_on = value }
if let value = partial.amount_left { self.amount_left = value }
if let value = partial.auto_tmm { self.auto_tmm = value }
if let value = partial.availability { self.availability = value }
if let value = partial.category { self.category = value }
if let value = partial.completed { self.completed = value }
if let value = partial.completion_on { self.completion_on = value }
if let value = partial.content_path { self.content_path = value }
if let value = partial.dl_limit { self.dl_limit = value }
if let value = partial.dlspeed { self.dlspeed = value }
if let value = partial.downloaded { self.downloaded = value }
if let value = partial.downloaded_session { self.downloaded_session = value }
if let value = partial.eta { self.eta = value }
if let value = partial.f_l_piece_prio { self.f_l_piece_prio = value }
if let value = partial.force_start { self.force_start = value }
if let value = partial.last_activity { self.last_activity = value }
if let value = partial.magnet_uri { self.magnet_uri = value }
if let value = partial.max_ratio { self.max_ratio = value }
if let value = partial.max_seeding_time { self.max_seeding_time = value }
if let value = partial.name { self.name = value }
if let value = partial.num_complete { self.num_complete = value }
if let value = partial.num_incomplete { self.num_incomplete = value }
if let value = partial.num_leechs { self.num_leechs = value }
if let value = partial.num_seeds { self.num_seeds = value }
if let value = partial.priority { self.priority = value }
if let value = partial.progress { self.progress = value }
if let value = partial.ratio { self.ratio = value }
if let value = partial.ratio_limit { self.ratio_limit = value }
if let value = partial.save_path { self.save_path = value }
if let value = partial.seeding_time { self.seeding_time = value }
if let value = partial.seeding_time_limit { self.seeding_time_limit = value }
if let value = partial.seen_complete { self.seen_complete = value }
if let value = partial.seq_dl { self.seq_dl = value }
if let value = partial.size { self.size = value }
if let value = partial.state { self.state = value }
if let value = partial.super_seeding { self.super_seeding = value }
if let value = partial.tags { self.tags = value }
if let value = partial.time_active { self.time_active = value }
if let value = partial.total_size { self.total_size = value }
if let value = partial.tracker { self.tracker = value }
if let value = partial.up_limit { self.up_limit = value }
if let value = partial.uploaded { self.uploaded = value }
if let value = partial.uploaded_session { self.uploaded_session = value }
if let value = partial.upspeed { self.upspeed = value }
}
}

extension Torrent {
init(hash: String) {
self.hash = hash
self.added_on = 0
self.amount_left = 0
self.auto_tmm = false
self.availability = 0.0
self.category = ""
self.completed = 0
self.completion_on = 0
self.content_path = nil
self.dl_limit = 0
self.dlspeed = 0
self.downloaded = 0
self.downloaded_session = 0
self.eta = 0
self.f_l_piece_prio = false
self.force_start = false
self.last_activity = 0
self.magnet_uri = ""
self.max_ratio = 0.0
self.max_seeding_time = 0
self.name = ""
self.num_complete = 0
self.num_incomplete = 0
self.num_leechs = 0
self.num_seeds = 0
self.priority = 0
self.progress = 0.0
self.ratio = 0.0
self.ratio_limit = 0.0
self.save_path = ""
self.seeding_time = nil
self.seeding_time_limit = 0
self.seen_complete = 0
self.seq_dl = false
self.size = 0
self.state = ""
self.super_seeding = false
self.tags = ""
self.time_active = 0
self.total_size = 0
self.tracker = ""
self.up_limit = 0
self.uploaded = 0
self.uploaded_session = 0
self.upspeed = 0
}

init(from partial: PartialTorrent, hash: String) {
self.init(hash: hash)
self.update(from: partial)
}
}
17 changes: 17 additions & 0 deletions qBitControl/Models/TorrentFilterOption.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

enum TorrentFilterOption: String, CaseIterable, Codable {
case all
case resumed
case stalledUploading = "stalled_uploading"
case stalledDownloading = "stalled_downloading"
case downloading
case seeding
case completed
case paused
case active
case inactive
case stalled
case errored
case checking
}
38 changes: 38 additions & 0 deletions qBitControl/Models/TorrentSortOption.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Foundation

enum TorrentSortOption: String, CaseIterable, Codable {
case addedOn = "added_on"
case amountLeft = "amount_left"
case availability
case category
case completed
case completionOn = "completion_on"
case dlLimit = "dl_limit"
case dlspeed
case downloaded
case downloadedSession = "downloaded_session"
case eta
case lastActivity = "last_activity"
case maxRatio = "max_ratio"
case maxSeedingTime = "max_seeding_time"
case name
case numComplete = "num_complete"
case numIncomplete = "num_incomplete"
case numLeechs = "num_leechs"
case numSeeds = "num_seeds"
case priority
case progress
case ratio
case ratioLimit = "ratio_limit"
case seedingTime = "seeding_time"
case seedingTimeLimit = "seeding_time_limit"
case size
case state
case tags
case timeActive = "time_active"
case totalSize = "total_size"
case upLimit = "up_limit"
case uploaded
case uploadedSession = "uploaded_session"
case upspeed
}
Loading
Loading