Skip to content

Commit

Permalink
Implements transfer file selector and closes #3
Browse files Browse the repository at this point in the history
  • Loading branch information
TheNightmanCodeth committed Mar 25, 2022
1 parent 80f2dc7 commit f664e31
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 16 deletions.
7 changes: 5 additions & 2 deletions Mission/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ struct ContentView: View {
@ObservedObject var store: Store = Store()
private var keychain = Keychain(service: "me.jdiggity.mission")

@State private var isShowingAddAlert = false

@State private var alertInput = ""
@State private var filename = ""
@State private var downloadDir = ""
Expand Down Expand Up @@ -119,6 +117,11 @@ struct ContentView: View {
.sheet(isPresented: $store.isShowingAddAlert, onDismiss: {}, content: {
AddTorrentDialog(store: store)
})
// Add transfer file picker
.sheet(isPresented: $store.isShowingTransferFiles, onDismiss: {}, content: {
FileSelectDialog(store: store)
.frame(width: 400, height: 500)
})
}

func binding(for torrent: Torrent) -> Binding<Torrent> {
Expand Down
87 changes: 75 additions & 12 deletions Mission/Remote/Transmission.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ public func getTorrents(config: TransmissionConfig, auth: TransmissionAuth, onRe
task.resume()
}

struct TorrentAdded: Codable {
var hashString: String
var id: Int
var name: String
}

struct TorrentAddResponse: Codable {
var arguments: [String: TorrentAdded]
}

/// Makes a request to the server containing either a base64 representation of a .torrent file or a magnet link
///
/// ```
Expand All @@ -150,7 +160,7 @@ public func getTorrents(config: TransmissionConfig, auth: TransmissionAuth, onRe
/// - Parameter file: A boolean value; true if `fileUrl` is a base64 encoded file and false if `fileUrl` is a magnet link
/// - Parameter config: A `TransmissionConfig` containing the server's address and port
/// - Parameter onAdd: An escaping function that receives the servers response code represented as a `TransmissionResponse`
public func addTorrent(fileUrl: String, saveLocation: String, auth: TransmissionAuth, file: Bool, config: TransmissionConfig, onAdd: @escaping (TransmissionResponse) -> Void) -> Void {
public func addTorrent(fileUrl: String, saveLocation: String, auth: TransmissionAuth, file: Bool, config: TransmissionConfig, onAdd: @escaping ((response: TransmissionResponse, transferId: Int)) -> Void) -> Void {
url = config
url?.scheme = "http"
url?.path = "/transmission/rpc"
Expand All @@ -177,7 +187,7 @@ public func addTorrent(fileUrl: String, saveLocation: String, auth: Transmission
// Send request to server
let task = URLSession.shared.dataTask(with: req) { (data, resp, error) in
if error != nil {
return onAdd(TransmissionResponse.configError)
return onAdd((TransmissionResponse.configError, 0))
}

let httpResp = resp as? HTTPURLResponse
Expand All @@ -188,11 +198,14 @@ public func addTorrent(fileUrl: String, saveLocation: String, auth: Transmission
addTorrent(fileUrl: fileUrl, saveLocation: saveLocation, auth: auth, file: file, config: config, onAdd: onAdd)
return
case 401?:
return onAdd(TransmissionResponse.forbidden)
return onAdd((TransmissionResponse.forbidden, 0))
case 200?:
return onAdd(TransmissionResponse.success)
let response = try? JSONDecoder().decode(TorrentAddResponse.self, from: data!)
let transferId: Int = response!.arguments["torrent-added"]!.id

return onAdd((TransmissionResponse.success, transferId))
default:
return onAdd(TransmissionResponse.failed)
return onAdd((TransmissionResponse.failed, 0))
}
}
task.resume()
Expand All @@ -208,17 +221,26 @@ struct TorrentFilesRequest: Codable {
var arguments: TorrentFilesArgs
}

struct TorrentFilesResponseFiles: Codable {
let files: [File]
}

struct TorrentFilesResponseTorrents: Codable {
let torrents: [TorrentFilesResponseFiles]
}

struct TorrentFilesResponse: Codable {
let arguments: [String: [File]]
let arguments: TorrentFilesResponseTorrents
}

public struct File: Codable {
public struct File: Codable, Identifiable {
var bytesCompleted: Int
var wanted: Int
var length: Int
var name: String
public let id = UUID()
}

public func getTransferFiles(torrent: Torrent, info: (config: TransmissionConfig, auth: TransmissionAuth), onReceived: @escaping ([File])->(Void)) {
public func getTransferFiles(transferId: Int, info: (config: TransmissionConfig, auth: TransmissionAuth), onReceived: @escaping ([File])->(Void)) {
url = info.config
url?.scheme = "http"
url?.path = "/transmission/rpc"
Expand All @@ -228,7 +250,7 @@ public func getTransferFiles(torrent: Torrent, info: (config: TransmissionConfig
method: "torrent-get",
arguments: TorrentFilesArgs(
fields: ["files"],
ids: [torrent.id]
ids: [transferId]
)
)

Expand All @@ -243,11 +265,12 @@ public func getTransferFiles(torrent: Torrent, info: (config: TransmissionConfig
switch httpResp?.statusCode {
case 409?: // If we get a 409, save the session token and try again
authorize(httpResp: httpResp)
getTransferFiles(torrent: torrent, info: info, onReceived: onReceived)
getTransferFiles(transferId: transferId, info: info, onReceived: onReceived)
return
case 200?:
print(String(decoding: data!, as: UTF8.self))
let response = try? JSONDecoder().decode(TorrentFilesResponse.self, from: data!)
let torrents = response?.arguments["files"]
let torrents = response?.arguments.torrents[0].files

return onReceived(torrents!)
default:
Expand Down Expand Up @@ -516,6 +539,46 @@ public func setPriority(torrent: Torrent, priority: TorrentPriority, info: (conf
task.resume()
}

/// Tells transmission to olny download the selected files
public func setTransferFiles(transferId: Int, files: [Int], info: (config: TransmissionConfig, auth: TransmissionAuth), onComplete: @escaping (TransmissionResponse) -> Void) {
url = info.config
url?.scheme = "http"
url?.path = "/transmission/rpc"
url?.port = info.config.port ?? 443

let requestBody = TorrentActionRequest(
method: "torrent-set",
arguments: [
"ids": [transferId],
"files-unwanted": files
]
)

let req = makeRequest(requestBody: requestBody, auth: info.auth)

let task = URLSession.shared.dataTask(with: req) { (data, resp, err) in
if err != nil {
onComplete(TransmissionResponse.configError)
}

let httpResp = resp as? HTTPURLResponse
// Call `onAdd` with the status code
switch httpResp?.statusCode {
case 409?: // If we get a 409, save the token and try again
authorize(httpResp: httpResp)
setTransferFiles(transferId: transferId, files: files, info: info, onComplete: onComplete)
return
case 401?:
return onComplete(TransmissionResponse.forbidden)
case 200?:
return onComplete(TransmissionResponse.success)
default:
return onComplete(TransmissionResponse.failed)
}
}
task.resume()
}

/// Gets the session-token from the response and sets it as the `lastSessionToken`
public func authorize(httpResp: HTTPURLResponse?) {
let mixedHeaders = httpResp?.allHeaderFields as! [String: Any]
Expand Down
4 changes: 4 additions & 0 deletions Mission/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class Store: NSObject, ObservableObject {

@Published var isShowingAddAlert: Bool = false
@Published var isShowingServerAlert: Bool = false
@Published var isShowingTransferFiles: Bool = false
@Published var transferToSetFiles: Int = 0

@Published var addTransferFilesList: [File] = []

var timer: Timer = Timer()

Expand Down
14 changes: 12 additions & 2 deletions Mission/Views/AddTorrentDialog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ struct AddTorrentDialog: View {
let info = makeConfig(store: store)

addTorrent(fileUrl: fileStream, saveLocation: downloadDir, auth: info.auth, file: true, config: info.config, onAdd: { response in
if response == TransmissionResponse.success {
if response.response == TransmissionResponse.success {
store.isShowingAddAlert.toggle()
showFilePicker(transferId: response.transferId, info: info)
}
})
}
Expand All @@ -100,8 +101,9 @@ struct AddTorrentDialog: View {
// Send the magnet link to the server
let info = makeConfig(store: store)
addTorrent(fileUrl: alertInput, saveLocation: downloadDir, auth: info.auth, file: false, config: info.config, onAdd: { response in
if response == TransmissionResponse.success {
if response.response == TransmissionResponse.success {
store.isShowingAddAlert.toggle()
showFilePicker(transferId: response.transferId, info: info)
}
})
}.padding()
Expand All @@ -112,6 +114,14 @@ struct AddTorrentDialog: View {
downloadDir = store.defaultDownloadDir
}
}

func showFilePicker(transferId: Int, info: (config: TransmissionConfig, auth: TransmissionAuth)) {
getTransferFiles(transferId: transferId, info: info, onReceived: { f in
store.addTransferFilesList = f
store.transferToSetFiles = transferId
store.isShowingTransferFiles.toggle()
})
}
}

// This is needed to silence buildtime warnings related to the filepicker.
Expand Down
79 changes: 79 additions & 0 deletions Mission/Views/FileSelectDialog.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// FileSelectDialog.swift
// Mission
//
// Created by Joe Diragi on 3/16/22.
//

import Foundation
import SwiftUI

struct MultipleSelectionRow: View {
var title: String
@State var isSelected: Bool
var action: () -> Void

var body: some View {
HStack {
Toggle(self.title, isOn: self.$isSelected)
.onChange(of: isSelected) { i in
action()
}
}
}
}

struct FileSelectDialog: View {
@ObservedObject var store: Store

@State var files: [File] = []
@State var selections: [Int] = []

init(store: Store) {
self.store = store
}

var body: some View {
VStack {
HStack {
Text("Select Files")
.font(.headline)
.padding(.leading, 20)
.padding(.bottom, 10)
.padding(.top, 20)
Button(action: {
store.isShowingTransferFiles.toggle()
}, label: {
Image(systemName: "xmark.circle.fill")
.padding(.top, 20)
.padding(.bottom, 10)
.padding(.leading, 20)
.frame(alignment: .trailing)
}).buttonStyle(BorderlessButtonStyle())
}
List {
ForEach(Array(store.addTransferFilesList.enumerated()), id: \.offset) { (i,f) in
MultipleSelectionRow(title: f.name, isSelected: self.selections.contains(i)) {
if self.selections.contains(i) {
self.selections.removeAll(where: { $0 == i })
} else {
self.selections.append(i)
}
}
}
}
Button("Submit") {
var dontDownload: [Int] = []
store.addTransferFilesList.enumerated().forEach { (i,f) in
if (!self.selections.contains(i)) {
dontDownload.append(i)
}
}
print("Don't download: \(dontDownload)")
setTransferFiles(transferId: store.transferToSetFiles, files: dontDownload, info: (config: store.server!.config, auth: store.server!.auth)) { i in
store.isShowingTransferFiles.toggle()
}
}.padding()
}
}
}

0 comments on commit f664e31

Please sign in to comment.