My question is rather simple.
After integrating Google Drive REST API I could finally download files from my Google Drive.SO I have followed this example
https://developers.google.com/drive/v3/web/manage-downloads
Everything is working fine,and in console I get the information that the file is downloaded. So here is the question:where it goes,to which folder ? What is the path to where the file is saved?
And how do I set the necessary save path if I need to save a file to iOS Documents folder?
NSString *fileId = #"0BwwA4oUTeiV1UVNwOHItT0xfa2M";
GTLRQuery *query = [GTLRDriveQuery_FilesGet queryForMediaWithFileId:fileId];
[driveService executeQuery:query completionHandler:^(GTLRServiceTicket *ticket,
GTLRDataObject *file,
NSError *error) {
if (error == nil) {
NSLog(#"Downloaded %lu bytes", file.data.length);
} else {
NSLog(#"An error occurred: %#", error);
}
}];
I a developing for iOS 10 and an above and as I read the infrmation:NOTE: Because NSURLConnection is deprecated as of iOS 9 and OS X 10.11, this class has been superseded by GTMSessionFetcher.I should use either code provided by google(posted above ) or GTMSessionFetcher.I am using code from google.But Will be thankful is someone will help me solve my problem with both (google and GTMSessionFetcher) variants.
UPD: For Google API Client lib
GTLRDataObject has data property which contains raw bytes of your file. It is enough to safe your file using standard iOS methods.
There is also contentType property which is a string with MIME type of the file. You probably want to store this information somewhere along with the path to saved data, it will help you to properly display what kind of file (image/song/text) it is, as well as use it when you decode/open/read saved data to present actual information.
GTLRQuery *query = [GTLRDriveQuery_FilesGet queryForMediaWithFileId:fileId];
[driveService executeQuery:query completionHandler:^(GTLRServiceTicket *ticket,
GTLRDataObject *file,
NSError *error) {
if (error == nil) {
NSLog(#"Downloaded %lu bytes", file.data.length);
// Here is where you take these bytes and save them as file to any location you want (typically your Documents folder)
NSURL *documentsDirectoryURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
[file.data writeToURL:documentsDirectoryURL atomically:YES];
// Now you can store the url/path of the saved file somewhere along with the MIME type string (maybe new class or structure describing the file in your app)
MyFile *file = [[MyFile alloc] initWithFilePath:[documentsDirectoryURL path] mimeType:contentType];
} else {
NSLog(#"An error occurred: %#", error);
}
}];
See CocoaDocs for GTLRDataObject reference.
Generic version: using iOS SDK
If you use NSURLSession, it has delegate method which you use to move file from temporary location to anywhere you need.
Obj-C
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
// Use `location` to move your data to Documents directory or wherever else
}
Swift
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// Use `location` to move your data to Documents directory or wherever else
}
Related
i´m screen recording with ReplayKit and when the delegate method previewController(_:didFinishWithActivityTypes:) is called when i click the save button, it automatically saves into the camera roll. How can i change that? I want to save the video somwhere on the filesystem on device. I searched all over google and in apple documentation, but i couldn´t find anything relating to that.
If your app supports the app FILES, the replay kit can save file directly to the folder. For example VLC app has a folder in FILES. Otherwise you may need to implement an action extension to do the similar thing.
use this API
- (void)startCaptureWithHandler:(nullable void (^)(CMSampleBufferRef sampleBuffer, RPSampleBufferType bufferType, NSError *_Nullable error))captureHandler completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler API_AVAILABLE(ios(11.0), tvos(11.0), macos(11.0));
sample code:
-(void)startCapture {
if (#available(iOS 11.0, *)) {
[[RPScreenRecorder sharedRecorder] startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
if (CMSampleBufferDataIsReady(sampleBuffer) && bufferType == RPSampleBufferTypeVideo) {
NSLog(#"Recording started successfully.");
//save using AVAssetWriter
}
} completionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(#"Recording started successfully.");
}else{
NSLog(#"Recording started error %#",error);
}
}];
} else {
// earlier versions issue
}
}
Also: this post may help u somehow
AVAssetWriter Doc
My app offers the option to download 3430 high resolution images from our server, each image of size 50k - 600k bytes.
The original approach was to just download all of them - but we realized that gave a lot of NSURLErrorTimedOut errors and crashed our program. We then implemented it such that we download all of the images, but in batches of 100 images at a time. Someone on SO suggested we actually implement our download like this:
Create a list of all file URLs that need to be downloaded.
Write your code so that it downloads these URLs sequentially. I.e. do
not let it start downloading a file until the previous one has
finished (or failed and you decided to skip it for now).
Use NSURLSession's support for downloading an individual file to a
folder, don't use the code to get an NSData and save the file
yourself. That way, your application doesn't need to be running while
the download finishes.
Ensure that you can tell whether a file has already been downloaded or
not, in case your download gets interrupted, or the phone is restarted
in mid-download. You can e.g. do this by comparing their names (if
they are unique enough), or saving a note to a plist that lets you
match a downloaded file to the URL where it came from, or whatever
constitutes an identifying characteristic in your case.
At startup, check whether all files are there. If not, put the missing
ones in above download list and download them sequentially, as in #2.
Before you start downloading anything (and that includes downloading
the next file after the previous download has finished or failed), do
a reachability check using the Reachability API from Apple's
SystemConfiguration.framework. That will tell you whether the user has
a connection at all, and whether you're on WiFi or cellular (in
general, you do not want to download a large number of files via
cellular, most cellular connections are metered).
We create a list of all images to download here:
- (void)generateImageURLList:(BOOL)batchDownloadImagesFromServer
{
NSError* error;
NSFetchRequest* leafletURLRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription* leafletURLDescription = [NSEntityDescription entityForName:#"LeafletURL" inManagedObjectContext:managedObjectContext];
[leafletURLRequest setEntity:leafletURLDescription];
numberOfImages = [managedObjectContext countForFetchRequest:leafletURLRequest error:&error];
NSPredicate* thumbnailPredicate = [NSPredicate predicateWithFormat:#"thumbnailLocation like %#", kLocationServer];
[leafletURLRequest setPredicate:thumbnailPredicate];
self.uncachedThumbnailArray = [managedObjectContext executeFetchRequest:leafletURLRequest error:&error];
NSPredicate* hiResPredicate = [NSPredicate predicateWithFormat:#"hiResImageLocation != %#", kLocationCache];
[leafletURLRequest setPredicate:hiResPredicate];
self.uncachedHiResImageArray = [managedObjectContext executeFetchRequest:leafletURLRequest error:&error];
}
We use NSURLSession to download an individual image to a folder by calling hitServerForUrl and implementing didFinishDownloadingToURL:
- (void)hitServerForUrl:(NSURL*)requestUrl {
NSURLSessionConfiguration *defaultConfigurationObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigurationObject delegate:self delegateQueue: nil];
NSURLSessionDownloadTask *fileDownloadTask = [defaultSession downloadTaskWithURL:requestUrl];
[fileDownloadTask resume];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
if (isThumbnail)
{
leafletURL.thumbnailLocation = kLocationCache;
}
else
{
leafletURL.hiResImageLocation = kLocationCache;
}
// Filename to write to
NSString* filePath = [leafletURL pathForImageAtLocation:kLocationCache isThumbnail:isThumbnail isRetina:NO];
// If it's a retina image, append the "#2x"
if (isRetina_) {
filePath = [filePath stringByReplacingOccurrencesOfString:#".jpg" withString:#"#2x.jpg"];
}
NSString* dir = [filePath stringByDeletingLastPathComponent];
[managedObjectContext save:nil];
NSError* error;
[[NSFileManager defaultManager] createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:&error];
NSURL *documentURL = [NSURL fileURLWithPath:filePath];
NSLog(#"file path : %#", filePath);
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
//Remove the old file from directory
}
[[NSFileManager defaultManager] moveItemAtURL:location
toURL:documentURL
error:&error];
if (error){
//Handle error here
}
}
This code calls loadImage, which calls `hitServer:
-(void)downloadImagesFromServer{
[self generateImageURLList:NO];
[leafletImageLoaderQueue removeAllObjects];
numberOfHiResImageLeft = [uncachedHiResImageArray count];
for ( LeafletURL* aLeafletURL in uncachedHiResImageArray)
{
//// Do the same thing again, except set isThumb = NO. ////
LeafletImageLoader* hiResImageLoader = [[LeafletImageLoader alloc] initWithDelegate:self];
[leafletImageLoaderQueue addObject:hiResImageLoader]; // do this before making connection!! //
[hiResImageLoader loadImage:aLeafletURL isThumbnail:NO isBatchDownload:YES];
//// Adding object to array already retains it, so it's safe to release it here. ////
[hiResImageLoader release];
uncachedHiResIndex++;
NSLog(#"uncached hi res index: %ld, un cached hi res image array size: %lu", (long)uncachedHiResIndex, (unsigned long)[uncachedHiResImageArray count]);
}
}
- (void)loadImage:(LeafletURL*)leafletURLInput isThumbnail:(BOOL)isThumbnailInput isBatchDownload:(BOOL)isBatchDownload isRetina:(BOOL)isRetina
{
isRetina_ = isRetina;
if (mConnection)
{
[mConnection cancel];
[mConnection release];
mConnection = nil;
}
if (mImageData)
{
[mImageData release];
mImageData = nil;
}
self.leafletURL = leafletURLInput;
self.isThumbnail = isThumbnailInput;
NSString* location = (self.isThumbnail) ?leafletURL.thumbnailLocation :leafletURL.hiResImageLocation;
//// Check if the image needs to be downloaded from server. If it is a batch download, then override the local resources////
if ( ([location isEqualToString:kLocationServer] || (isBatchDownload && [location isEqualToString:kLocationResource])) && self.leafletURL.rawURL != nil )
{
//NSLog(#"final loadimage called server");
//// tell the delegate to get ride of the old image while waiting. ////
if([delegate respondsToSelector:#selector(leafletImageLoaderWillBeginLoadingImage:)])
{
[delegate leafletImageLoaderWillBeginLoadingImage:self];
}
mImageData = [[NSMutableData alloc] init];
NSURL* url = [NSURL URLWithString:[leafletURL pathForImageOnServerUsingThumbnail:self.isThumbnail isRetina:isRetina]];
[self hitServerForUrl:url];
}
//// if not, tell the delegate that the image is already cached. ////
else
{
if([delegate respondsToSelector:#selector(leafletImageLoaderDidFinishLoadingImage:)])
{
[delegate leafletImageLoaderDidFinishLoadingImage:self];
}
}
}
Currently, I'm trying to figure out how to download the images sequentially, such that we don't call hitServer until the last image is finished downloading. Do I need to be downloading in the background? Thank you for suggestions!
My app offers the option to download 3430 high resolution images from our server, each image of size 50k - 600k bytes.
This seems like a job for on-demand resources. Just turn these files into on-demand resources obtained from your own server, and let the system take care of downloading them in its own sweet time.
This sounds very much like an architectural issue. If you fire off downloads without limiting them of course you're going to start getting timeouts and other things. Think about other apps and what they do. Apps that give the user the ability to do multiple downloads often limit how may can occur at once. iTunes for example can queue up thousands of downloads, but only runs 3 at a time. Limiting to just one at a time will only slow things down for your users. You need a balance that consider your user's available bandwidth.
The other part of this is to again consider what your users want. Does every one of your uses want every single image? I don't know what you are offering them, but in most apps which access resources like images or music, it's up to the user what and when they download. Thus they only download what they are interested in. So I'd recommend only downloading what the users are viewing or have somehow requested they want to download.
I am working on google client Api V3 for uploading files from my IOS Application.
The Scopes I have used are
NSArray *scopes = [NSArray arrayWithObjects:kGTLAuthScopeDrive,kGTLAuthScopeDriveFile,kGTLAuthScopeDriveAppdata,kGTLAuthScopeDriveMetadata, nil];
The code I used for uploading a new file is
GTLUploadParameters *uploadParameters = [GTLUploadParameters uploadParametersWithData:data MIMEType:file.mimeType];
GTLQuery * query = [GTLQueryDrive queryForFilesCreateWithObject:metadata uploadParameters:uploadParameters];
GTLServiceTicket *uploadTicket=[self.gogService executeQuery:query completionHandler:^(GTLServiceTicket *ticket,
GTLDriveFile *updatedFile,
NSError *error) {
if (error == nil) {
NSLog(#"File WebContent Link %#", [updatedFile webViewLink]);
}
else {
NSLog(#"An error occurred: %#", [error localizedDescription]);
}
}];
[uploadTicket setUploadProgressBlock:^(GTLServiceTicket *ticket, unsigned long long totalBytesWritten, unsigned long long totalBytesExpectedToWrite)
{
progressBar.progress=(1.0*totalBytesWritten/(1.0*totalBytesExpectedToWrite)) ;
NSLog(#"%llu",totalBytesWritten);
NSLog(#"%llu",totalBytesExpectedToWrite);
}];
The problem is web content link is null and want it in response to store it in my database because my web App will use this link to open that file.
Previous version of google drive api was returning web content link.
How can I get Web Content Link?
Check this documentation: Migrate to Google Drive API v3. Be noted that full resources are no longer returned by default. Use the fields query parameter to request specific fields to be returned. If left unspecified only a subset of commonly used fields are return. You may check this sample for reference.
I'm trying to start a new GKGameSession and when I use createSession, all I can get so far is nil. Here's my code:
GKGameSession.createSession(inContainer: "test", withTitle: "MyGame", maxConnectedPlayers: 8)
{ (newGameSession, error) in
self.gameSession = newGameSession
print("\(newGameSession)")
newGameSession?.getShareURL(completionHandler: { (url, error) in
print("url: \(url) error: \(error)")
})
}
The only thing it prints is "nil". Any help is appreciated.
If you using the emulator, I suggest using a device instead. GKGameSessions do not play well with the emulator, because they depend on push notifications and an iCloud account that is logged in.
newGameSession is optional. So it seems like something has gone wrong when creating a new session.
I would say newGameSession is likely nil, in that case error will hopefully contain some useful information.
Try replacing print("\(newGameSession)") with print(newGameSession, error) to see what the error var has to say, or set a breakpoint if you know how to do that.
Try to use your app's iCloud container name instead of "test". The container name will be in the format iCloud.com.yourcompany.appname if you have selected the default container option. To ensure your app has an iCloud container you need to enable it in your app's Capabilities.
I'm a jumping in a little late, but I'd triple check the container name you're using to create the session.
I enabled all three options in xCode: key-value storage, iCloud documents and CloudKit.
I don't use iCloud drive.
The following code successfully creates a new session each time I send invites. It prints the new session, then iterates through all existing sessions for that container ID.
-(void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match
{
[self dismissViewControllerAnimated:YES completion:nil];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
NSString *iCloudContainerName = [#"iCloud." stringByAppendingString: bundleIdentifier];
[GKGameSession createSessionInContainer:iCloudContainerName
withTitle:#"test"
maxConnectedPlayers:4
completionHandler:^(GKGameSession * _Nullable session, NSError * _Nullable error)
{
NSLog(#"(1) Session: %#, Error: %#", session.identifier, [error description]);
[GKGameSession loadSessionsInContainer:iCloudContainerName
completionHandler:^(NSArray<GKGameSession *> * _Nullable sessions, NSError * _Nullable error)
{
for (GKGameSession *session in sessions)
{
NSLog(#"(2) Session: %#, Error: %#", session.identifier, [error description]);
}
NSLog(#"-----");
}];
}];
}
So I am trying to allow two users to swap files that each has in their Google Drive. That involves knowing the ID of the other person's file and using the API calls to retrieve it. Both files sit in folders that have been shared to anyone/public.
Trouble is when I execute the code below I am finding that each user can only use the downloadUrl corresponding to the file they own - the others return a 404. In this case either "mine" or "theirs" depending on the account I'm logged into.
// _driveService and its authorizer setup elsewhere
// Retrieve the metadata then the actual data
NSString *mine = #"0B4Pba9IBDsR3T1NVTC1XSGJTenc";
NSString *theirs = #"0B4n9OyY8tqWpNlNaN1dUc3FsNG8";
NSString *get = [NSString stringWithFormat:#"https://www.googleapis.com/drive/v2/files/%#",theirs];
[_driveService fetchObjectWithURL:[NSURL URLWithString:get] completionHandler:^
(GTLServiceTicket *ticket, GTLDriveFile *file, NSError *error)
{
if (error != nil)
NSLog(#"Error retrieving metadata: %#", error);
else
{
// Download the actual data
GTMHTTPFetcher *fetcher = [_driveService.fetcherService fetcherWithURLString:file.downloadUrl];
[fetcher beginFetchWithCompletionHandler:^
(NSData *data, NSError *error)
{
if (error != nil)
NSLog(#"Error retrieving actual data: %#", error);
else
{
NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Content: %#", content);
}
}];
}
}];
Error retrieving actual data: Error Domain=com.google.HTTPStatus Code=404 "The operation couldn’t be completed. (com.google.HTTPStatus error 404.)"
What am I doing wrong here? If it's a permissions thing, why am I allowed to get the metadata?
Note this is for an iOS app and both files were created and uploaded from the app using the official client API (rev 353).
Hah, so it seems the devil is in the detail I left out of the question. When creating the authorizer the scope I was providing is kGTLAuthScopeDriveFile, which was the default in an example and I forgot all about it when everything else thus far worked fine. Apparently I need to use kGTLAuthScopeDrive instead (the differences are explained here)
The logic seems a bit flawed here though, I mean I don't want access to other files that weren't created with the app, I just want access to a public file somebody else created with the app...