Skip to content

Commit

Permalink
Merge pull request #30 from Infomaniak/feat-upload-logic
Browse files Browse the repository at this point in the history
feat: Upload logic
  • Loading branch information
valentinperignon authored Nov 1, 2024
2 parents 54d4080 + 857c360 commit c66cd7e
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 15 deletions.
11 changes: 7 additions & 4 deletions Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import ProjectDescriptionHelpers

let transferList = Feature(name: "TransferList")

// MARK: New Transfer

let newTransferView = Feature(name: "NewTransferView")

// MARK: New Upload

let uploadProgressView = Feature(name: "UploadProgressView")

// MARK: New Transfer

let newTransferView = Feature(name: "NewTransferView", additionalDependencies: [
uploadProgressView,
TargetDependency.external(name: "InfomaniakCore")
])

// MARK: Root

let transferDetailsView = Feature(name: "TransferDetailsView")
Expand Down
15 changes: 15 additions & 0 deletions SwissTransferCore/UploadFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import Foundation
import OSLog
import STCore

public class UploadFile: Identifiable {
public var id: String {
Expand Down Expand Up @@ -59,3 +60,17 @@ public class UploadFile: Identifiable {
}
}
}

extension UploadFile: UploadFileSession {
public var localPath: String {
url.absoluteString
}

public var name: String {
url.lastPathComponent
}

public var remoteUploadFile: (any RemoteUploadFile)? {
nil
}
}
1 change: 1 addition & 0 deletions SwissTransferCore/Utils/KMP+Sendable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ import STCore
// The following objects should be Sendable as discussed with the KMP team.
extension TransferUi: @retroactive @unchecked Sendable {}
extension FileUi: @retroactive @unchecked Sendable {}
extension NewUploadSession: @retroactive @unchecked Sendable {}
41 changes: 37 additions & 4 deletions SwissTransferFeatures/NewTransferView/NewTransferView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
*/

import InfomaniakCoreSwiftUI
import OSLog
import STCore
import STResources
import STUploadProgressView
import SwiftUI
import SwissTransferCore
import SwissTransferCoreUI
Expand All @@ -26,14 +29,17 @@ public struct NewTransferView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var newTransferManager: NewTransferManager

@State private var isLoadingFileToUpload = false
@State private var navigationPath = NavigationPath()

public init(urls: [URL]) {
let transferManager = NewTransferManager()
_ = transferManager.addFiles(urls: urls)
_newTransferManager = StateObject(wrappedValue: transferManager)
}

public var body: some View {
NavigationStack {
NavigationStack(path: $navigationPath) {
ScrollView {
VStack(spacing: IKPadding.medium) {
// FilesCell
Expand All @@ -53,15 +59,17 @@ public struct NewTransferView: View {
}
.padding(.vertical, value: .medium)
}
.navigationDestination(for: NewUploadSession.self) { newUploadSession in
UploadProgressView(uploadSession: newUploadSession)
}
.floatingContainer {
NavigationLink {
// Start transfer
} label: {
Button(action: startUpload) {
Text(STResourcesStrings.Localizable.buttonNext)
.frame(maxWidth: .infinity)
}
.buttonStyle(.ikBorderedProminent)
.ikButtonFullWidth(true)
.ikButtonLoading(isLoadingFileToUpload)
.controlSize(.large)
}
.scrollDismissesKeyboard(.immediately)
Expand All @@ -83,6 +91,31 @@ public struct NewTransferView: View {
}
.environmentObject(newTransferManager)
}

func startUpload() {
Task {
isLoadingFileToUpload = true

do {
let filesToUpload = try newTransferManager.filesToUpload()
let newUploadSession = NewUploadSession(
duration: "30",
authorEmail: "",
password: "",
message: "",
numberOfDownload: 250,
language: .english,
recipientsEmails: [],
files: filesToUpload
)
navigationPath.append(newUploadSession)
} catch {
Logger.general.error("Error getting files to upload \(error.localizedDescription)")
}

isLoadingFileToUpload = false
}
}
}

#Preview {
Expand Down
159 changes: 159 additions & 0 deletions SwissTransferFeatures/UploadProgressView/TransferSessionManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
Infomaniak SwissTransfer - iOS App
Copyright (C) 2024 Infomaniak Network SA
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import Combine
import Foundation
import InfomaniakCore
import InfomaniakDI
import OSLog
import STCore
import STNetwork
import SwissTransferCore

final class UploadTaskDelegate: NSObject, URLSessionTaskDelegate {
let taskProgress: Progress

init(totalBytesExpectedToSend: Int) {
taskProgress = Progress(totalUnitCount: Int64(totalBytesExpectedToSend))
}

func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64
) {
taskProgress.completedUnitCount = totalBytesSent
}
}

class TransferSessionManager: ObservableObject {
@LazyInjectService private var injection: SwissTransferInjection

@Published var percentCompleted: Double = 0

private let uploadURLSession = URLSession.shared

private var overallProgress: Progress?

private var cancellables: Set<AnyCancellable> = []

private static let rangeProviderConfig = RangeProvider.Config(
chunkMinSize: 50 * 1024 * 1024,
chunkMaxSizeClient: 50 * 1024 * 1024,
chunkMaxSizeServer: 50 * 1024 * 1024,
optimalChunkCount: 200,
maxTotalChunks: 10000,
minTotalChunks: 1
)

enum ErrorDomain: Error {
case remoteContainerNotFound
case invalidURL(rawURL: String)
case invalidUploadChunkURL
case invalidRangeCompute
}

func startUpload(session newUploadSession: NewUploadSession) async throws -> String {
do {
overallProgress = Progress(totalUnitCount: Int64(newUploadSession.files.count))
overallProgress?
.publisher(for: \.fractionCompleted)
.receive(on: RunLoop.main)
.sink { [weak self] fractionCompleted in
self?.percentCompleted = fractionCompleted
}
.store(in: &cancellables)

let uploadManager = injection.uploadManager

let uploadSession = try await uploadManager.createAnGetUpload(newUploadSession: newUploadSession)

let uploadWithRemoteContainer = try await uploadManager.doInitUploadSession(
uuid: uploadSession.uuid,
recaptcha: "aabb"
)

guard let uploadWithRemoteContainer,
let container = uploadWithRemoteContainer.remoteContainer else {
throw ErrorDomain.remoteContainerNotFound
}

let remoteUploadFiles = uploadWithRemoteContainer.files.compactMap { $0.remoteUploadFile }
assert(remoteUploadFiles.count == uploadWithRemoteContainer.files.count, "All files should have a remote upload file")

for (index, remoteUploadFile) in remoteUploadFiles.enumerated() {
let localFile = uploadWithRemoteContainer.files[index]

try await uploadFile(atPath: localFile.localPath, toRemoteFile: remoteUploadFile, uploadUUID: uploadSession.uuid)
}

Logger.general.info("Found container: \(container.uuid)")

let transferUUID = try await uploadManager.finishUploadSession(uuid: uploadSession.uuid)

return transferUUID
} catch {
Logger.general.error("Error trying to start upload: \(error)")
fatalError("Implement error handling")
}
}

private func uploadFile(atPath: String, toRemoteFile: any RemoteUploadFile, uploadUUID: String) async throws {
guard let fileURL = URL(string: atPath) else {
throw ErrorDomain.invalidURL(rawURL: atPath)
}

let rangeProvider = RangeProvider(fileURL: fileURL, config: TransferSessionManager.rangeProviderConfig)

let ranges = try rangeProvider.allRanges
guard let chunkProvider = ChunkProvider(fileURL: fileURL, ranges: ranges) else {
throw ErrorDomain.invalidRangeCompute
}

let rangeCount = ranges.count
let fileProgress = Progress(totalUnitCount: Int64(rangeCount))
overallProgress?.addChild(fileProgress, withPendingUnitCount: 1)

var index: Int32 = 0
while let chunk = chunkProvider.next() {
guard let rawChunkURL = try injection.sharedApiUrlCreator.uploadChunkUrl(
uploadUUID: uploadUUID,
fileUUID: toRemoteFile.uuid,
chunkIndex: index,
isLastChunk: index == rangeCount - 1
) else {
throw ErrorDomain.invalidUploadChunkURL
}

guard let chunkURL = URL(string: rawChunkURL) else {
throw ErrorDomain.invalidURL(rawURL: rawChunkURL)
}

var uploadRequest = URLRequest(url: chunkURL)
uploadRequest.httpMethod = "POST"

let taskDelegate = UploadTaskDelegate(totalBytesExpectedToSend: chunk.count)
fileProgress.addChild(taskDelegate.taskProgress, withPendingUnitCount: 1)
try await uploadURLSession.upload(for: uploadRequest, from: chunk, delegate: taskDelegate)

index += 1
}
}
}
49 changes: 46 additions & 3 deletions SwissTransferFeatures/UploadProgressView/UploadProgressView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,59 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import STCore
import STNetwork
import SwiftUI
import SwissTransferCore

public struct UploadProgressView: View {
public init() {}
@EnvironmentObject private var transferManager: TransferManager

@StateObject private var transferSessionManager = TransferSessionManager()

@State private var error: Error?

let uploadSession: NewUploadSession

public init(uploadSession: NewUploadSession) {
self.uploadSession = uploadSession
}

public var body: some View {
Text("UploadProgressView")
VStack {
ProgressView(value: transferSessionManager.percentCompleted)
}
.onAppear {
Task {
do {
let transferUUID = try await transferSessionManager.startUpload(session: uploadSession)

// FIXME: Remove next two lines waiting for virus check
try await Task.sleep(for: .seconds(2))
try await transferManager.addTransferByLinkUUID(linkUUID: transferUUID)

guard let transfer = transferManager.getTransferByUUID(transferUUID: transferUUID) else {
fatalError("Couldn't find transfer")
}

// TODO: Navigate to transfer
} catch {
self.error = error
}
}
}
}
}

#Preview {
UploadProgressView()
UploadProgressView(uploadSession: NewUploadSession(
duration: "30",
authorEmail: "",
password: "",
message: "Coucou",
numberOfDownload: 250,
language: .english,
recipientsEmails: [],
files: []
))
}
2 changes: 2 additions & 0 deletions SwissTransferResources/SwissTransfer.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,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>com.apple.developer.devicecheck.appattest-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.infomaniak.swisstransfer</string>
Expand Down
4 changes: 2 additions & 2 deletions Tuist/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Infomaniak/multiplatform-SwissTransfer",
"state" : {
"revision" : "f998d8ae57f4ae7dc2235fafe355e5a1bb4449da",
"version" : "0.5.0"
"revision" : "026ce2192ddcf3174528d17a0989bc35fe67d92d",
"version" : "0.6.0"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Tuist/Package.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// swift-tools-version: 5.9
import PackageDescription
@preconcurrency import PackageDescription

#if TUIST
import ProjectDescription
Expand Down
2 changes: 1 addition & 1 deletion Tuist/ProjectDescriptionHelpers/Feature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public extension [Feature] {
}
}

public struct Feature: Dependable {
@frozen public struct Feature: Dependable {
let name: String
var targetName: String {
"ST\(name)"
Expand Down

0 comments on commit c66cd7e

Please sign in to comment.