I am building an app that allows users to select a file and/or folder either locally or across the network and list the contents of that selection in a NSTableView after some filtering (no hidden files, only accepting .tif, .eps). The user can then select a file name from the list and then have the files metadata shown to them. At least that is what I want to happen. Right now I am getting null returned for the metadata. Here's my code:
- (void)tableViewSelectionDidChange:(NSNotification *)notif {
NSDictionary* metadata = [[NSDictionary alloc] init];
//get selected item
NSString* rowData = [fileList objectAtIndex:[tblFileList selectedRow]];
//set path to file selected
NSString* filePath = [NSString stringWithFormat:#"%#/%#", objPath, rowData];
//declare a file manager
NSFileManager* fileManager = [[NSFileManager alloc] init];
//check to see if the file exists
if ([fileManager fileExistsAtPath:filePath] == YES) {
//escape all the garbage in the string
NSString *percentEscapedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)filePath, NULL, NULL, kCFStringEncodingUTF8);
//convert path to NSURL
NSURL* filePathURL = [[NSURL alloc] initFileURLWithPath:percentEscapedString];
NSError* error;
NSLog(#"%#", [filePathURL checkResourceIsReachableAndReturnError:error]);
//declare a cg source reference
CGImageSourceRef sourceRef;
//set the cg source references to the image by passign its url path
sourceRef = CGImageSourceCreateWithURL((CFURLRef)filePathURL, NULL);
//set a dictionary with the image metadata from the source reference
metadata = (NSDictionary *)CGImageSourceCopyPropertiesAtIndex(sourceRef,0,NULL);
NSLog(#"%#", metadata);
[filePathURL release];
} else {
[self showAlert:#"I cannot find this file."];
}
[fileManager release];
}
I'm guessing the problem here is the CFURLREF in CGImageSourceCreateWithURL. Instead of NSURL should I be using something else?
Thanks
Here's the path I am passing (logged from filePathURL): file://localhost/Volumes/STORAGE%20SVR/Illustration-Wofford/Illustration%20Pickup/Archive/AL013111_IL_Communication.eps
I can't tell where the "localhost" part in your file URL comes from, but I think that's the culprit. A file url doesn't usually contain a "localhost" part. In your example, it should look like this:
file:///Volumes/STORAGE%20SVR/Illustration-Wofford/Illustration%20Pickup/Archive/AL013111_IL_Communication.eps
But I'm pretty sure you've figured this out by now :)
Update: I stand corrected by Mike's comment: file://localhost/... is the same thing as file:///...!
Related
-- a question about how to make an object that is saved to the documents directory persist on the drive and be recoverable after the iDevice is rebooted.
Here's my problem. I make a data object with NSCoding and fill it with data. I write it to the documentsDirectory each time the data in the object are updated. I stop the app and start the app again, and my data object persists, with all of its data. But if I reboot the iPhone the code I wrote to recover and read the data object fails.
The code I wrote originally used only a NSString for the file path. It worked well under ios7 but it fails under ios8.
Reading up on things, I found this clue from the Apple documentation:
"Important: Although they are safe to use while your app is running, file reference URLs are not safe to store and reuse between launches of your app because a file’s ID may change if the system is rebooted. If you want to store the location of a file persistently between launches of your app, create a bookmark as described in Locating Files Using Bookmarks."
So I rewrote my ios7 file open and file close methods so they no longer use strings or urls but get their strings and urls from a bookmark that is saved using NSUserDefaults. Same problem: everything works fine so long as I do not power off the phone, but all is lost once I do. I am not able to solve this.
Here is my current series of steps. First I either determine (or if it already exists in NSUsrDefaults, I recover) the absolute path to the documentsDirectory, using a bookmark:
+ (NSString*) getGeoModelAbsolutePath
{
NSString *path;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSURL *documentsDirectoryBookmarkURL;
NSData* documentsDirectoryBookmark = [userDefaults objectForKey:#"documentDirectoryBookmark"];
if(documentsDirectoryBookmark == nil)
{
documentsDirectoryBookmarkURL = [self getDocumentsDirectoryURL];
documentsDirectoryBookmark = [self bookmarkForURL:documentsDirectoryBookmarkURL];
}
documentsDirectoryBookmarkURL = [self urlForBookmark:documentsDirectoryBookmark];
path = documentsDirectoryBookmarkURL.path;
path = [path stringByAppendingString:#"/Model.mod"];
return path;
}
using methods modified from my ios7 code (which used only the getDocumentsDirectory method):
+ (NSString *)getDocumentsDirectory
{
NSURL *directory = [self getDocumentsDirectoryURL];
NSString * documentsDirectory = directory.path;
return documentsDirectory;
}
And
+ (NSURL *)getDocumentsDirectoryURL
{
NSURL *directory = [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask]
lastObject];
return directory;
}
And
+ (NSData*)bookmarkForURL:(NSURL*)url {
NSError* theError = nil;
NSData* bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationSuitableForBookmarkFile
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&theError];
if (theError || (bookmark == nil)) {
// Handle any errors.
return nil;
}
return bookmark;
}
So now I have a NSString path with the model filename that I can use to get to the GeoModel
- (GeoModel*) openGeoModel
{
GeoModel *geoModel;
NSString* documentsDirectoryGeoModel =[FileManager getGeoModelAbsolutePath];
if([FileManager fileExistsAtAbsolutePath:documentsDirectoryGeoModel])
{
NSData* data = [NSData dataWithContentsOfFile: documentsDirectoryGeoModel]; //]documentsDirectoryGeoModel];
geoModel = [NSKeyedUnarchiver unarchiveObjectWithData: data];
NSString *unarchivedGeoModelVersion = geoModel.geoModel_VersionID;
if(![unarchivedGeoModelVersion isEqual: currentGeoModelVersion])
{
[FileManager deleteFile:documentsDirectoryGeoModel];
geoModel = [GeoModel geoModelInit];
[Utilities setGeoProjectCounter:0];
}
}
else
{
geoModel = [GeoModel geoModelInit];
}
[FileManager saveGeoModel];
return geoModel;
}
Which I then can save to the documentsDirectory as follows:
+ (BOOL)saveGeoModel
{
NSError *error = nil;
NSString *path = [self getGeoModelAbsolutePath];
[NSKeyedArchiver archiveRootObject:appDelegate.currentGeoModel toFile:path];
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: appDelegate.currentGeoModel];
BOOL success = [encodedData writeToFile: path options:NSDataWritingAtomic error:&error];
return success;
}
Which is always successful -- but is persistent only if I do not turn off the device! I am not making any progress with this: Any help would be much appreciated!
Thanks in advance
Tim Redfield
There. I think it is answered -- unless someone else has a comment on how to improve the above listings, they DO work as they ought to!
i have this code working on IOS7, its check if an image exist in device and if not, it download locally.
Now on IOS8 doesnt save nothing, could someone help me?
//folder where save
NSString *ImagesPath = [[NSString alloc] initWithString:[[[[NSBundle mainBundle] resourcePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:#"Documents"]];
// check if image exist
NSString* foofile = [ImagesPath stringByAppendingPathComponent:nombreImagenLocal];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
// check if image exist locally
if (!fileExists){
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:rutaCompletaLogo]];
//if not, i save it
if (data) {
// url where is saved
NSString *cachedImagePath = [ImagesPath stringByAppendingPathComponent:nombreImagenLocal];
if ([data writeToFile:cachedImagePath atomically:YES]) {
NSLog(#"Downloaded file saved to: %#", cachedImagePath);
}// end
The path to 'Documents' folder has changed from iOS8. Check the Apple tech note
Please, make sure you don't use hardcoded values. Use the methods provided by the API:
NSString *resourcePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
OR (as stated in the previous link)
// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
Hope this helps!
I trying to record a video and I'm getting the error like
Cannot record to URL <#file url> because it is not a file URL.
I define the destination url as follows:
NSString *Path = [[NSString alloc] init];
Path = #"/Users/me/Documents/My fols/recording_try/newMovie.mov";
NSURL *dest = [[NSURL alloc] initWithString:[Path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
Then after creating the session, input and output objects. I tried recording like this.
mMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init] ;
[mSession addOutput:mMovieFileOutput];
[mMovieFileOutput startRecordingToOutputFileURL:dest recordingDelegate:self];
I have started running the session, tried using begin and comitconfiguration, etc. But every time I run I get an error like:
[AVCaptureMovieFileOutput startRecordingToOutputFileURL:recordingDelegate:] - Cannot record to URL /Users/me/Documents/My%20fols/recording_try/newMovie.mov because it is not a file URL.
I don't know where I'm going wrong... Could someone please help???
Thanks in Advance...
Just change your NSURL configuration to conform to file URL type
Something like this:
NSURL *dest = [[NSURL alloc] initFileURLWithPath:<#(NSString *)#>]
Try something like this:
// variable names should start with lower case letters
// also, let's do as much as we can with auto-released objects
// so we don't have to worry about leaking (if we're not using ARC)
NSString *path = [NSString stringWithString: #"/Users/me/Documents/My fols/recording_try"];
NSURL *dest = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
if(dest == nil)
{
NSLog( #"does not appear to be a valid NSURL object" );
return;
}
NSError * error = nil;
if([[NSFileManager defaultManager] createDirectoryAtURL: dest withIntermediateDirectories: YES attributes: nil error: &error] == YES)
{
dest = [dest URLByAppendingPathComponent: #"newMovie.mov"];
// now you can create the session plus input and output objects
// within this block
} else {
NSLog( #"was not able to create the directory which contains path %# - error is %#", path, [error localizedDescription] );
}
I want to cache an image I download from a URL in the local filesystem.
What I don't know, is how can I create from the URL a NSString that is compatible with the characters the iOS file system supports. For instance, if I try to create a file with a : in the name, it will fail.
What steps should I follow to create this NSString? Is a simple hash the best way to go? If so, what hash routine is available in iOS that I can use?
I'd use hashing:
it'll make for much more readable file names
you avoid problems with file name length
md5 should be perfectly fine for your purposes. Unfortunately, the ios5-sdk contains only a C-String function for this:
CC_MD5(in, in_len, out);
The function is contained in <CommonCrypto/CommonDigest.h>, there are other hash functions there, too.
You can find infos on how to wrap this up in a function that takes/returns an NSString here.
The following code belongs to ZDS_Shared. resolveLocalURLForRemoteURL accepts a remote URL and returns a URL pointing to the file on the iOS filesystem. The filename will be an alphanumeric string that doesn't resemble the original URL, but that shouldn't be a problem.
https://github.com/ZarraStudios/ZDS_Shared/blob/master/ZSAssetManager.m#L185
- (NSURL*)resolveLocalURLForRemoteURL:(NSURL*)url
{
if (!url) return nil;
NSString *filename = [[url absoluteString] zs_digest];
NSString *filePath = [[self cachePath] stringByAppendingPathComponent:filename];
return [NSURL fileURLWithPath:filePath];
}
https://github.com/ZarraStudios/ZDS_Shared/blob/master/NSString%2BZSAdditions.m#L38
// NSString category
- (NSString*)zs_digest
{
const char *cstr = [self cStringUsingEncoding:NSASCIIStringEncoding];
return [[NSData dataWithBytes:cstr length:strlen(cstr)] zs_digest];
}
https://github.com/ZarraStudios/ZDS_Shared/blob/master/NSData%2BZSAdditions.m#L38
// NSData category
- (NSString*)zs_digest
{
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([self bytes], [self length], digest);
NSMutableString* outputHolder = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[outputHolder appendFormat:#"%02x", digest[i]];
}
NSString *output = [outputHolder copy];
MCRelease(outputHolder);
return [output autorelease];
}
This code copies the referenced file and places it in the Docs Directory. I'm trying to build a simple backup solution. The problem is this operation does not overwrite the existing file if the operation is repeated.
Two questions:
What's the best way to overwrite in code?
How difficult would it be to append the current date to each copied file? In this case there would be no overwrite operation. This would be much more useful for keeping incremental backups. If I decide to do it this way I understand I would need to create a new path in order to keep things organized.
Thanks.
Paul
NSString * name = #"testFile";
NSArray * files = [NSArray arrayWithObject: name];
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
[ws performFileOperation: NSWorkspaceCopyOperation
source: #"~/Library/Application Support/testApp"
destination: #"~/Documents/"
files: files
tag: 0];
You could try using NSFileManager, example below (untested):
// Better way to get the Application Support Directory, similar method for Documents Directory
- (NSString *)applicationSupportDirectory {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory();
return [basePath stringByAppendingPathComponent:#"testApp"];
}
- (void) removeFile {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *applicationSupportDirectory = [self applicationSupportDirectory];
NSError *error = nil;
NSString* filePath = [applicationSupportDirectory stringByAppendingPathComponent: #"testFile"];
if ([fileManager fileExistsAtPath:filePath isDirectory:NULL]) {
[fileManager removeItemAtPath:filePath error:&error];
}
}
Edit:
Take a look at the NSFileManager Class Reference for other functions that might be useful (for your second question).