I wish to copy video file from Photo Library to my app's Documents directory and wish to be notified about completion. Here is what I do:
let videoAsset = fetchResult.object(at: indexPath.item)
print(videoAsset.description)
let options = PHVideoRequestOptions()
options.version = .original
PHImageManager.default().requestAVAsset(forVideo: videoAsset, options: options) { [weak self] (avAsset, audioMix, info) in
if let avurlAsset = avAsset as? AVURLAsset {
let url = avurlAsset.url
let toUrl = //some Url
let fileManager = FileManager.default
do {
try fileManager.copyItem(at: url, to: toUrl)
} catch {
NSLog("Unable to copy file from \(url) to \(toUrl)")
}
}
}
Only problem with this approach is I have no way to be notified of completion of copyItem. What is the alternative to copyItem method (or altogether a different approach to above) that is atleast blocking till copy finishes? Is it possible to use FileHandle & read consecutive bytes and write to another file? Will that be synchronous enough?
EDIT: As pointed by Alex, copyItem is actually synchronous routine. On closer inspection, I see I sometimes get errors on copying. Not sure why the permission errors show up when it is app's Documents folder where I copy.
2018-08-27 20:30:07.485841+0530 MyProject[3577:1288452] Copying file...
2018-08-27 20:30:07.487880+0530 MyProject[3577:1288452] stat on /var/mobile/Media/DCIM/107APPLE/IMG_7915.MP4: Operation not permitted
2018-08-27 20:30:07.512994+0530
MyProject[3577:1288452] Unable to copy file from file:///var/mobile/Media/DCIM/107APPLE/IMG_7915.MP4 to file:///var/mobile/Containers/Data/Application/CC13FD5A-E4CF-42A1-931F-2F1FFE799C15/Documents/IMG-0027.mov, Error Domain=NSCocoaErrorDomain Code=513 "“IMG_7915.MP4” couldn’t be copied because you don’t have permission to access “Documents”." UserInfo=
{NSSourceFilePathErrorKey=/var/mobile/Media/DCIM/107APPLE/IMG_7915.MP4, NSUserStringVariant=(
Copy
),
NSDestinationFilePath=/var/mobile/Containers/Data/Application/CC13FD5A-E4CF-42A1-931F-2F1FFE799C15/Documents/IMG-0027.mov, NSFilePath=/var/mobile/Media/DCIM/107APPLE/IMG_7915.MP4, NSUnderlyingError=0x111c441c0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
Copyitem: Copies the item at the specified path to a new location synchronously.
Returns true if the item was copied successfully or the file manager’s delegate stopped the operation deliberately. Returns false if an error occurred.
This is a sync method so after it executed after catch without error then it means successful copied.
https://developer.apple.com/documentation/foundation/filemanager/1407903-copyitem
Related
I'm basically trying to save an audio file to an iPhone device, specifically in the Files app.
This is the code I'm using which works in the iOS Simulator:
if let audioUrl = URL(string: "https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3") {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
print(destinationUrl)
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
print("The file already exists at path", destinationUrl)
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: audioUrl) { location, response, error in
guard let location = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: location, to: destinationUrl)
print("File moved to documents folder", destinationUrl)
} catch {
print(error)
}
}.resume()
}
}
this is the result in the simulator:
2022-04-08 15:48:17.605964+0200 TunnelPlay[3977:122650] [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics
File moved to documents folder file:///Users/panashemuzamhindo/Library/Developer/CoreSimulator/Devices/A7F7DD88-1E0C-45CD-9783-F46E6644F98C/data/Containers/Data/Application/EDE8B3CC-C290-4369-8B88-4AE1717B9DB8/Documents/1645329769Sengkhathele(featit.caramel).mp3
Main Problem: When I try run this section in TestFlight, the app runs the save process but theres nothing in Files, maybe I'm looking for it in the wrong location? or I need some permissions?
Desired Outcome: I want to see the downloaded MP3 in the iPhone Files app.
And I did try to change the scheme to Release instead of Debug, but it still doesn't save the file.
If you want to be able to access the files in the iOS Files app, make sure to also enable LSSupportsOpeningDocumentsInPlace (in addition to enabling UIFileSharingEnabled)
LSSupportsOpeningDocumentsInPlace
A Boolean value indicating whether the app may open the original document from a file provider, rather than a copy of the document.
UIFileSharingEnabled
A Boolean value indicating whether the app shares files.
I have the following code. My goal is to decode the data once the file has been downloaded to the device. I used fileExists to check if the file existed on iCloud. And then startDownloadingUbiquitousItem to download it. But I can't figure out how to tell if it has been downloaded. May I ask if there is a way to tell its downloaded? Like a completion handler or notification?
if paperyCloudURL != nil, FileManager.default.fileExists(atPath: paperyCloudURL!.path, isDirectory: nil) {
try? FileManager.default.startDownloadingUbiquitousItem(at: paperyCloudURL!)
//TODO: Should have check if the file exist before load
let data = try Data(contentsOf: paperyCloudURL!)
dataModel = try! decoder.decode(DataModel.self, from: data)
}
You need to test the file NSMetadataQuery object key NSMetadataUbiquitousItemDownloadingStatusKey is set to NSMetadataUbiquitousItemDownloadingStatusCurrent.
Error Domain=NSCocoaErrorDomain Code=257 "The file “IMG_9807.MOV” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/var/mobile/Media/DCIM/109APPLE/IMG_9807.MOV, NSUnderlyingError=0x1c1e5fe00 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"
i am sending asset URL to other controller and try to convert into data
PHImageManager.default().requestAVAsset(forVideo: self.albumView.phAsset, options: options) { (video, audioMix, info) in
DispatchQueue.main.async {
let urlAsset = video as! AVURLAsset
self.dismiss(animated: false, completion: {
self.delegate?.fusumaVideoCompleted(withFileURL: urlAsset.url)
})
}
}
here below methods for convert AVAssetUrl to data
do {
let data = try Data(contentsOf: product.videoURL, options: .mappedIfSafe)
return .upload(.multipart([MultipartFormData(provider: .data(data), name: "post[video]", fileName: "video.\(pathExtension)", mimeType: "video/\(pathExtension)")]))
} catch {
debugPrint(error)
}
As the error tells you, you cannot access the video file in the user's photo library by way of its URL for purposes of uploading it. You should obtain the video data and upload that. A video is very big, so you should not get the data directly and hold it in memory; instead, export the data to a file in a place that you are allowed to access, such as the Temporary folder.
To do that, you might (for example) use this method:
https://developer.apple.com/documentation/photos/phassetresourcemanager/1616280-writedata
Or this one:
https://developer.apple.com/documentation/photos/phimagemanager/1616981-requestexportsession
If you use the Mail app to email a video from your own device's photo library, you will actually see that happening; there is a pause with a progress bar while the video is exported, and then the email is constructed.
I download a .zip file from a URL in ViewControllerA, and put it in the documents directory using:
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .allDomainsMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("zipFile.zip")
When i am trying to retrieve the file from ViewControllerB and unzipping it using:
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .allDomainsMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("zipFile.zip")
do{
let file = try Zip.quickUnzipFile(destinationFileUrl)
}catch {
print("Error: \(error.localizedDescription)")
}
It is giving me an error:
Error: The operation couldn’t be completed. (Zip.ZipError error 1.)
But when i am trying it to do it in the same ViewController. i.e. If i am trying to download the file in ViewControllerA and unzip the file right away, it is working fine:
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .allDomainsMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("zipFile.zip")
Downloader.load(url: remoteURL, to: destinationFileUrl, completion: {
print("Downloaded.")
do{
let file = try Zip.quickUnzipFile(destinationFileUrl)
}catch {
print("Error: \(error.localizedDescription)")
}
})
Things i have verified:
Zip file exists in the document directory.
Zip file has valid file size.
Zip file has read and write permissions.
What is it that is preventing unzip process between two different ViewControllers?
First, the unzip operation should not be placed in the VC.In fact, it doesn't have a half - money relationship with VC.
You can make some changes in the code that you can run normally and make the method public, and then call this method to get data anywhere you need.
Downloader.load(url: remoteURL, to: destinationFileUrl, completion: {
print("Downloaded.")
do{
let file = try Zip.quickUnzipFile(destinationFileUrl)
completion(file)
}catch {
print("Error: \(error.localizedDescription)")
}
})
I found the problem. It was not because of two different View Controllers, it was a mistake of the static function. Since Downloader.load() was a static function, it was holding on to the file after downloading. That is why it was unable to parse the Zip file as it was unable to open it.
I changed the Downloader class load function to non-static:
let downloader: Downloader = Downloader()
downloader.load()
and it works fine now. Unfortunately the error generated by Zip was not descriptive and did not help me to figure out the problem. I created a simple playground application with two view controllers and studied why Zip worked there and not in my app. This was the only difference.
I want to create pre-seed database at the beginning
The file is quite large (5mb).
I use copyItemAtPath to copy files, so do this method has completion?
How do i know when this process has been finished?
This code is enough:
do {
// copy files from main bundle to documents directory
print("copy")
try
NSFileManager.defaultManager().copyItemAtPath(sourcePath, toPath: destinationPath)
} catch let error as NSError {
// Catch fires here, with an NSError being thrown
print("error occurred, here are the details:\n \(error)")
}
where destinationPath can be for example:
NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, .UserDomainMask, true).first
From How to show the progress of copying a large file in iOS?
Run your copying process in a seperate thread (T1)
Run another thread (T2) which reads periodically (say every 100ms)
the destination file current_size.
Calculate the percentage to display progress: current_size / total_size