Skip to content
Open
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
3 changes: 1 addition & 2 deletions Modules/CommonsLib/Sources/CommonsLib/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ public struct Constants {
}

public struct Folder {
public static let SignedContainerFolder = "SignedContainers"
public static let CryptoContainerFolder = "CryptoContainers"
public static let ContainerFolder = "Containers"
public static let Temp = "tempfiles"
public static let Shared = "shareddownloads"
public static let SavedFiles = "savedfiles"
Expand Down
31 changes: 29 additions & 2 deletions Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ extension CryptoContainer {
let sanitizedName = dataFile.key.sanitized()

let destinationPath = try Directories.getCacheDirectory(
subfolders: [Constants.Folder.CryptoContainerFolder, Constants.Folder.Temp],
subfolders: [Constants.Folder.ContainerFolder, Constants.Folder.Temp],
fileManager: fileManager
)

Expand Down Expand Up @@ -371,8 +371,35 @@ extension CryptoContainer {
dataFiles.append(fileUrl)
}

let fileManager = Container.shared.fileManager()

let cryptoContainersDirectory = try Directories.getCacheDirectory(
subfolders: [Constants.Folder.ContainerFolder],
fileManager: fileManager
)

let isFileInTempSignedContainerDirectory = containerFile.absoluteString.hasPrefix(
cryptoContainersDirectory.appending(path: Constants.Folder.Temp, directoryHint: .isDirectory).absoluteString
)

let isFileInRecentDocuments = containerFile.absoluteString.hasPrefix(
cryptoContainersDirectory.absoluteString
) && !isFileInTempSignedContainerDirectory

var renamedContainerFile = containerFile

if !isFileInRecentDocuments {
renamedContainerFile = Container.shared.containerUtil().getContainerFile(
for: containerFile,
in: isFileInRecentDocuments ? containerFile.deletingLastPathComponent() :
containerFile.deletingLastPathComponent().deletingLastPathComponent()
)

try fileManager.moveItem(at: containerFile, to: renamedContainerFile)
}

return try await create(
containerFile: containerFile,
containerFile: renamedContainerFile,
dataFiles: dataFiles,
recipients: recipients,
isDecrypted: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public actor ContainerWrapper: ContainerWrapperProtocol, Loggable {
}

@MainActor
public func saveDataFile(containerFile: URL, dataFile: DataFileWrapper, to directory: URL?) async throws -> URL {
public func saveDataFile(dataFile: DataFileWrapper, to directory: URL?) async throws -> URL {
let savedFilesDirectory = try directory ?? Directories.getCacheDirectory(
subfolders: [CommonsLib.Constants.Folder.SavedFiles],
fileManager: fileManager
Expand All @@ -80,8 +80,8 @@ public actor ContainerWrapper: ContainerWrapperProtocol, Loggable {

do {
try await DigiDocContainerWrapper.container(
containerFile.resolvedPath,
saveDataFile: dataFile.fileId,
containerURL.resolvedPath,
saveDataFile: dataFile.fileName,
to: tempSavedFileLocation.resolvedPath
)
ContainerWrapper.logger().info("Successfully saved \(sanitizedFilename) to 'Saved Files' directory")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public protocol ContainerWrapperProtocol: Sendable {
func create(file: URL, dataFiles: [String]) async throws
func open(containerFile: URL, isSivaConfirmed: Bool) async throws -> ContainerWrapper
@discardableResult func addDataFiles(containerFile: URL, dataFiles: [URL]) async throws -> ContainerWrapperProtocol
func saveDataFile(containerFile: URL, dataFile: DataFileWrapper, to directory: URL?) async throws -> URL
func saveDataFile(dataFile: DataFileWrapper, to directory: URL?) async throws -> URL
@discardableResult func removeSignature(index: Int, containerFile: URL) async throws -> ContainerWrapperProtocol
@discardableResult func removeDataFile(index: Int, containerFile: URL) async throws -> ContainerWrapperProtocol
func prepareSignature(
Expand All @@ -42,7 +42,7 @@ public protocol ContainerWrapperProtocol: Sendable {
}

extension ContainerWrapperProtocol {
func saveDataFile(containerFile: URL, dataFile: DataFileWrapper) async throws -> URL {
try await saveDataFile(containerFile: containerFile, dataFile: dataFile, to: nil)
func saveDataFile(dataFile: DataFileWrapper) async throws -> URL {
try await saveDataFile(dataFile: dataFile, to: nil)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,7 @@ public actor SignedContainer: SignedContainerProtocol, Loggable {
}

public func saveDataFile(dataFile: DataFileWrapper, to directory: URL?) async throws -> URL {
guard let containerFileURL = containerFile else {
throw DigiDocError.containerDataFileSavingFailed(
ErrorDetail(
message: "Unable to save container. No container file found.",
userInfo: ["fileName": containerFile?.lastPathComponent ?? "N/A"]
)
)
}
return try await container.saveDataFile(containerFile: containerFileURL, dataFile: dataFile, to: directory)
return try await container.saveDataFile(dataFile: dataFile, to: directory)
}

public func getNestedTimestampedContainer() async throws -> SignedContainerProtocol? {
Expand Down Expand Up @@ -361,7 +353,7 @@ extension SignedContainer {
let fileManager = Container.shared.fileManager()

let signedContainersDirectory = try Directories.getCacheDirectory(
subfolders: [Constants.Folder.SignedContainerFolder],
subfolders: [Constants.Folder.ContainerFolder],
fileManager: fileManager
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,34 +298,30 @@ struct ContainerWrapperTests {
return
}

let savedFileURL = try await containerWrapper.saveDataFile(containerFile: containerFile, dataFile: dataFile)
let savedFileURL = try await containerWrapper.saveDataFile(dataFile: dataFile)

#expect(savedFileURL.isValidURL())
#expect(savedFileURL.lastPathComponent == dataFile.fileName)
}

@Test
func saveDataFile_throwErrorWhenInvalidDataFile() async throws {
let sampleContainer = try await createSampleContainer(dataFileURLs: dataFileURLs)

guard let containerFile = await sampleContainer.getRawContainerFile() else { return }

defer {
try? FileManager.default.removeItem(at: containerFile)
}

let dataFile = MockDataFileWrapper.mockDataFileWrapper(
fileId: "",
fileName: "datafile-\(UUID().uuidString)",
fileSize: 0,
mediaType: CommonsLib.Constants.Extension.Default)

do {
_ = try await containerWrapper.saveDataFile(containerFile: containerFile, dataFile: dataFile)
_ = try await containerWrapper.saveDataFile(dataFile: dataFile)
Issue.record("Expected an error")
return
} catch let error as DigiDocError {
#expect(error.localizedDescription.contains("unable to save data file"))
guard case .containerDataFileSavingFailed = error else {
Issue.record("Unexpected DigiDocError error: \(error)")
return
}
#expect(true)
} catch {
Issue.record("Unexpected error: \(error)")
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ final class SignedContainerTests {

mockContainerUtil.getContainerFileHandler = { _, _ in uniqueFileURL }

mockContainerWrapper.saveDataFileHandler = { _, _, _ in uniqueFileURL }
mockContainerWrapper.saveDataFileHandler = { _, _ in uniqueFileURL }

mockFileManager.moveItemHandler = { _, _ in
do {
Expand Down Expand Up @@ -274,7 +274,7 @@ final class SignedContainerTests {
return uniqueFileURL
}

mockContainerWrapper.saveDataFileHandler = { _, _, _ in uniqueFileURL }
mockContainerWrapper.saveDataFileHandler = { _, _ in uniqueFileURL }

mockFileManager.moveItemHandler = { _, _ in
do {
Expand Down Expand Up @@ -371,7 +371,7 @@ final class SignedContainerTests {
[MockDataFileWrapper.mockDataFileWrapper()]
}

mockContainerWrapper.saveDataFileHandler = { _, _, _ in
mockContainerWrapper.saveDataFileHandler = { _, _ in
return URL(fileURLWithPath: "/tmp/mockFile.txt")
}

Expand Down Expand Up @@ -451,7 +451,7 @@ final class SignedContainerTests {
mockContainerDataFilesDirURL
}

mockContainerWrapper.saveDataFileHandler = { _, _, _ in
mockContainerWrapper.saveDataFileHandler = { _, _ in
testContainer
}

Expand Down Expand Up @@ -554,7 +554,7 @@ final class SignedContainerTests {
mockContainerDataFilesDirURL
}

mockContainerWrapper.saveDataFileHandler = { _, _, _ in
mockContainerWrapper.saveDataFileHandler = { _, _ in
throw URLError(.fileDoesNotExist)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public struct ContainerUtil: ContainerUtilProtocol, Loggable {

public func getSignatureContainersDir() throws -> URL {
let signedContainersDirectory = try Directories.getCacheDirectory(
subfolders: [Constants.Folder.SignedContainerFolder],
subfolders: [Constants.Folder.ContainerFolder],
fileManager: fileManager
)

Expand Down
54 changes: 32 additions & 22 deletions Modules/UtilsLib/Sources/UtilsLib/File/FileUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,23 +115,30 @@ public struct FileUtil: FileUtilProtocol, Loggable {
FileUtil.logger().info("Checking if file is from iCloud")
// Check if file is opened from iCloud
if isFileFromiCloud(fileURL: resolvedURL) {
if !isFileDownloadedFromiCloud(fileURL: resolvedURL) {
FileUtil.logger().info(
"File '\(resolvedURL.lastPathComponent)' from iCloud is not downloaded. Downloading..."
)

let downloadedFileUrl = await downloadFileFromiCloud(fileURL: resolvedURL)
if let fileUrl = downloadedFileUrl {
FileUtil.logger().info("File '\(resolvedURL.lastPathComponent)' downloaded from iCloud")
return fileUrl
} else {
FileUtil.logger().info(
"Unable to download file '\(resolvedURL.lastPathComponent)' from iCloud")
return nil
do {
return try await withCheckedThrowingContinuation { continuation in
let coordinator = NSFileCoordinator()
var error: NSError?

coordinator.coordinate(
readingItemAt: url,
options: .withoutChanges,
error: &error
) { coordURL in
continuation.resume(returning: coordURL)
}

if let error {
continuation.resume(throwing: error)
}
}
} else {
FileUtil.logger().info("File '\(resolvedURL.lastPathComponent)' from iCloud is already downloaded")
return url
} catch {
let fileName = resolvedURL.lastPathComponent
let errorDescription = String(reflecting: error)
FileUtil.logger().error(
"Unable to download file '\(fileName, privacy: .public)' from iCloud. \(errorDescription)"
)
return nil
}
}
}
Expand Down Expand Up @@ -208,17 +215,18 @@ public struct FileUtil: FileUtilProtocol, Loggable {
public func downloadFileFromiCloud(fileURL: URL) async -> URL? {
do {
try fileManager.startDownloadingUbiquitousItem(at: fileURL)
FileUtil.logger().info("Downloading file '\(fileURL.lastPathComponent)' from iCloud")
FileUtil.logger().info("Downloading file '\(fileURL.lastPathComponent, privacy: .public)' from iCloud")

while !isFileDownloadedFromiCloud(fileURL: fileURL) {
try await Task.sleep(for: .seconds(0.5))
}

FileUtil.logger().info("iCloud file '\(fileURL.lastPathComponent)' downloaded")
FileUtil.logger().info("iCloud file '\(fileURL.lastPathComponent, privacy: .public)' downloaded")
return fileURL
} catch {
let fileName = fileURL.lastPathComponent
FileUtil.logger().error(
"Unable to start iCloud file '\(fileURL.lastPathComponent)' download: \(error.localizedDescription)"
"Unable to start iCloud file '\(fileName, privacy: .public)' download: \(error.localizedDescription)"
)
return nil
}
Expand Down Expand Up @@ -318,12 +326,14 @@ public struct FileUtil: FileUtilProtocol, Loggable {

private func removeDirectory(at url: URL) {
let filePath = url.path(percentEncoded: false)
FileUtil.logger().info("Removing \(filePath)")
FileUtil.logger().info("Removing \(filePath, privacy: .public)")
do {
try fileManager.removeItem(at: url)
FileUtil.logger().info("\(filePath) removed")
FileUtil.logger().info("\(filePath, privacy: .public) removed")
} catch {
FileUtil.logger().error("Unable to remove \(filePath): \(error.localizedDescription)")
FileUtil.logger().error(
"Unable to remove \(filePath, privacy: .public): \(error.localizedDescription, privacy: .public)"
)
}
}
}
2 changes: 1 addition & 1 deletion Modules/UtilsLib/Sources/UtilsLib/System/SystemUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public struct SystemUtil: Loggable {
public static func getOSVersion() -> String {
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
let versionString = "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
logger().info("Operating system version: \(versionString)")
logger().info("Operating system version: \(versionString, privacy: .public)")
return versionString
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ struct ContainerUtilTests {
let cachesDir = URL(fileURLWithPath: "/mock/cache")
let expectedDir = cachesDir
.appending(path: BundleUtil.getBundleIdentifier())
.appending(path: Constants.Folder.SignedContainerFolder)
.appending(path: Constants.Folder.ContainerFolder)

mockFileManager.urlHandler = { directory, _, _, _ in
#expect(directory == .cachesDirectory)
Expand Down Expand Up @@ -184,10 +184,10 @@ struct ContainerUtilTests {
@Test
func getContainerDataFilesDir_returnDirectoryWhenFileInSignatureDirAndUseCacheDir() throws {
let cachesDir = URL(fileURLWithPath: "/mock/cache")
let signatureDir = cachesDir.appending(path: Constants.Folder.SignedContainerFolder)
let signatureDir = cachesDir.appending(path: Constants.Folder.ContainerFolder)
let containerFile = signatureDir.appending(path: "file.asice")
let expectedDataDir = cachesDir
.appending(path: Constants.Folder.SignedContainerFolder)
.appending(path: Constants.Folder.ContainerFolder)
.appending(path: "file.asice-data-files")

mockFileManager.urlHandler = { _, _, _, _ in cachesDir }
Expand Down
1 change: 1 addition & 0 deletions RIADigiDoc.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
Domain/Model/Error/NFC/DecryptError.swift,
Domain/Model/Error/NFC/ReadCertAndSignError.swift,
Domain/Model/Error/NFC/UnblockPINError.swift,
Domain/Model/File/FileOpeningMethod.swift,
Domain/Model/FileItem.swift,
Domain/Model/IdCard/IdCardData.swift,
Domain/Model/IdCard/PinResponse.swift,
Expand Down
26 changes: 26 additions & 0 deletions RIADigiDoc/Domain/Model/File/FileOpeningMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2017 - 2025 Riigi Infosüsteemi Amet
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/

import Foundation

public enum FileOpeningMethod: Int, Sendable {
case all = 0
case signing = 1
case crypto = 2
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ actor FileOpeningService: FileOpeningServiceProtocol {
from sourceURL: URL,
) async throws -> URL {
let signedContainersDirectory = try Directories.getCacheDirectory(
subfolders: [Constants.Folder.SignedContainerFolder],
subfolders: [Constants.Folder.ContainerFolder],
fileManager: fileManager
)

Expand Down
Loading