Why do I get this error when I run the following code? :
"Internal Error" (1/1000); "No authToken received for asset"
I think it has something to do with the setObject code in the last line.
let documentsDirectoryPath:NSString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
var imageURL: URL!
let imageData = UIImageJPEGRepresentation(self.newImage, 1.0)
let path:String = documentsDirectoryPath.appendingPathComponent(self.newImage.description)
try? UIImageJPEGRepresentation(self.newImage, 1.0)!.write(to: URL(fileURLWithPath: path), options: [.atomicWrite])
imageURL = URL(fileURLWithPath: path)
try? imageData?.write(to: imageURL, options: [.atomicWrite])
let imageAsset:CKAsset? = CKAsset(fileURL: URL(fileURLWithPath: path))
curImages = record["Images"] as! [CKAsset]
curImages.append(imageAsset!)
print("saving image")
record.setObject(curImages as CKRecordValue?, forKey: "Images")
I've encountered this, too. It appears to be a bug in cloudkit, and--from what I can tell--it happens when you try to re-use any part of the "asset creation chain."
In other words, you have some initial data, you create an image from that data, you write it to a file, you load that file into a CKAsset, then you load the CKAsset into the CKRecrod. In my experiments, if you re-use any of those components... or if they just happen to be the same (that is, you create an image, then you happen to create a new-but-identical image later) you'll see this error.
For example, the following code reliably recreates the "no auth token" error when saving a record. All it does is create an array of assets and places it into the record:
for (int i = 0; i <= maxPlayers; i++)
{
int tempVal = 0xf;
NSData *tempData = [[NSData alloc] initWithBytes:&tempVal length:sizeof(tempVal)];
NSString *tempDataFilepath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"temp%d.dat",i]];
[tempData writeToFile:tempDataFilepath atomically:YES];
NSURL *tempDataURL = [NSURL fileURLWithPath:tempDataFilepath];
someArray[i] = [[CKAsset alloc] initWithFileURL:tempDataURL ];
}
someRecord[SOME_FIELD_NAME] = someArray;
Simply changing the third line to:
int tempVal = i; //force the temp value to be different every time
Completely solves the error.
Furthermore, this error occurs even when I tried to use a value in a different CKAsset **that was already used in a prior CKAsset For example, using int tempVal = 0xf in the first asset, then using int secondTempVal = 0xf in another CKAsset also produces the "no auth token" error.
In my case, I was able to force the asset value to always be a unique value, and completely solved the problem. In your case, I suggest the following possible work arounds:
Check if you're using identical images for your assets. If you are, try slightly modifying the images for each new CKAsset.
If you must re-use identical images, try saving the record after you set each asset. I do not know if that will solve the issue, and it certainly increases your network traffic. But it's worth an experiment to see if it helps.
In this question Saving CKAsset to CKRecord in CloudKit produces error: "No authToken received for asset" the OP was able to create separate copies of the image file that ultimately solved the problem.
Open a bug with Apple. I didn't bother doing this, as I've grown jaded watching similar bug reports sit open for years without attention. But who knows, you might have better luck.
This is not an answer to the specific problem (which as been solved by the accepted answer), but it solves another problem that creates the same error message, so it might be useful for somebody else:
I have an app that uses CoreData+Cloudkit with a .public database, i.e. my description for the NSPersistentCloudKitContainer uses
description.cloudKitContainerOptions!.databaseScope = .public
Whenever I change my CloudKit schema, I have to re-initialize it using
do {
try self.initializeCloudKitSchema()
} catch {
print("Could not initialize schema, error \(error)")
}
This creates the error
"Internal Error" (1/1000); "No authToken received for asset"
although I do not use any asset in my model.
I now realized that it has something to do with the .public database:
As soon as I out-comment the instruction that sets the database scope to .public, the re-initialization works without problems.
Now the CloudKit schema is independent of the database type (.private or .public). Thus, a re-initialization of the schema requires the following:
Set the database to .private (the default)
Execute the init code
Set the database to .public
Disable the init code
PS: I know I should write now a bug report, but I stopped doing so: Nearly none of my bug reports (about 15) have ever been answered or processed, so it is not worth the effort.
Related
Is it possible to compare to PFFiles?
I am trying to check whether a downloaded PFFile:
let imageFile = object["groupImage"] as PFFile
is equal to a mock data created by me like this:
let imageData = UIImagePNGRepresentation(UIImage(named: "dot.png")!)
uploadMock = PFFile(name: "mock", data: imageData!)
Now it happens when I call the comparison, it will not work.
if (mock?.isEqual(image))!{
print(true)
} else{
print(false)
}
will always give false, even though the images are the same.
It seems like it would be necessary, to download the image before. I tried to work around with checking the filename (it worked used to work until I transferred to another database).
Any ideas?
I decided to utilize Amazon S3 to upload files, however I find AWS Docs a bit confusing about S3 capabilities for iOS platform.
I would like to know how my app would act in the following scenarios:
Scenario 1: During the upload user has accidentally lost internet connection
Scenario 2: App crashes during the upload
I heard that iOS SDK takes care of such issues itself by resuming remaining upload when possible, I failed to find relevant information in docs, though.
Will AWSS3 framework cover both this scenarios? Does it need any additional lines od code to not be vulnerable for potential crashes and network errors?
I've found some relevant informations for Android platform
I'd love to know what can I expect from the following code
let image = UIImage(named: "12.jpeg")
let fileManager = FileManager.default
let imageData = UIImageJPEGRepresentation(image!, 0.99)
let path = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("\(imageData!).jpeg")
fileManager.createFile(atPath: path as String, contents: imageData, attributes: nil)
let fileUrl = NSURL(fileURLWithPath: path)
let uploadRequest = AWSS3TransferManagerUploadRequest()
uploadRequest?.bucket = "bucketname"
uploadRequest?.key = "folder/12.jpeg"
uploadRequest?.contentType = "image/jpeg"
uploadRequest?.body = fileUrl as URL!
uploadRequest?.serverSideEncryption = AWSS3ServerSideEncryption.awsKms
uploadRequest?.uploadProgress = { (bytesSent, totalBytesSent, totalBytesExpectedToSend) -> Void in
DispatchQueue.main.async(execute: {
print("bytes sent \(bytesSent), total bytes sent \(totalBytesSent), of total \(totalBytesExpectedToSend)")
})
}
let transferManager = AWSS3TransferManager.default()
transferManager?.upload(uploadRequest).continue(with: AWSExecutor.mainThread(), withSuccessBlock: { (taskk: AWSTask) -> Any? in
if taskk.error != nil {
// Error.
} else {
// Do something with your result.
}
return nil
})
Is it already crash/network proof?
EDIT:
This is the part of docs that sounds ambiguous to me:
S3 provides a multipart upload feature that lets you upload a single
object as a set of parts. Each part is a contiguous portion of the
object's data, and the object parts are uploaded independently and in
any order. If transmission of any part fails, you can retransmit that
part without affecting other parts. After all parts of the object are
uploaded, S3 assembles these parts and creates the object.
Does that mean it has its own inherent mechanism to manage that? Let's say I kill app during uploading a file, when I relaunch it and start over the upload process , will it start with the last chunk where it left off before I killed the app?
I have created a share extension for my ios app. When I click on the share option in the photo app my share extension is shown and I click on it and my controller is shown. Everything is working fine up to this. I am uploading video to youtube using the youtube api. I am using this method to create the parameter
GTLUploadParameters *uploadParameters = [GTLUploadParameters uploadParametersWithData:fileData MIMEType:#"video/*"];
Now if the video is small then it is easily converted to NSData using this code
NSData *fileData = [NSData dataWithContentsOfURL:[NSURL URLWithString:videoURL]];
and everything is working and video is uploaded.
But if the video is large then it simply crash and exit from the share extension(I put breakpoint and found this problem. If I remove the fileData conversion then its not crashing.). So what I did was instead of converting it to NSData I used this youtube api method
GTLUploadParameters *uploadParameters = [GTLUploadParameters uploadParametersWithFileURL:[NSURL URLWithString:videoURL] MIMEType:#"video/*"];
Now app is not crashing but I am getting network error. The error is
Error Domain=NSURLErrorDomain Code=-995 "(null)"
little searching found that it is because of NSURLSession and told to use something like this
sessionConfiguration.sharedContainerIdentifier = #“com.me.myapp.containerIdentifier”;
I am using youtube api. I am not sure where to use it OR is there any other way to use youtube api in share extension with large video file.
NOTE: I am using youtube api in my app and its working fine with NSData.
Hope question is clear. I am stuck on it for a day now. Please help.
Thanks in advance.
EDIT 1:
I used this code
NSData *fileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:videoURL] options:0 error:&error];
filedata is nil. The error I am getting is
Error Domain=NSCocoaErrorDomain Code=260 "The file “IMG_2187.MOV”
couldn’t be opened because there is no such file."
UserInfo={NSFilePath=/file:/var/mobile/Media/DCIM/102APPLE/IMG_2187.MOV,
Consider this line:
var videoDataURL = info[UIImagePickerControllerMediaURL] as! NSURL!
This does a forced unwrapping of info[UIImagePickerControllerMediaURL] (which is bad, because if it was nil, the app would crash) and that casts it as an implicitly unwrapped optional NSURL!. That doesn't make sense. Just do a conditional unwrapping (and unwrap to a NSURL, not a NSURL!):
if let videoDataURL = info[UIImagePickerControllerMediaURL] as? NSURL { ... }
The next line calls filePathURL:
var videoFileURL = videoDataURL.filePathURL
If you wanted a file URL, you already have one, so no conversion is needed, but instead just use videoDataURL. If you really wanted a path, you'd use path method:
let videoPath = videoDataURL.path
Frankly, Apple is trying to shift us away from using string paths, so just use the original videoDataURL and avoid the use of both path and filePathURL.
You are using dataWithContentsOfMappedFile:
var video = NSData.dataWithContentsOfMappedFile("\(videoDataURL)")
If you really wanted to use dataWithContentsOfMappedFile, the proper Swift syntax is:
let video = NSData(contentsOfMappedFile: videoPath!)
But dataWithContentsOfMappedFile deprecated, so you should instead use:
let video = try NSData(contentsOfFile: videoPath!, options: .DataReadingMappedIfSafe)
Or, bypassing that videoPath altogether, you could:
let video3 = try NSData(contentsOfURL: videoDataURL, options: .DataReadingMappedIfSafe)
Obviously, those try renditions should be done within a do block with a catch block.
By the way, as you'll see in all of my above examples, one should use let where possible.
Quite frankly, I would advise against loading it into a NSData at all. Just copy it with NSFileManager, which is a more efficient use of memory. If the video is long, it could be quite large, and you should avoid loading the whole thing into memory at any given point in time.
So you could:
if let videoDataURL = info[UIImagePickerControllerMediaURL] as? NSURL {
do {
// build your destination URL however you want
//
// let tempFolder = NSURL(fileURLWithPath: NSTemporaryDirectory())
// let destinationURL = tempFolder.URLByAppendingPathComponent("test.mov")
// or
let documents = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
let destinationURL = documents.URLByAppendingPathComponent("test.mov")
// but just copy from the video URL to the destination URL
try NSFileManager.defaultManager().copyItemAtURL(videoDataURL, toURL: destinationURL)
} catch {
print(error)
}
}
If you're uploading this to a web service, you'd then use a NSURLSessionUploadTask, using file or stream options. The construction of this request is a separate question, but hopefully you get the idea: With large assets like photos or, especially, videos, don't instantiate a NSData with the asset if you can possibly avoid it.
Please try this if your file exist in your phone instead of [NSURL URLWithString:videoURL].
NSData *fileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:videoURL]];
In an iOS app, how can I cache an image with specified expiry age? There are examples on how to store and retrieve images, but how can I set an expiry period to auto delete old images?
As indicated by Fahri, you will need to manage the cache yourself (or using an open source library). You could easily create a cache directory to store your images. Then, at application launch, you parse this image cache directory to check image creation date, check time elapsed and remove those older than the specified age.
The below Swift code will do this parsing/removing job, I set the specified age to 30,000 (seconds)
// We list the stored images in Caches/Images and delete old ones
let cacheDirectory = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first! as NSURL
let filelist = try? filemanager.contentsOfDirectoryAtPath(cacheDirectory.path!)
var newDir = cacheDirectory.URLByAppendingPathComponent("Images")
var properties = [NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey]
var URLlist = try? filemanager.contentsOfDirectoryAtURL(newDir, includingPropertiesForKeys: properties, options: [])
if URLlist != nil {
for URLname in URLlist! {
let filePath = URLname.path!
let attrFile: NSDictionary? = try? filemanager.attributesOfItemAtPath(filePath)
let createdAt = attrFile![NSFileCreationDate] as! NSDate
let createdSince = fabs( createdAt.timeIntervalSinceNow )
#if DEBUG
print( "file created at \(createdAt), \(createdSince) seconds ago" )
#endif
if createdSince > 30000 {
let resultDelete: Bool
do {
try filemanager.removeItemAtPath(filePath)
resultDelete = true
} catch _ {
resultDelete = false
}
#if DEBUG
print("purging file =\(filePath), result= \(resultDelete)")
#endif
}
}
}
Web is web, iOS is iOS. If you want to create image cache with expiration, you have to implement it yourself, or use open source lib. I can give you the idea, it's not hard to implement. So, in addition to storing and retrieving functionality, you also need to add metadata management methods, using which you could know when the image was added, and what's the expiration date for that image, and when some events occur (app become active, going to background etc.) you should check the meta for your images, and delete the image if the expiration date passed. That's it, nothing hard. Good luck!
P.S.: In some git source projects I have seen the functionality your are looking for, check DFCache on github, maybe it suits your needs.
I am monitoring my iCloud sandbox (iOS) using an NSMetaDataQuery are recommended - and all is working well.
I'm attempting to use the NSMetadataQueryUpdateChangedItemsKey in the NSMetadataQueryDidUpdateNotification in order to efficiently update my internal model of the file system. Challenge I have is that when a file is moved/renamed, how can I know the original file path - so I can update my model?
It appears that the NSMetaDataItem objects are persistent (i.e. the same object instance is updated when the path changes) so I could use the pointer value as a kind of index into my model. However - I'd be taking advantage of an apparent implementation detail (which could change.) Perhaps NSMetaDataItems are recycled when memory runs low?
Anyone know how this should be done (or if it is actually the case that NSMetaDataItem objects persist for the lifetime of the NSMetaDataQuery - and stay 'attached' to the same file system item.)
Yes, the NSMetadataQuery doesn't provide a way to consult the previous path.
When an item is moved, its index in the NSMetadataQuery results remains the same. So we can duplicate the path of the results and when the update kicks in, we only need to check the NSMetadataItem at the exact position of the duplicated array.
if let updatedObj = obj.userInfo?[NSMetadataQueryUpdateChangedItemsKey] as! [NSMetadataItem]? {
for it in updatedObj {
let url = it.valueForAttribute(NSMetadataItemURLKey) as! NSURL
let value = it.valueForAttribute(NSMetadataUbiquitousItemIsUploadedKey) as! NSNumber
print("Path: " + url.path!)
print("Updated: " + value.stringValue)
let index = metaDataQuery.indexOfResult(it)
let prevPath = duplicatedPathArray[index]
if (prevPath != url.path!) {
print("File Moved. Previous path: " + prevPath)
duplicatePath()
}
}
}
Make sure you update the array each time a file is added or removed.
Documentation mentions that results are suitable for Cocoa Bindings, which means that most likely those objects are persistent.
I use more hardcore combination of NSFilePresenter and NSMetadataQuery running side by side to monitor documents in container. NSFilePresenter has convenient API for detecting when files were being moved:
func presentedSubitem(at oldURL: URL, didMoveTo newURL: URL)
For that to work though when you move files in container you have to explicitly notify file coordinator that you're moving file (see points 1-3):
let fc = NSFileCoordinator()
var error: NSError?
fc.coordinate(writingItemAt: from, options: .forMoving, writingItemAt: to, options: .forReplacing, error: &error, byAccessor: {
(fromURL, toURL) in
do {
// 1
fc.item(at: fromURL, willMoveTo: toURL)
try FileManager.default.moveItem(at: fromURL, to: toURL)
// 2
fc.item(at: fromURL, didMoveTo: toURL)
} catch {
// 3
fc.item(at: fromURL, didMoveTo: fromURL)
}
})