Search code examples
iosswiftalamofire

Swift - Move or update file fails


I'm downloading loads of files with my Swift iOS app and I use incremental updating for my contents.

My download code is quite basic, I download a file using Alamofire into a temp directory, unzip it, check if the file or folder exists in the target directory, remove it if found, and move the new file/folder to that directory.

But there is rarely an issue after checking the existing directory and removing it. When I try to move the new file/folder to that directory, I get an error of

"File or directory already exists or contents is not empty."

But checking it out after it failed in the device directory, it shows a completely fine directory, no existing files that might collided, or sometimes the new content is indeed moved to the target directory just like it should, even it said failed. That is what bothers me the most.

Did anyone ever had this kind of behaviour when saving and moving files in the directory?

I almost forgot, I am working in the application support directory.

internal func loadArtifact(artifact: Artifact,  updateClosure: @escaping ((Bool, Int64, Int64, String?) -> ()))
{
    let serverpath = artifact.serverpath
    let name = artifact.name
    let zipname = artifact.zipname
    let root = artifact.artifactSetroot
    let hidden = artifact.artifactSet!.hidden
    let objectID = artifact.objectID

    let baseArtifactSet = artifactSetManager.defaultartifactSetManager.getartifactSetDirectory(artifact.artifactSet!)

    if baseArtifactSet == nil
    {
        errorLog("No target directory for artifactSet")
        return
    }
    if name == nil
    {
        errorLog("No name for artifact. Can´t save in temp")
        return
    }

    var targetDirectory = baseArtifactSet
    targetDirectory?.appendPathComponent("temp")

    targetDirectory?.appendPathComponent(zipname!.replacingOccurrences(of: ".zip", with: ""))

    let destination: DownloadRequest.DownloadFileDestination = { _, _ in
        return (targetDirectory!, [.removePreviousFile, .createIntermediateDirectories])
    }


    let url = URL(string: serverpath!)
    alamofireManager!.download(url!, to: destination).downloadProgress { (progress) in
        updateClosure(false, progress.completedUnitCount, progress.completedUnitCount, nil)
        }.responseData { (data) in
            traceLog("\(data)")
            if data.response?.statusCode == 200
            {
                traceLog("Got an artifact. Will continue to extract itto target position")
                self.asyncQueue.async {

                    var unzipTarget = baseArtifactSet


                    SSZipArchive.unzipFile(atPath: targetDirectory!.path, toDestination: root ? "\(unzipTarget!.path)/temp/root" : "\(unzipTarget!.path)/temp", progressHandler: { (message, something, progress, max) in

                    }, completionHandler: { (message, finished, error) in


                        let tmpPath = root ? "\(unzipTarget!.path)/temp/root" : "\(unzipTarget!.path)/temp/artifacts/\(zipname!.replacingOccurrences(of: ".zip", with: ""))"
                        let targetPath = root ? unzipTarget!.path : "\(unzipTarget!.path)/artifacts/\(zipname!.replacingOccurrences(of: ".zip", with: ""))"

                        if error == nil && root == false && FileManager.default.fileExists(atPath: targetPath)
                        {
                            do
                            {
                                try FileManager.default.removeItem(atPath: targetPath)
                                traceLog("Deletet old directory at path: \(unzipTarget!.path)")
                            }catch
                            {
                                errorLog("Could not remove directory at path: \(unzipTarget!.path) -  \(error)")
                                updateClosure(true,0,0,"Could not remove directory at path: \(unzipTarget!.path) -  \(error)")
                            }


                        }


                        do
                        {

                            if FileManager.default.fileExists(atPath: "\(unzipTarget!.path)/artifacts") == false
                            {
                                try FileManager.default.createDirectory(atPath: "\(unzipTarget!.path)/artifacts", withIntermediateDirectories: true, attributes: nil)
                            }




                            if root
                            {



                                do{

                                    let targetDirContent = try FileManager.default.contentsOfDirectory(atPath: targetPath)
                                    for file in targetDirContent
                                    {
                                        if file != "artifacts" && file != "temp"
                                        {
                                            try FileManager.default.removeItem(atPath: "\(targetPath)/\(file)")
                                        }
                                    }



                                    let files =  try FileManager.default.contentsOfDirectory(atPath: tmpPath)
                                    for file in files
                                    {
                                        let fileURL = URL(fileURLWithPath: "\(tmpPath)/\(file)")
                                        if fileURL != nil
                                        {
                                            try  FileManager.default.moveItem(atPath: fileURL.path, toPath: "\(targetPath)/\(file)")
                                        }
                                    }

                                    if root && hidden
                                    {
                                        let deviceInfoString = "var deviceInfo={'platform':'iOS'}"

                                        if FileManager.default.fileExists(atPath: "\(targetPath)/deviceInfo.js")
                                        {
                                            try FileManager.default.removeItem(atPath: "\(targetPath)/deviceInfo.js")   
                                        }

                                    }

                                }
                                catch{
                                    errorLog("Error while enumerating files in temp root directory: \(error)")
                                    updateClosure(true,0,0,"Error while enumerating files in temp root directory: \(error)")

                                }

                            }else
                            {
                                if FileManager.default.fileExists(atPath: targetPath)
                                {
                                    debugLog("Deleting: \(targetPath)")
                                    try FileManager.default.removeItem(atPath: targetPath)
                                }else
                                {
                                    debugLog("Creating: \(targetPath)")

                                }


                                debugLog("Trying to move")
                                try  FileManager.default.moveItem(atPath: tmpPath, toPath: targetPath)

                            }


                        }catch
                        {
                            errorLog("Could not move directory for artifact: \(unzipTarget!.path) -  \(error)")
                            updateClosure(true,0,0,"Could not move directory for artifact: \(unzipTarget!.path) -  \(error)")
                        }




                        try FileManager.default.removeItem(atPath: targetDirectory!.path)

                        self.asyncQueue.async (flags: .barrier) {

                            let ownContext =  MyStorageClient.defaultManager.newBackgroundWorkerMOC()
                            let ownArtifact = ownContext.object(with: objectID) as! Artifact

                            ownArtifact.state = ArtifactState.Ready.rawValue
                            ownArtifact.updateAvaiable = false

                            if root == true && ownArtifact.artifactSet?.state == artifactSetState.Initialised.rawValue
                            {
                                ownArtifact.artifactSet?.state = artifactSetState.Created.rawValue
                            }


                            do{
                                try ownContext.save()

                                updateClosure(true, 0,0,nil)

                            }catch{
                                errorLog("Error while saving artifact context")

                                updateClosure(true, 0,0, "Error while saving context after artifact Update. \(error)")
                            }

                        }
                    })

                }
            }else
            {

                errorLog("Something went wrong downloading an artifact: \(data.response)")
                updateClosure(true,0,0,"Something went wrong downloading an artifact: \(data.response)")

            }
    }


}

So, can anyone tell me, why it is failing? I added do-try-catches around most of the try lines, and when it failed, it is basically failing on this chunk:

 if FileManager.default.fileExists(atPath: targetPath)
 {
     debugLog("Deleting: \(targetPath)")
     try FileManager.default.removeItem(atPath: targetPath)
 }else
 {
     debugLog("Creating: \(targetPath)")

 }


  debugLog("Trying to move")
  try  FileManager.default.moveItem(atPath: tmpPath, toPath: targetPath)

targetPath is: file:///var/mobile/Containers/Data/Application/applicationIdAndStuff/Library/Application%20Support/environment/workspace/customers/customer/temp/downloadFileOrFolder

Another update: I changed the code, so the old file will be moved to a backup folder instead of deleting right away, because I thought changing the address should be faster than deleting the file entirely. Wrong. Now the error occurs more often than before.

So the issue for me is referred to the FileManager, that takes too long to finish procedures. I really don't want to put my block to sleep for any time, do you have another idea of how to handle this issue?


Solution

  • I figured out the issue, simply by sorting the code to required >only once tasks< and >tasks for each artifact< to take down pressure on the FileManager.

    So this code handles the download and unarchiving and moving process for each artifact and it always checked the directories on a higher level if they exist and add them if they do not. For each artifact. So this was already potential for a conflict when more than one download and unarchiving finishes almost at the same time and start doing the directory stuff.

    Then, by preparing the folder for moving files made more trouble than it helped. Although it works most of the time, it sometimes messes up the systems check to move the files and causes an error telling me that the directory or files already exist.

    So before getting to each loadArtifact for the files, I reset needed directories and create higher level directories in order to prevent repeating checks in the loop.

    Since that all works like expected.