0

My app has a drafts table view that shows audio files the user saved. For the data source array, I loop through the drafts directory (inside NSDocumentDirectory). The code works fine, and it shows all the files I've saved so far.

The problem is, just recently, all the files besides the first two are empty. By empty, I mean this: when I use NSData's datatWithContentsOfFile method data length is 0, i.e. 0 bytes. The first two files still have data, around 267639 b each.

But if there is no data in the file, why would it appear when I loop through the drafts directory? These empty files were intact until just recently. What could have caused them to become empty?

Below is the code used to loop through the drafts directory.

// check if drafts folders exist
BOOL draftsFoldersExist = NO;
NSError *error = nil;
NSArray *folders = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSArray *appFolderContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[folders objectAtIndex:0] error:&error];
if ([appFolderContents count] > 0 && error == nil) {
    for (NSString *item in appFolderContents) {
        if ([item isEqualToString:@"drafts"])
            draftsFoldersExist = YES;
    }
}
_draftsArray = [[NSMutableArray alloc] init];

if (draftsFoldersExist) {
    // drafts data source
    NSString *draftsPath = [NSString stringWithFormat:@"%@/drafts", [folders objectAtIndex:0]];
    NSString *draftsPathEncoded = [draftsPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *draftsPathURL = [NSURL URLWithString:draftsPathEncoded];
    // enumerate files in drafts folders
    NSArray *desiredProperties = @[NSURLIsReadableKey, NSURLCreationDateKey];
    NSArray *drafts = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:draftsPathURL includingPropertiesForKeys:desiredProperties options:0 error:&error];

    for (NSURL *item in drafts) {
        NSMutableDictionary *draftDict = [[NSMutableDictionary alloc] init];
        // name
        NSString *name = [item lastPathComponent];
        // is readable
        NSNumber *isReadableBoolValue = nil;
        [item getResourceValue:&isReadableBoolValue forKey:NSURLIsReadableKey error:&error];
        if ([isReadableBoolValue isEqualToNumber:@YES]) {
            // filename
            [draftDict setValue:[name stringByDeletingPathExtension] forKey:@"draftFilename"];
            // creation date
            NSDate *creationDate = nil;
            [item getResourceValue:&creationDate forKey:NSURLCreationDateKey error:&error];
            [draftDict setValue:creationDate forKey:@"creationDate"];
            // insert into first position of data source array
            _draftsArray = [[@[draftDict] arrayByAddingObjectsFromArray:_draftsArray] mutableCopy];
            // meta
        } else {
            NSLog(@"unreadable item: %@", name);
        }
    }
}

UPDATE:

I downloaded the app directory's files via organizer per @Joride's suggestion and found all of the files intact and with data. Here they are:

2014-08-08_20-53-30.m4a
2014-08-09_19-11-08.m4a
2014-08-10_17-36-28.m4a
2014-08-11_18-53-46.m4a
2014-08-13_12-57-57.m4a
2014-08-16_20-44-33.m4a
2014-08-16_20-45-06.m4a

I guess the question now is why are some of them not showing any data with the dataWithContentsOfFile method.

I use the following code to init the NSData object:

NSData *fileData = [NSData dataWithContentsOfFile:filepath options:NSDataReadingMapped error:&readingError];

For the files that have zero data, the reading error says "The operation couldn’t be completed. No such file or directory".

inorganik
  • 24,255
  • 17
  • 90
  • 114
  • 1
    You probably need to show the code you use to store and retrieve the files. – Joride Aug 17 '14 at 21:48
  • @Joride there you go – inorganik Aug 17 '14 at 21:57
  • 1
    Great. Going to read it now. Before I do: you cam download the entire app-dir from xcode organozer (under devices). Choose ' Inspect Package ' to see the contents. Are your files there? What size are those files? – Joride Aug 17 '14 at 22:03
  • I have feeling something is off withe the way you are manually constructing paths / URLS. I'm on an iPhone right now, so it's hard to read the code. I'll post back when i'm on a mac. – Joride Aug 17 '14 at 22:06
  • That's a neat trick about downloading the files from organizer - didn't know you could do that! Files are indeed intact - I edited my answer. – inorganik Aug 18 '14 at 03:07
  • I created a test app, placed your code in viewDidAppear of the viewController and since 'drafts' does not exists, the code will do nothing. How is the folder 'drafts' created, and how are you storing files in it? – Joride Aug 18 '14 at 12:20

2 Answers2

2

I figured out the problem. The app's document directory changed at some point, so the location of the files changed.

Files were saved here:

/var/mobile/Applications/09E7C349-6D03-4D2F-BE17-46C00B17C9F5/Documents/drafts/2014-08-13_12-57-57.m4a

and later here:

/var/mobile/Applications/1A444A31-C29D-4B0F-8B47-A5B57D7F3281/Documents/drafts/2014-08-16_20-45-06.m4a

Notice the app's folder has a different token. So the files were "there" but not there I guess. Apparently an app's directory can change when a new provisioning profile is used or there's an update from the app store (see this answer).

So the moral of the story is: don't save absolute paths as a reference to your file.

Community
  • 1
  • 1
inorganik
  • 24,255
  • 17
  • 90
  • 114
  • Yes. That is why you should use NSFileManager. And only ever use/store paths relative to your app for this reason (the token also changes when you update your app). So in the end, the problem was In the way that you formed the URLs. From the documentation on NSSearchPathForDirectoriesInDomains: (...) "You should consider using the NSFileManager methods ... Note: The directory returned by this method may not exist. This method simply gives you the appropriate location for the requested directory(...)" – Joride Aug 20 '14 at 08:30
0

Allright, I've written what I think you want to achieve. Note that in this example the code to load the files is done synchronously on the main queue (in some viewDidAppear: method). This is off course a very bad design for real code, as it might block the UI and main queue. But this should work. If it does, then I think your code has some problems with the URLS, or the options you use when loading the files. If this code also does not work, there is something else causing a problem.

Here you go:

#import "ViewController.h"

NSString * const kDraftsDir = @"drafts";

@interface ViewController ()
@property (nonatomic, readonly) NSMutableArray * drafts;
@end

@implementation ViewController
@synthesize drafts = _drafts;

- (NSMutableArray *) drafts
{
    if (nil == _drafts)
    {
        _drafts = [[NSMutableArray alloc] init];
    }
    return _drafts;
}
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear: animated];
    [self readDrafts];
}
- (void) readDrafts
{
    NSFileManager * fileManager = [[NSFileManager alloc] init];
    NSError * error;
    NSURL * appDocumentDirectoryURL = [fileManager URLForDirectory: NSDocumentDirectory
                                                         inDomain: NSUserDomainMask
                                                appropriateForURL: nil
                                                           create: YES
                                                            error: &error];
    if (nil == appDocumentDirectoryURL)
    {
        NSLog(@"Error getting appDocumentDirectoryURL: %@, %@", error, [error localizedDescription]);
    }

    NSURL * draftsDirectoryURL = [appDocumentDirectoryURL URLByAppendingPathComponent:  kDraftsDir];
    if ([fileManager fileExistsAtPath: draftsDirectoryURL.path isDirectory: NULL])
    {
        // the folder exists, there might be drafts
        NSArray * files = [fileManager contentsOfDirectoryAtURL: draftsDirectoryURL
                                     includingPropertiesForKeys: nil
                                                        options: NSDirectoryEnumerationSkipsHiddenFiles
                                                          error: &error];
        if (nil == files)
        {
            NSLog(@"Error loading drafts from disk: %@, %@", error, [error localizedDescription]);
        }
        else
        {
            for (NSURL * aDraftURL in files)
            {
                // This is just a check for debugging purposes
                NSData * draftData = [NSData dataWithContentsOfURL: aDraftURL];
                if (draftData.length == 0)
                {
                    NSLog(@"WARNING: no file, or zero-size file at URL: %@", aDraftURL);
                }

                NSMutableDictionary * draftDict = [[NSMutableDictionary alloc] init];

                NSString * draftName = [[aDraftURL lastPathComponent]stringByDeletingPathExtension];
                // let's make sure we won't crash because we are inserting nil into a dictionary
                if (nil != draftName)
                {
                    draftDict[@"draftFilename"] = [[aDraftURL lastPathComponent]stringByDeletingPathExtension];
                }
                else
                {
                    NSLog(@"WARNING: no fileName without extension for draft at URL: %@", aDraftURL);
                }


                NSDate * creationDate = nil;
                [aDraftURL getResourceValue: &creationDate forKey:NSURLCreationDateKey error:&error];
                // let's make sure we won't crash because we are inserting nil into a dictionary
                if (nil != creationDate)
                {
                    draftDict[@"creationDate"] = creationDate;
                }
                else
                {
                    NSLog(@"WARNING: no creationdate found for file at URL: %@", aDraftURL);
                }

                // self.drafts will always return a fully initialized NSMutableArray, so we
                // can safely add an object at index 0
                [self.drafts insertObject: draftDict atIndex: 0];
            }
        }
    }
    else
    {
        NSLog(@"The drafts folder does not exist.");
    }

    NSLog(@"%@ stored drafts: %@", @([self.drafts count]), self.drafts);
}

@end
Joride
  • 3,722
  • 18
  • 22
  • Thanks - I appreciate you taking the time to write this. I inserted this code in my project and gave it a try. It works fine, and finds all the files. However, my code also finds all the files (as I noted in my question). The problem still remains, not that I can't find drafts, but that 5 of the 7 init empty NSData objects. – inorganik Aug 19 '14 at 14:13