I need solution for osx 10.6.7+. I trying to solve problem of searching "old" files on the disk. Old Files I mean files which early 1 year.
I've created NSPredicate but NSMetadataQuery returns nothing
NSPredicate * fileTypePredicate = [NSPredicate predicateWithFormat: #"file_type == \"audio\""];
NSPredicate * accessDatePredicate = [NSPredicate predicateWithFormat: #"%K <= %#", #"kMDItemAccessedDates", timeYearBefore];
return [NSCompoundPredicate andPredicateWithSubpredicates: #[fileTypePredicate, accessDatePredicate]];
Instead of kMDItemAccessedDates also I used acess_date but also without success.
For the first one, I don't know why you expect “file_type” to be a supported metadata property.
There are two properties for an item's content type. One is kMDItemContentType, which is the immediate type of the item's content (generally as determined by its filename extension). The other is kMDItemContentTypeTree, which is an array of that type and every one of its ancestors.
To find audio files, you want to find items whose kMDItemContentTypeTree is equal to kUTTypeAudio. (Yes, equality testing will work for this, even though you really want to test whether the array contains the type. Try it in Terminal: mdfind 'kMDItemContentTypeTree == public.audio')
In your code, you should use a two-parameter format string, just like you have for the second predicate, but with == as the operator: #"%K == %#" For the parameters, pass kMDItemContentTypeTree (the key) and kUTTypeAudio (the value—in this case, content type—you're looking for).
As for the second one, I can't find any mention of kMDItemAccessedDates anywhere other exactly one other Stack Overflow question. I think the author of that question might have made that key up for his custom Spotlight importer; you can't expect to find it on a stock OS X system.
You might try kMDItemLastUsedDate instead. (Don't write it as a string literal, in #"…"—just have kMDItemLastUsedDate without any quotes around it or an # in front of it.)
Related
Imagine this simple example of two paths on macOS:
/etc/hosts
/private/etc/hosts
Both point to the same file. But how do you determine that?
Another example:
~/Desktop
/Users/yourname/Desktop
Or what about upper / lower case mixes on a case-insensitive file system:
/Volumes/external/my file
/Volumes/External/My File
And even this:
/Applications/Über.app
Here: The "Ü" can be specified in two unicode composition formats (NFD, NFC). For an example where this can happen when you use the (NS)URL API see this gist of mine.
Since macOS 10.15 (Catalina) there are additionally firmlinks that link from one volume to another in a volume group. Paths for the same FS object could be written as:
/Applications/Find Any File.app
/System/Volumes/Data/Applications/Find Any File.app
I like to document ways that reliably deal with all these intricacies, with the goal of being efficient (i.e. fast).
There are two ways to check if two paths (or their file URLs) point to the same file system item:
Compare their paths. This requires that the paths get prepared first.
Compare their IDs (inodes). This is overall safer as it avoids all the complications with unicode intricacies and wrong case.
Comparing file IDs
In ObjC this is fairly easy (note: Accordingly to a knowledgeable Apple developer one should not rely on [NSURL fileReferenceURL], so this code uses a cleaner way):
NSString *p1 = #"/etc/hosts";
NSString *p2 = #"/private/etc/hosts";
NSURL *url1 = [NSURL fileURLWithPath:p1];
NSURL *url2 = [NSURL fileURLWithPath:p2];
id ref1 = nil, ref2 = nil;
[url1 getResourceValue:&ref1 forKey:NSURLFileResourceIdentifierKey error:nil];
[url2 getResourceValue:&ref2 forKey:NSURLFileResourceIdentifierKey error:nil];
BOOL equal = [ref1 isEqual:ref2];
The equivalent in Swift (note: do not use fileReferenceURL, see this bug report):
let p1 = "/etc/hosts"
let p2 = "/private/etc/hosts"
let url1 = URL(fileURLWithPath: p1)
let url2 = URL(fileURLWithPath: p2)
let ref1 = try url1.resourceValues(forKeys[.fileResourceIdentifierKey])
.fileResourceIdentifier
let ref2 = try url2.resourceValues(forKeys[.fileResourceIdentifierKey])
.fileResourceIdentifier
let equal = ref1?.isEqual(ref2) ?? false
Both solution use the BSD function lstat under the hood, so you could also write this in plain C:
static bool paths_are_equal (const char *p1, const char *p2) {
struct stat stat1, stat2;
int res1 = lstat (p1, &stat1);
int res2 = lstat (p2, &stat2);
return (res1 == 0 && res2 == 0) &&
(stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
}
However, heed the warning about using these kind of file references:
The value of this identifier is not persistent across system restarts.
This is mainly meant for the volume ID, but may also affect the file ID on file systems that do not support persistent file IDs.
Comparing paths
To compare the paths you must get their canonical path first.
If you do not do this, you can not be sure that the case is correct, which in turn will lead to very complex comparison code. (See using NSURLCanonicalPathKey for details.)
There are different ways how the case can be messed up:
The user may have entered the name manually, with the wrong case.
You have previously stored the path but the user has renamed the file's case in the meantime. You path will still identify the same file, but now the case is wrong and a comparison for equal paths could fail depending on how you got the other path you compare with.
Only if you got the path from a file system operation where you could not specify any part of the path incorrectly (i.e. with the wrong case), you do not need to get the canonical path but can just call standardizingPath and then compare their paths for equality (no case-insensitive option necessary).
Otherwise, and to be on the safe side, get the canonical path from a URL like this:
import Foundation
let uncleanPath = "/applications"
let url = URL(fileURLWithPath: uncleanPath)
if let resourceValues = try? url.resourceValues(forKeys: [.canonicalPathKey]),
let resolvedPath = resourceValues.canonicalPath {
print(resolvedPath) // gives "/Applications"
}
If your path is stored in an String instead of a URL object, you could call stringByStandardizingPath (Apple Docs). But that would neither resolve incorrect case nor would it decompose the characters, which may cause problems as shown in the aforementioned gist.
Therefore, it's safer to create a file URL from the String and then use the above method to get the canonical path or, even better, use the lstat() solution to compare the file IDs as shown above.
There's also a BSD function to get the canonical path from a C string: realpath(). However, this is not safe because it does not resolve the case of different paths in a volume group (as shown in the question) to the same string. Therefore, this function should be avoided for this purpose.
I used to have a series of independent arrays (e.g. name(), id(), description() ). I used to be able to check whether a value existed in a specific array by doing name.include?("Mark")
Now that I moved to a MUCH MORE elegant way to manage different these independent arrays (here for background: How do I convert an Array with a JSON string into a JSON object (ruby)) I am trying to figure out how I do the same.
In short I put all the independent arrays in a single structure so that I can reference the content as object().name, object().id, object().description.
However I am missing now how I can check whether the object array has a value "Mark" in its name structure.
I have tried object.name.include?("Mark") but it doesn't quite like it.
I have also tried to use has_value?but that doesn't seem to be working either (likely because it used to be an hash before I imported it into the structure but right now is no longer a hash - see here: How do I convert an Array with a JSON string into a JSON object (ruby))
Thoughts? How can I check whether object.name contains a certain string?
Thanks.
If you want to find all customers called Mark you can write the following:
customers_named_mark = array_of_customers.select{|c| c.name == 'Mark' }
This will return a potentially empty array.
If you want to find the first customer named Mark, write
customer_named_mark = array_of_customers.detect{|c| c.name == 'Mark' }
This will return the first matching item or nil.
I am try to see whether a certain image is a RAW image file by getting the UTI for the file, then using UTTypeConformsTo() to see if the image's UTI conforms to "public.camera-raw-image", but when I try doing so, UTTypeConformsTo() returns false. Here is the code in question:
- (NSNumber*)bw_conformsToUTI:(NSString*)otherString
{
Boolean conformsBoolean = UTTypeConformsTo((CFStringRef)self, (CFStringRef)otherString);
NSNumber* conforms = [NSNumber numberWithBool:conformsBoolean];
return conforms;
}
(the method is written as a category on NSString, and the lines are split apart like that because I was making sure in the debugger that there wasn't anything funny going on with converting from a Boolean to a BOOL and losing bits; conformsBoolean definitely comes back as all 0 bits)
If self is "com.canon.cr2-raw-image" and otherString is "public.camera-raw-image", I would expect this to return YES, but instead it results in NO. I tracked down where the "com.canon.cr2-raw-image" UTI is defined, and found it in /System/Library/CoreServices/CoreTypes.bundle/Contents/Library/RawCameraTypes.bundle/Info.plist where it does indeed specify that com.canon.cr2-raw-image conforms directly to public.camera-raw-image. I did notice that this is declare as an imported UTI and not an exported UTI, but since my application does recognize the declaration (as evidenced that I got that string in the first place via UTTypeCreatePreferredIdentifierForTag()), I don't think that should make a difference.
Is there anything obviously wrong that I'm doing or misunderstanding here?
I'm getting all collections using [[NSFontManager sharedFontManager] collectionNames], but I see some untranslated strings (such as "com.apple.AllFonts"). There is a way to localize them? I see that Font Book does translate them successfully. Maybe I am doing something wrong.
Thanks,
—Albe
Apple prefixes all of its internal collection names with "com.apple", probably to avoid conflicts. Depending on what you're doing, you could:
Skip any collection name that begins with "com.apple" -- they're not collections created by the user.
If a collection begins with "com.apple", split it and just get the last part of the name. Something like if ([name hasPrefix:#"com.apple"]) name = [[name componentsSeparatedByString:#"."] objectAtIndex:2]; would work.
What is the best way to count the number of entries in a property list?
I currently build a dictionary from the plist entries (*) and then use the dictionary's count:
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:myPlistPath];
NSDictionary *myPlistDict = (NSDictionary *) [NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format
errorDescription:&errorDesc];
NSLog(#"There are %d entries in the plist.", [myPlistDict count]);
This strikes me as unnecessarily "heavy", but I was not able to find a more efficient solution. Any ideas?
(*) targeting 10.5 and therefore using the deprecated +propertyListFromData:… class method.
Well... if you're converting to XML anyway, you could use NSXMLNode's childCount method. The documentation does suggest that it's more efficient than calling [children count], but the creation of the NSXMLNode might make this just as bad (or even worse than) the NSDictionary method.
Have you profiled? Are you working with particularly large plists? Are you requesting this count often? I say: use NSDictionary, cache the value if you request it often, and move on unless this is unacceptably slow. (Yeah, it looks ugly right now, but there are bigger things to worry about.)