diff --git a/Sources/NextcloudKit/Extensions/NSLock+Extension.swift b/Sources/NextcloudKit/Extensions/NSLock+Extension.swift new file mode 100644 index 00000000..9cb023ac --- /dev/null +++ b/Sources/NextcloudKit/Extensions/NSLock+Extension.swift @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Claudio Cambra +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation + +extension NSLock { + @discardableResult + func perform(_ block: () throws -> T) rethrows -> T { + lock() + defer { unlock() } + return try block() + } +} diff --git a/Sources/NextcloudKit/Models/NKDownloadLimit.swift b/Sources/NextcloudKit/Models/NKDownloadLimit.swift index b3722a4c..adb5d54b 100644 --- a/Sources/NextcloudKit/Models/NKDownloadLimit.swift +++ b/Sources/NextcloudKit/Models/NKDownloadLimit.swift @@ -9,7 +9,7 @@ import Foundation /// /// Each relates to a share of a file and is optionally provided by the [Files Download Limit](https://github.com/nextcloud/files_downloadlimit) app for Nextcloud server. /// -public class NKDownloadLimit: NSObject { +public struct NKDownloadLimit: Sendable { /// /// The number of downloads which already happened. /// diff --git a/Sources/NextcloudKit/Models/NKFile.swift b/Sources/NextcloudKit/Models/NKFile.swift index 422b1477..a44e273d 100644 --- a/Sources/NextcloudKit/Models/NKFile.swift +++ b/Sources/NextcloudKit/Models/NKFile.swift @@ -5,7 +5,7 @@ import Foundation -public class NKFile: NSObject { +public struct NKFile: Sendable { public var account = "" public var classFile = "" public var commentsUnread: Bool = false diff --git a/Sources/NextcloudKit/Models/NKFileProperty.swift b/Sources/NextcloudKit/Models/NKFileProperty.swift index 1da8f4d5..1df5e71e 100644 --- a/Sources/NextcloudKit/Models/NKFileProperty.swift +++ b/Sources/NextcloudKit/Models/NKFileProperty.swift @@ -5,9 +5,18 @@ import Foundation +#if swift(<6.0) public class NKFileProperty: NSObject { public var classFile: String = "" public var iconName: String = "" public var name: String = "" public var ext: String = "" } +#else +public struct NKFileProperty: Sendable { + public var classFile: String = "" + public var iconName: String = "" + public var name: String = "" + public var ext: String = "" +} +#endif diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 87107a2d..ab7e2590 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -12,7 +12,7 @@ import MobileCoreServices import CoreServices #endif -public protocol NextcloudKitDelegate { +public protocol NextcloudKitDelegate: Sendable { func authenticationChallenge(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) @@ -29,7 +29,7 @@ public protocol NextcloudKitDelegate { func request(_ request: DataRequest, didParseResponse response: AFDataResponse) } -public class NKCommon: NSObject { +public struct NKCommon: Sendable { public var nksessions = ThreadSafeArray() public var delegate: NextcloudKitDelegate? @@ -82,7 +82,7 @@ public class NKCommon: NSObject { case xls = "xls" } - public struct UTTypeConformsToServer { + public struct UTTypeConformsToServer: Sendable { var typeIdentifier: String var classFile: String var editor: String @@ -91,9 +91,15 @@ public class NKCommon: NSObject { var account: String } +#if swift(<6.0) internal var utiCache = NSCache() internal var mimeTypeCache = NSCache() internal var filePropertiesCache = NSCache() +#else + internal var utiCache = [String: String]() + internal var mimeTypeCache = [String: String]() + internal var filePropertiesCache = [String: NKFileProperty]() +#endif internal var internalTypeIdentifiers = ThreadSafeArray() public var filenamePathLog: String = "" @@ -133,56 +139,77 @@ public class NKCommon: NSObject { // MARK: - Init - override init() { - super.init() - + init() { filenamePathLog = internalPathLog + "/" + internalFilenameLog } // MARK: - Type Identifier - public func clearInternalTypeIdentifier(account: String) { + mutating public func clearInternalTypeIdentifier(account: String) { internalTypeIdentifiers = internalTypeIdentifiers.filter({ $0.account != account }) } - public func addInternalTypeIdentifier(typeIdentifier: String, classFile: String, editor: String, iconName: String, name: String, account: String) { + mutating public func addInternalTypeIdentifier(typeIdentifier: String, classFile: String, editor: String, iconName: String, name: String, account: String) { if !internalTypeIdentifiers.contains(where: { $0.typeIdentifier == typeIdentifier && $0.editor == editor && $0.account == account}) { let newUTI = UTTypeConformsToServer(typeIdentifier: typeIdentifier, classFile: classFile, editor: editor, iconName: iconName, name: name, account: account) internalTypeIdentifiers.append(newUTI) } } - public func getInternalType(fileName: String, mimeType: String, directory: Bool, account: String) -> (mimeType: String, classFile: String, iconName: String, typeIdentifier: String, fileNameWithoutExt: String, ext: String) { + mutating public func getInternalType(fileName: String, mimeType: String, directory: Bool, account: String) -> (mimeType: String, classFile: String, iconName: String, typeIdentifier: String, fileNameWithoutExt: String, ext: String) { var ext = (fileName as NSString).pathExtension.lowercased() var mimeType = mimeType var classFile = "", iconName = "", typeIdentifier = "", fileNameWithoutExt = "" var inUTI: CFString? +#if swift(<6.0) if let cachedUTI = utiCache.object(forKey: ext as NSString) { inUTI = cachedUTI - } else { + } +#else + if let cachedUTI = utiCache[ext] { + inUTI = cachedUTI as CFString + } +#endif + if inUTI == nil { if let unmanagedFileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil) { inUTI = unmanagedFileUTI.takeRetainedValue() if let inUTI { +#if swift(<6.0) utiCache.setObject(inUTI, forKey: ext as NSString) +#else + utiCache[ext] = inUTI as String +#endif } } } - if let inUTI = inUTI { + if let inUTI { typeIdentifier = inUTI as String fileNameWithoutExt = (fileName as NSString).deletingPathExtension // contentType detect if mimeType.isEmpty { +#if swift(<6.0) if let cachedMimeUTI = mimeTypeCache.object(forKey: inUTI) { mimeType = cachedMimeUTI as String - } else { + } +#else + if let cachedMimeUTI = mimeTypeCache[inUTI as String] { + mimeType = cachedMimeUTI + } +#endif + + if mimeType.isEmpty { if let mimeUTI = UTTypeCopyPreferredTagWithClass(inUTI, kUTTagClassMIMEType) { let mimeUTIString = mimeUTI.takeRetainedValue() as String mimeType = mimeUTIString +#if swift(<6.0) mimeTypeCache.setObject(mimeUTIString as NSString, forKey: inUTI) +#else + mimeTypeCache[inUTI as String] = mimeUTIString as String +#endif } } } @@ -197,12 +224,21 @@ public class NKCommon: NSObject { } else { var fileProperties: NKFileProperty +#if swift(<6.0) if let cachedFileProperties = filePropertiesCache.object(forKey: inUTI) { fileProperties = cachedFileProperties } else { fileProperties = getFileProperties(inUTI: inUTI) filePropertiesCache.setObject(fileProperties, forKey: inUTI) } +#else + if let cachedFileProperties = filePropertiesCache[inUTI as String] { + fileProperties = cachedFileProperties + } else { + fileProperties = getFileProperties(inUTI: inUTI) + filePropertiesCache[inUTI as String] = fileProperties + } +#endif classFile = fileProperties.classFile iconName = fileProperties.iconName @@ -212,7 +248,7 @@ public class NKCommon: NSObject { } public func getFileProperties(inUTI: CFString) -> NKFileProperty { - let fileProperty = NKFileProperty() + var fileProperty = NKFileProperty() let typeIdentifier: String = inUTI as String if let fileExtension = UTTypeCopyPreferredTagWithClass(inUTI as CFString, kUTTagClassFilenameExtension) { diff --git a/Sources/NextcloudKit/NKDataFileXML.swift b/Sources/NextcloudKit/NKDataFileXML.swift index eef09d65..d9c567bd 100644 --- a/Sources/NextcloudKit/NKDataFileXML.swift +++ b/Sources/NextcloudKit/NKDataFileXML.swift @@ -7,7 +7,7 @@ import Foundation import SwiftyXMLParser class NKDataFileXML: NSObject { - let nkCommonInstance: NKCommon + var nkCommonInstance: NKCommon let requestBodyComments = """ @@ -307,7 +307,7 @@ class NKDataFileXML: NSObject { let elements = xml["d:multistatus", "d:response"] for element in elements { - let file = NKFile() + var file = NKFile() if let href = element["d:href"].text { var fileNamePath = href if href.last == "/" { @@ -577,7 +577,7 @@ class NKDataFileXML: NSObject { file.downloadLimits.append(NKDownloadLimit(count: count, limit: limit, token: token)) } - let results = self.nkCommonInstance.getInternalType(fileName: file.fileName, mimeType: file.contentType, directory: file.directory, account: nkSession.account) + var results = self.nkCommonInstance.getInternalType(fileName: file.fileName, mimeType: file.contentType, directory: file.directory, account: nkSession.account) file.contentType = results.mimeType file.iconName = results.iconName diff --git a/Sources/NextcloudKit/NKError.swift b/Sources/NextcloudKit/NKError.swift index f162a261..5d4db76d 100644 --- a/Sources/NextcloudKit/NKError.swift +++ b/Sources/NextcloudKit/NKError.swift @@ -32,7 +32,7 @@ extension OCSPath { static var ocsXMLMsg: Self { ["d:error", "s:message"] } } -public class NKError: NSObject { +public struct NKError: Sendable, Equatable { static let internalError = -9999 // Chunk error public static let chunkNoEnoughMemory = -9998 @@ -159,7 +159,7 @@ public class NKError: NSObject { self.responseData = responseData } - convenience init(httpResponse: HTTPURLResponse) { + init(httpResponse: HTTPURLResponse) { self.init(statusCode: httpResponse.statusCode, fallbackDescription: httpResponse.description) } @@ -181,7 +181,7 @@ public class NKError: NSObject { self.error = NSError(domain: NSCocoaErrorDomain, code: self.errorCode, userInfo: [NSLocalizedDescriptionKey: self.errorDescription]) } - public convenience init(error: AFError?, afResponse: T, responseData: Data? = nil) { + public init(error: AFError?, afResponse: T, responseData: Data? = nil) { if let errorCode = afResponse.response?.statusCode { guard let dataResponse = afResponse as? Alamofire.DataResponse, let errorData = dataResponse.data @@ -216,9 +216,13 @@ public class NKError: NSObject { } } - public override func isEqual(_ object: Any?) -> Bool { - if let object = object as? NKError { - return self.errorCode == object.errorCode && self.errorDescription == object.errorDescription + public static func == (lhs: NKError, rhs: NKError) -> Bool { + return lhs.errorCode == rhs.errorCode && lhs.errorDescription == rhs.errorDescription + } + + public static func == (lhs: NKError, rhs: NKError?) -> Bool { + if let rhs { + return lhs == rhs; } return false } diff --git a/Sources/NextcloudKit/NKSession.swift b/Sources/NextcloudKit/NKSession.swift index ba019afc..89e6c340 100644 --- a/Sources/NextcloudKit/NKSession.swift +++ b/Sources/NextcloudKit/NKSession.swift @@ -3,9 +3,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later import Foundation -import Alamofire +@preconcurrency import Alamofire -public class NKSession { +public struct NKSession: Sendable { public var urlBase: String public var user: String public var userId: String @@ -26,7 +26,8 @@ public class NKSession { public let sessionUploadBackgroundWWan: URLSession public let sessionUploadBackgroundExt: URLSession - init(urlBase: String, + init(nkCommonInstance: NKCommon, + urlBase: String, user: String, userId: String, password: String, @@ -51,7 +52,7 @@ public class NKSession { self.httpMaximumConnectionsPerHostInUpload = httpMaximumConnectionsPerHostInUpload self.requestCachePolicy = requestCachePolicy - let backgroundSessionDelegate = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance) + let backgroundSessionDelegate = NKBackground(nkCommonInstance: nkCommonInstance) /// Strange but works ?!?! let sharedCookieStorage = user + "@" + urlBase @@ -66,11 +67,11 @@ public class NKSession { configuration.httpCookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: sharedCookieStorage) sessionData = Alamofire.Session(configuration: configuration, - delegate: NextcloudKitSessionDelegate(nkCommonInstance: NextcloudKit.shared.nkCommonInstance), - rootQueue: NextcloudKit.shared.nkCommonInstance.rootQueue, - requestQueue: NextcloudKit.shared.nkCommonInstance.requestQueue, - serializationQueue: NextcloudKit.shared.nkCommonInstance.serializationQueue, - eventMonitors: [NKLogger(nkCommonInstance: NextcloudKit.shared.nkCommonInstance)]) + delegate: NextcloudKitSessionDelegate(nkCommonInstance: nkCommonInstance), + rootQueue: nkCommonInstance.rootQueue, + requestQueue: nkCommonInstance.requestQueue, + serializationQueue: nkCommonInstance.serializationQueue, + eventMonitors: [NKLogger(nkCommonInstance: nkCommonInstance)]) /// Session Download Background let configurationDownloadBackground = URLSessionConfiguration.background(withIdentifier: NKCommon().identifierSessionDownloadBackground) diff --git a/Sources/NextcloudKit/NextcloudKit+API.swift b/Sources/NextcloudKit/NextcloudKit+API.swift index a9ad6158..ec8bc84f 100644 --- a/Sources/NextcloudKit/NextcloudKit+API.swift +++ b/Sources/NextcloudKit/NextcloudKit+API.swift @@ -379,7 +379,19 @@ public extension NextcloudKit { #else #if os(iOS) - let screenScale = UIScreen.main.scale + var screenScale = 1.0 + if #available(iOS 13.0, *) { + let semaphore = DispatchSemaphore(value: 0) + Task { + screenScale = await UIScreen.main.scale + semaphore.signal() + } + semaphore.wait() + } else { + #if swift(<6.0) + screenScale = UIScreen.main.scale + #endif + } #else let screenScale = 1.0 #endif diff --git a/Sources/NextcloudKit/NextcloudKit+Upload.swift b/Sources/NextcloudKit/NextcloudKit+Upload.swift index 6438f082..809d1f35 100644 --- a/Sources/NextcloudKit/NextcloudKit+Upload.swift +++ b/Sources/NextcloudKit/NextcloudKit+Upload.swift @@ -161,7 +161,7 @@ public extension NextcloudKit { if error == .success { completion(NKError()) } else if error.errorCode == 404 { - NextcloudKit.shared.createFolder(serverUrlFileName: serverUrlChunkFolder, account: account, options: options) { _, _, _, _, error in + self.createFolder(serverUrlFileName: serverUrlChunkFolder, account: account, options: options) { _, _, _, _, error in completion(error) } } else { diff --git a/Sources/NextcloudKit/NextcloudKit.swift b/Sources/NextcloudKit/NextcloudKit.swift index ce7b8402..b4d55640 100644 --- a/Sources/NextcloudKit/NextcloudKit.swift +++ b/Sources/NextcloudKit/NextcloudKit.swift @@ -11,25 +11,35 @@ import Alamofire import SwiftyJSON open class NextcloudKit { +#if swift(<6.0) public static let shared: NextcloudKit = { let instance = NextcloudKit() return instance }() +#endif #if !os(watchOS) private let reachabilityManager = Alamofire.NetworkReachabilityManager() #endif - public let nkCommonInstance = NKCommon() + public var nkCommonInstance = NKCommon() internal lazy var internalSession: Alamofire.Session = { return Alamofire.Session(configuration: URLSessionConfiguration.af.default, delegate: NextcloudKitSessionDelegate(nkCommonInstance: nkCommonInstance), eventMonitors: [NKLogger(nkCommonInstance: self.nkCommonInstance)]) }() +#if swift(<6.0) init() { #if !os(watchOS) startNetworkReachabilityObserver() #endif } +#else + public init() { +#if !os(watchOS) + startNetworkReachabilityObserver() +#endif + } +#endif deinit { #if !os(watchOS) @@ -70,6 +80,7 @@ open class NextcloudKit { } let nkSession = NKSession( + nkCommonInstance: nkCommonInstance, urlBase: urlBase, user: user, userId: userId, @@ -94,7 +105,7 @@ open class NextcloudKit { userAgent: String? = nil, nextcloudVersion: Int? = nil, replaceWithAccount: String? = nil) { - guard let nkSession = nkCommonInstance.nksessions.filter({ $0.account == account }).first else { return } + guard var nkSession = nkCommonInstance.nksessions.filter({ $0.account == account }).first else { return } if let urlBase { nkSession.urlBase = urlBase } diff --git a/Sources/NextcloudKit/NextcloudKitBackground.swift b/Sources/NextcloudKit/NextcloudKitBackground.swift index 8b7da1ec..ad74e864 100644 --- a/Sources/NextcloudKit/NextcloudKitBackground.swift +++ b/Sources/NextcloudKit/NextcloudKitBackground.swift @@ -4,7 +4,7 @@ import Foundation -public class NKBackground: NSObject, URLSessionTaskDelegate, URLSessionDelegate, URLSessionDownloadDelegate { +public final class NKBackground: NSObject, URLSessionTaskDelegate, URLSessionDelegate, URLSessionDownloadDelegate { let nkCommonInstance: NKCommon public init(nkCommonInstance: NKCommon) { diff --git a/Sources/NextcloudKit/NextcloudKitSessionDelegate.swift b/Sources/NextcloudKit/NextcloudKitSessionDelegate.swift index c8b42707..9d9c65ce 100644 --- a/Sources/NextcloudKit/NextcloudKitSessionDelegate.swift +++ b/Sources/NextcloudKit/NextcloudKitSessionDelegate.swift @@ -14,15 +14,11 @@ import Alamofire import SwiftyJSON final class NextcloudKitSessionDelegate: SessionDelegate { - public var nkCommonInstance: NKCommon? + public let nkCommonInstance: NKCommon? - override public init(fileManager: FileManager = .default) { - super.init(fileManager: fileManager) - } - - convenience init(nkCommonInstance: NKCommon?) { - self.init() + public init(fileManager: FileManager = .default, nkCommonInstance: NKCommon? = nil) { self.nkCommonInstance = nkCommonInstance + super.init(fileManager: fileManager) } public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { diff --git a/Sources/NextcloudKit/Utils/FileAutoRenamer.swift b/Sources/NextcloudKit/Utils/FileAutoRenamer.swift index ca0d508a..b835f381 100644 --- a/Sources/NextcloudKit/Utils/FileAutoRenamer.swift +++ b/Sources/NextcloudKit/Utils/FileAutoRenamer.swift @@ -12,47 +12,43 @@ import Foundation // Copyright © 2024 Marino Faggiana. All rights reserved. // -public class FileAutoRenamer { +public final class FileAutoRenamer: Sendable { public static let shared: FileAutoRenamer = { let instance = FileAutoRenamer() return instance }() - private var forbiddenFileNameCharacters: [String] = [] - - private var forbiddenFileNameExtensions: [String] = [] { - didSet { - forbiddenFileNameExtensions = forbiddenFileNameExtensions.map({$0.lowercased()}) - } - } + private let forbiddenFileNameCharacters: [String] + private let forbiddenFileNameExtensions: [String] private let replacement = "_" - public func setup(forbiddenFileNameCharacters: [String], forbiddenFileNameExtensions: [String]) { + public init(forbiddenFileNameCharacters: [String] = [], forbiddenFileNameExtensions: [String] = []) { self.forbiddenFileNameCharacters = forbiddenFileNameCharacters - self.forbiddenFileNameExtensions = forbiddenFileNameExtensions + self.forbiddenFileNameExtensions = forbiddenFileNameExtensions.map { $0.lowercased() } } public func rename(filename: String, isFolderPath: Bool = false) -> String { var pathSegments = filename.split(separator: "/", omittingEmptySubsequences: false).map { String($0) } + var mutableForbiddenFileNameCharacters = self.forbiddenFileNameCharacters if isFolderPath { - forbiddenFileNameCharacters.removeAll { $0 == "/" } + mutableForbiddenFileNameCharacters.removeAll { $0 == "/" } } pathSegments = pathSegments.map { segment in var modifiedSegment = segment - forbiddenFileNameCharacters.forEach { forbiddenChar in + if mutableForbiddenFileNameCharacters.contains(" ") { + modifiedSegment = modifiedSegment.trimmingCharacters(in: .whitespaces) + } + + mutableForbiddenFileNameCharacters.forEach { forbiddenChar in if modifiedSegment.contains(forbiddenChar) { modifiedSegment = modifiedSegment.replacingOccurrences(of: forbiddenChar, with: replacement, options: .caseInsensitive) } } - if forbiddenFileNameExtensions.contains(" ") { - modifiedSegment = modifiedSegment.trimmingCharacters(in: .whitespaces) - } - // Replace forbidden extension, if any (ex. .part -> _part) forbiddenFileNameExtensions.forEach { forbiddenExtension in if modifiedSegment.lowercased().hasSuffix(forbiddenExtension) && isFullExtension(forbiddenExtension) { @@ -77,6 +73,11 @@ public class FileAutoRenamer { modifiedSegment.append(".\(fileExtension.lowercased())") } + if modifiedSegment.hasPrefix(".") { + modifiedSegment.remove(at: modifiedSegment.startIndex) + modifiedSegment = replacement + modifiedSegment + } + return modifiedSegment } diff --git a/Sources/NextcloudKit/Utils/FileNameValidator.swift b/Sources/NextcloudKit/Utils/FileNameValidator.swift index 52dc05c1..af509cab 100644 --- a/Sources/NextcloudKit/Utils/FileNameValidator.swift +++ b/Sources/NextcloudKit/Utils/FileNameValidator.swift @@ -4,61 +4,39 @@ import Foundation -public class FileNameValidator { - public static let shared: FileNameValidator = { - let instance = FileNameValidator() - return instance - }() - - private var forbiddenFileNames: [String] = [] { - didSet { - forbiddenFileNames = forbiddenFileNames.map({$0.uppercased()}) - } - } - - private var forbiddenFileNameBasenames: [String] = [] { - didSet { - forbiddenFileNameBasenames = forbiddenFileNameBasenames.map({$0.uppercased()}) - } - } - - private var forbiddenFileNameCharacters: [String] = [] - - private var forbiddenFileNameExtensions: [String] = [] { - didSet { - forbiddenFileNameExtensions = forbiddenFileNameExtensions.map({$0.uppercased()}) - } +public final class FileNameValidator: Sendable { + private let forbiddenFileNames: [String] + private let forbiddenFileNameBasenames: [String] + private let forbiddenFileNameCharacters: [String] + private let forbiddenFileNameExtensions: [String] + + public func fileWithSpaceError() -> NKError { + NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: NSLocalizedString("_file_name_validator_error_space_", value: "Name must not contain spaces at the beginning or end.", comment: "")) } - public let fileWithSpaceError = NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: NSLocalizedString("_file_name_validator_error_space_", value: "Name must not contain spaces at the beginning or end.", comment: "")) - - public var fileReservedNameError: NKError { + public func fileReservedNameError(templateString: String) -> NKError { let errorMessageTemplate = NSLocalizedString("_file_name_validator_error_reserved_name_", value: "\"%@\" is a forbidden name.", comment: "") let errorMessage = String(format: errorMessageTemplate, templateString) return NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: errorMessage) } - public var fileForbiddenFileExtensionError: NKError { - let errorMessageTemplate = NSLocalizedString("_file_name_validator_error_forbidden_file_extension_", value: "\".%@\" is a forbidden file extension.", comment: "") + public func fileForbiddenFileExtensionError(templateString: String) -> NKError { + let errorMessageTemplate = NSLocalizedString("_file_name_validator_error_forbidden_file_extension_", value: ".\"%@\" is a forbidden file extension.", comment: "") let errorMessage = String(format: errorMessageTemplate, templateString) return NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: errorMessage) } - public var fileInvalidCharacterError: NKError { + public func fileInvalidCharacterError(templateString: String) -> NKError { let errorMessageTemplate = NSLocalizedString("_file_name_validator_error_invalid_character_", value: "Name contains an invalid character: \"%@\".", comment: "") let errorMessage = String(format: errorMessageTemplate, templateString) return NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: errorMessage) } - private var templateString = "" - - private init() {} - - public func setup(forbiddenFileNames: [String], forbiddenFileNameBasenames: [String], forbiddenFileNameCharacters: [String], forbiddenFileNameExtensions: [String]) { - self.forbiddenFileNames = forbiddenFileNames - self.forbiddenFileNameBasenames = forbiddenFileNameBasenames + public init(forbiddenFileNames: [String], forbiddenFileNameBasenames: [String], forbiddenFileNameCharacters: [String], forbiddenFileNameExtensions: [String]) { + self.forbiddenFileNames = forbiddenFileNames.map { $0.uppercased() } + self.forbiddenFileNameBasenames = forbiddenFileNameBasenames.map { $0.uppercased() } self.forbiddenFileNameCharacters = forbiddenFileNameCharacters - self.forbiddenFileNameExtensions = forbiddenFileNameExtensions + self.forbiddenFileNameExtensions = forbiddenFileNameExtensions.map { $0.uppercased() } } public func checkFileName(_ filename: String) -> NKError? { @@ -68,23 +46,20 @@ public class FileNameValidator { if forbiddenFileNames.contains(filename.uppercased()) || forbiddenFileNames.contains(filename.withRemovedFileExtension.uppercased()) || forbiddenFileNameBasenames.contains(filename.uppercased()) || forbiddenFileNameBasenames.contains(filename.withRemovedFileExtension.uppercased()) { - templateString = filename - return fileReservedNameError + return fileReservedNameError(templateString: filename) } for fileNameExtension in forbiddenFileNameExtensions { if fileNameExtension == " " { if filename.uppercased().hasSuffix(fileNameExtension) || filename.uppercased().hasPrefix(fileNameExtension) { - return fileWithSpaceError + return fileWithSpaceError() } } else if filename.uppercased().hasSuffix(fileNameExtension.uppercased()) { if fileNameExtension == " " { - return fileWithSpaceError + return fileWithSpaceError() } - templateString = filename.fileExtension - - return fileForbiddenFileExtensionError + return fileForbiddenFileExtensionError(templateString: filename.fileExtension) } } @@ -106,8 +81,7 @@ public class FileNameValidator { let range = NSRange(location: 0, length: charAsString.utf16.count) if regex.firstMatch(in: charAsString, options: [], range: range) != nil { - templateString = charAsString - return fileInvalidCharacterError + return fileInvalidCharacterError(templateString: charAsString) } } return nil diff --git a/Sources/NextcloudKit/Utils/ThreadSafeArray.swift b/Sources/NextcloudKit/Utils/ThreadSafeArray.swift index 1dec61d1..fae38414 100644 --- a/Sources/NextcloudKit/Utils/ThreadSafeArray.swift +++ b/Sources/NextcloudKit/Utils/ThreadSafeArray.swift @@ -7,14 +7,13 @@ import Foundation /// A thread-safe array. -public class ThreadSafeArray { +public struct ThreadSafeArray: Sendable { private var array = [Element]() - private let queue = DispatchQueue(label: "com.nextcloud.ThreadSafeArray", attributes: .concurrent) public init() { } - public convenience init(_ array: [Element]) { + public init(_ array: [Element]) { self.init() self.array = array } @@ -26,37 +25,27 @@ public extension ThreadSafeArray { /// The first element of the collection. var first: Element? { - var result: Element? - queue.sync { result = self.array.first } - return result + NSLock().perform { self.array.first } } /// The last element of the collection. var last: Element? { - var result: Element? - queue.sync { result = self.array.last } - return result + NSLock().perform { self.array.last } } /// The number of elements in the array. var count: Int { - var result = 0 - queue.sync { result = self.array.count } - return result + NSLock().perform { self.array.count } } /// A Boolean value indicating whether the collection is empty. var isEmpty: Bool { - var result = false - queue.sync { result = self.array.isEmpty } - return result + NSLock().perform { self.array.isEmpty } } /// A textual representation of the array and its elements. var description: String { - var result = "" - queue.sync { result = self.array.description } - return result + NSLock().perform { self.array.description } } } @@ -69,9 +58,7 @@ public extension ThreadSafeArray { /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match. /// - Returns: The first element of the sequence that satisfies predicate, or nil if there is no element that satisfies predicate. func first(where predicate: (Element) -> Bool) -> Element? { - var result: Element? - queue.sync { result = self.array.first(where: predicate) } - return result + NSLock().perform { self.array.first(where: predicate) } } /// Returns the last element of the sequence that satisfies the given predicate. @@ -79,9 +66,7 @@ public extension ThreadSafeArray { /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match. /// - Returns: The last element of the sequence that satisfies predicate, or nil if there is no element that satisfies predicate. func last(where predicate: (Element) -> Bool) -> Element? { - var result: Element? - queue.sync { result = self.array.last(where: predicate) } - return result + NSLock().perform { self.array.last(where: predicate) } } /// Returns an array containing, in order, the elements of the sequence that satisfy the given predicate. @@ -89,9 +74,7 @@ public extension ThreadSafeArray { /// - Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array. /// - Returns: An array of the elements that includeElement allowed. func filter(_ isIncluded: @escaping (Element) -> Bool) -> ThreadSafeArray { - var result: ThreadSafeArray? - queue.sync { result = ThreadSafeArray(self.array.filter(isIncluded)) } - return result! + NSLock().perform { ThreadSafeArray(self.array.filter(isIncluded)) } } /// Returns the first index in which an element of the collection satisfies the given predicate. @@ -99,9 +82,7 @@ public extension ThreadSafeArray { /// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match. /// - Returns: The index of the first element for which predicate returns true. If no elements in the collection satisfy the given predicate, returns nil. func index(where predicate: (Element) -> Bool) -> Int? { - var result: Int? - queue.sync { result = self.array.firstIndex(where: predicate) } - return result + NSLock().perform { self.array.firstIndex(where: predicate) } } /// Returns the elements of the collection, sorted using the given predicate as the comparison between elements. @@ -109,9 +90,7 @@ public extension ThreadSafeArray { /// - Parameter areInIncreasingOrder: A predicate that returns true if its first argument should be ordered before its second argument; otherwise, false. /// - Returns: A sorted array of the collection’s elements. func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> ThreadSafeArray { - var result: ThreadSafeArray? - queue.sync { result = ThreadSafeArray(self.array.sorted(by: areInIncreasingOrder)) } - return result! + NSLock().perform { ThreadSafeArray(self.array.sorted(by: areInIncreasingOrder)) } } /// Returns an array containing the results of mapping the given closure over the sequence’s elements. @@ -119,9 +98,7 @@ public extension ThreadSafeArray { /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value. /// - Returns: An array of the non-nil results of calling transform with each element of the sequence. func map(_ transform: @escaping (Element) -> ElementOfResult) -> [ElementOfResult] { - var result = [ElementOfResult]() - queue.sync { result = self.array.map(transform) } - return result + NSLock().perform { self.array.map(transform) } } /// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence. @@ -129,9 +106,7 @@ public extension ThreadSafeArray { /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value. /// - Returns: An array of the non-nil results of calling transform with each element of the sequence. func compactMap(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] { - var result = [ElementOfResult]() - queue.sync { result = self.array.compactMap(transform) } - return result + NSLock().perform { self.array.compactMap(transform) } } /// Returns the result of combining the elements of the sequence using the given closure. @@ -141,9 +116,7 @@ public extension ThreadSafeArray { /// - nextPartialResult: A closure that combines an accumulating value and an element of the sequence into a new accumulating value, to be used in the next call of the nextPartialResult closure or returned to the caller. /// - Returns: The final accumulated value. If the sequence has no elements, the result is initialResult. func reduce(_ initialResult: ElementOfResult, _ nextPartialResult: @escaping (ElementOfResult, Element) -> ElementOfResult) -> ElementOfResult { - var result: ElementOfResult? - queue.sync { result = self.array.reduce(initialResult, nextPartialResult) } - return result ?? initialResult + NSLock().perform { self.array.reduce(initialResult, nextPartialResult) } } /// Returns the result of combining the elements of the sequence using the given closure. @@ -153,16 +126,14 @@ public extension ThreadSafeArray { /// - updateAccumulatingResult: A closure that updates the accumulating value with an element of the sequence. /// - Returns: The final accumulated value. If the sequence has no elements, the result is initialResult. func reduce(into initialResult: ElementOfResult, _ updateAccumulatingResult: @escaping (inout ElementOfResult, Element) -> Void) -> ElementOfResult { - var result: ElementOfResult? - queue.sync { result = self.array.reduce(into: initialResult, updateAccumulatingResult) } - return result ?? initialResult + NSLock().perform { self.array.reduce(into: initialResult, updateAccumulatingResult) } } /// Calls the given closure on each element in the sequence in the same order as a for-in loop. /// /// - Parameter body: A closure that takes an element of the sequence as a parameter. func forEach(_ body: (Element) -> Void) { - queue.sync { self.array.forEach(body) } + NSLock().perform { self.array.forEach(body) } } /// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate. @@ -170,9 +141,7 @@ public extension ThreadSafeArray { /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element represents a match. /// - Returns: true if the sequence contains an element that satisfies predicate; otherwise, false. func contains(where predicate: (Element) -> Bool) -> Bool { - var result = false - queue.sync { result = self.array.contains(where: predicate) } - return result + NSLock().perform { self.array.contains(where: predicate) } } /// Returns a Boolean value indicating whether every element of a sequence satisfies a given predicate. @@ -180,18 +149,14 @@ public extension ThreadSafeArray { /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element satisfies a condition. /// - Returns: true if the sequence contains only elements that satisfy predicate; otherwise, false. func allSatisfy(_ predicate: (Element) -> Bool) -> Bool { - var result = false - queue.sync { result = self.array.allSatisfy(predicate) } - return result + NSLock().perform { self.array.allSatisfy(predicate) } } /// Returns the array /// /// - Returns: the array part. func getArray() -> [Element]? { - var results: [Element]? - queue.sync { results = self.array } - return results + NSLock().perform { self.array } } } @@ -202,19 +167,15 @@ public extension ThreadSafeArray { /// Adds a new element at the end of the array. /// /// - Parameter element: The element to append to the array. - func append(_ element: Element) { - queue.async(flags: .barrier) { - self.array.append(element) - } + mutating func append(_ element: Element) { + NSLock().perform { self.array.append(element) } } /// Adds new elements at the end of the array. /// /// - Parameter element: The elements to append to the array. - func append(_ elements: [Element]) { - queue.async(flags: .barrier) { - self.array += elements - } + mutating func append(_ elements: [Element]) { + NSLock().perform { self.array += elements } } /// Inserts a new element at the specified position. @@ -222,10 +183,8 @@ public extension ThreadSafeArray { /// - Parameters: /// - element: The new element to insert into the array. /// - index: The position at which to insert the new element. - func insert(_ element: Element, at index: Int) { - queue.async(flags: .barrier) { - self.array.insert(element, at: index) - } + mutating func insert(_ element: Element, at index: Int) { + NSLock().perform { self.array.insert(element, at: index) } } /// Removes and returns the element at the specified position. @@ -233,11 +192,8 @@ public extension ThreadSafeArray { /// - Parameters: /// - index: The position of the element to remove. /// - completion: The handler with the removed element. - func remove(at index: Int, completion: ((Element) -> Void)? = nil) { - queue.async(flags: .barrier) { - let element = self.array.remove(at: index) - DispatchQueue.main.async { completion?(element) } - } + mutating func remove(at index: Int, completion: ((Element) -> Void)? = nil) { + NSLock().perform { self.array.remove(at: index) } } /// Removes and returns the elements that meet the criteria. @@ -245,27 +201,21 @@ public extension ThreadSafeArray { /// - Parameters: /// - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match. /// - completion: The handler with the removed elements. - func remove(where predicate: @escaping (Element) -> Bool, completion: (([Element]) -> Void)? = nil) { - queue.async(flags: .barrier) { + mutating func remove(where predicate: @escaping (Element) -> Bool) -> [Element] { + return NSLock().perform { var elements = [Element]() - while let index = self.array.firstIndex(where: predicate) { elements.append(self.array.remove(at: index)) } - - DispatchQueue.main.async { completion?(elements) } + return elements } } /// Removes all elements from the array. /// - /// - Parameter completion: The handler with the removed elements. - func removeAll(completion: (([Element]) -> Void)? = nil) { - queue.async(flags: .barrier) { - let elements = self.array - self.array.removeAll() - DispatchQueue.main.async { completion?(elements) } - } + /// - Parameter keepingCapacity: Pass true to keep the existing capacity of the array after removing its elements. The default value is false. + mutating func removeAll(keepingCapacity: Bool = false) { + NSLock().perform { self.array.removeAll(keepingCapacity: keepingCapacity) } } } @@ -277,21 +227,14 @@ public extension ThreadSafeArray { /// - Returns: optional element if it exists. subscript(index: Int) -> Element? { get { - var result: Element? - - queue.sync { - guard self.array.startIndex.. Bool { - var result = false - queue.sync { result = self.array.contains(element) } - return result + NSLock().perform { self.array.contains(element) } } } diff --git a/Tests/NextcloudKitIntegrationTests/Common/BaseXCTestCase.swift b/Tests/NextcloudKitIntegrationTests/Common/BaseXCTestCase.swift index 393d2e78..158b3720 100644 --- a/Tests/NextcloudKitIntegrationTests/Common/BaseXCTestCase.swift +++ b/Tests/NextcloudKitIntegrationTests/Common/BaseXCTestCase.swift @@ -10,11 +10,17 @@ import NextcloudKit class BaseXCTestCase: XCTestCase { var appToken = "" + var ncKit: NextcloudKit! - func setupAppToken() { + func setupAppToken() async { let expectation = expectation(description: "Should get app token") +#if swift(<6.0) + ncKit = NextcloudKit.shared +#else + ncKit = NextcloudKit() +#endif - NextcloudKit.shared.getAppPassword(url: TestConstants.server, user: TestConstants.username, password: TestConstants.password) { token, _, error in + ncKit.getAppPassword(url: TestConstants.server, user: TestConstants.username, password: TestConstants.password) { token, _, error in XCTAssertEqual(error.errorCode, 0) XCTAssertNotNil(token) @@ -23,11 +29,11 @@ class BaseXCTestCase: XCTestCase { self.appToken = token expectation.fulfill() } - - waitForExpectations(timeout: TestConstants.timeoutLong) + + await fulfillment(of: [expectation], timeout: TestConstants.timeoutLong) } - override func setUpWithError() throws { - setupAppToken() + override func setUp() async throws { + await setupAppToken() } } diff --git a/Tests/NextcloudKitUnitTests/FileAutoRenamerUnitTests.swift b/Tests/NextcloudKitUnitTests/FileAutoRenamerUnitTests.swift index d82ae672..f2191821 100644 --- a/Tests/NextcloudKitUnitTests/FileAutoRenamerUnitTests.swift +++ b/Tests/NextcloudKitUnitTests/FileAutoRenamerUnitTests.swift @@ -12,11 +12,11 @@ import Testing let forbiddenFilenameExtension = "." let characterArrays = [ - ["\\\\", "*", ">", "&", "/", "|", ":", "<", "?"], - [">", ":", "?", "&", "*", "\\\\", "|", "<", "/"], - ["<", "|", "?", ":", "&", "*", "\\\\", "/", ">"], - ["?", "/", ":", "&", "<", "|", ">", "\\\\", "*"], - ["&", "<", "|", "*", "/", "?", ">", ":", "\\\\"] + ["\\\\", "*", ">", "&", "/", "|", ":", "<", "?", " "], + [">", ":", "?", " ", "&", "*", "\\\\", "|", "<", "/"], + ["<", "|", "?", ":", "&", "*", "\\\\", " ", "/", ">"], + ["?", "/", " ", ":", "&", "<", "|", ">", "\\\\", "*"], + ["&", "<", "|", "*", "/", "?", ">", " ", ":", "\\\\"] ] let extensionArrays = [ @@ -35,7 +35,7 @@ import Testing @Test func testInvalidChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -49,7 +49,7 @@ import Testing @Test func testInvalidExtension() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -63,7 +63,7 @@ import Testing @Test func testMultipleInvalidChars() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -77,7 +77,7 @@ import Testing @Test func testStartEndInvalidExtensions() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -91,7 +91,7 @@ import Testing @Test func testStartInvalidExtension() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -105,7 +105,7 @@ import Testing @Test func testEndInvalidExtension() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -119,7 +119,7 @@ import Testing @Test func testHiddenFile() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -133,7 +133,7 @@ import Testing @Test func testUppercaseExtension() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -147,7 +147,7 @@ import Testing @Test func testMiddleNonPrintableChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -161,7 +161,7 @@ import Testing @Test func testStartNonPrintableChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -175,7 +175,7 @@ import Testing @Test func testEndNonPrintableChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -189,7 +189,7 @@ import Testing @Test func testExtensionNonPrintableChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -203,7 +203,7 @@ import Testing @Test func testMiddleInvalidFolderChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -217,7 +217,7 @@ import Testing @Test func testEndInvalidFolderChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -231,7 +231,7 @@ import Testing @Test func testStartInvalidFolderChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -245,7 +245,7 @@ import Testing @Test func testMixedInvalidChar() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -259,7 +259,7 @@ import Testing @Test func testStartsWithPathSeparator() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) @@ -273,7 +273,7 @@ import Testing @Test func testStartsWithPathSeparatorAndValidFilepath() { for (characterArray, extensionArray) in combinedTuples { - fileAutoRenamer.setup( + let fileAutoRenamer = FileAutoRenamer( forbiddenFileNameCharacters: characterArray, forbiddenFileNameExtensions: extensionArray ) diff --git a/Tests/NextcloudKitUnitTests/FileNameValidatorUnitTests.swift b/Tests/NextcloudKitUnitTests/FileNameValidatorUnitTests.swift index fab4d2ed..7828c27a 100644 --- a/Tests/NextcloudKitUnitTests/FileNameValidatorUnitTests.swift +++ b/Tests/NextcloudKitUnitTests/FileNameValidatorUnitTests.swift @@ -6,10 +6,10 @@ import XCTest @testable import NextcloudKit class FileNameValidatorUnitTests: XCTestCase { - let fileNameValidator = FileNameValidator.shared + var fileNameValidator: FileNameValidator! override func setUp() { - fileNameValidator.setup( + fileNameValidator = FileNameValidator( forbiddenFileNames: [".htaccess",".htaccess"], forbiddenFileNameBasenames: ["con", "prn", "aux", "nul", "com0", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", "com¹", "com²", "com³", @@ -23,31 +23,31 @@ class FileNameValidatorUnitTests: XCTestCase { func testInvalidCharacter() { let result = fileNameValidator.checkFileName("file