Skip to content

Loading…

File object enhancements #1180

Merged
merged 42 commits into from

3 participants

@skurfer
Quicksilver OS X member

This brings together one of @tiennou's branches with one of mine.

He made a number of improvements to the way file objects are dealt with, including caching frequently used attributes to avoid repeated disk access. Both of us have removed a lot of unused code. I've also rebased it against the current master, so it shouldn't conflict.

My changes were mostly about allowing more control over preview icons. See #1177 for details. The one difference from that is I've restored the old default behavior, so previews will be generated for everything. Users have the option to control previews via a hidden preference. To test, try something like

defaults write com.blacktree.Quicksilver QSFilePreviewTypes '(public.image, public.movie, public.audio, com.adobe.pdf)'

This also includes all the commits from #1119 except for 16cb992, as it sounds like that was unresolved.

skurfer and others added some commits
@skurfer skurfer don't blindly create a new instance of NSFileManager for every file d016bd6
@skurfer skurfer store the UTI with the object
This allows any future operations that depend on UTI to grab it from
memory instead of (potentially) hitting the disk again.
0d7a299
@skurfer skurfer only generate previews for images, video, music, and PDFs
Other changes:
* Unused variables/statements removed
* Code specific to preference panes removed (It was never hit anyway)
* Attempts to `initWithContentsOfFile:` removed (It was never getting
hit - Quick Look takes care of it)
* Special case code from plug-ins is now tried first (and based on UTI
instead of extension)

close #1149
5836b06
@tiennou tiennou DRY. f3b6685
@tiennou tiennou Fix an issue with resolving an alias to a non-existent file. 9aff818
@tiennou tiennou Group all UTI-related functions in QSUTI.h/m.
- Apply DRY on those that do the same thing. The specific check for 'fold' was dropped because it's a standard type.
- Replace some UTI constants.
8026649
@tiennou tiennou Use a thread to scan when clicking things in the interface. 5bfe8cc
@tiennou tiennou Use FSPathMoveObjectToTrashSync for moving files to the Trash.
I don't want to know what would happen in case the file wasn't on the same drive than ~/.Trash… Hopefully it looks it's only used by the update code when moving the old QS app to the Trash.
70d34ac
@tiennou tiennou Catch exception when trying to instantiate instances. 1e777cb
@skurfer skurfer Remove unused stuff + cleanup.
Conflicts:
	Quicksilver/Code-QuickStepCore/QSObject_FileHandling.h
e108e70
@tiennou tiennou Use the object's cache to store its LSItemInfoRecord info.
Provide accessors for `-isPackage`, `-isAlias`, `-fileExtension`, and `-fileUTI`. Also changes `-isApplication` & `-isFolder` to use the cached LSItemInfoRecord.
a6794f5
@tiennou tiennou Use the new accessors. e17d52d
@tiennou tiennou Fix usage of NSError.
You should never touch an NSError object before checking that an error was triggered, in case the callee decides to set the pointer to garbage.
c09deab
@tiennou tiennou Remove static bundlePresetChildren cache.
This would force a restart in case a plugin declares one of those and gets loaded late.
30ac2da
@tiennou tiennou Remove unused code.
This is a special case for pref panes that draws the pane's icon in the old-style preference pane template. Now that we have _real_ previewing for those, that's useless ;-).
49d8b81
@skurfer skurfer Merge branch 'filesystem' into file-uti
Conflicts:
	Quicksilver/Code-QuickStepCore/QSObject_FileHandling.m
52dd7aa
@skurfer skurfer use new method to get the UTI 1efc849
@skurfer skurfer add a new method to see if a file object conforms to a UTI
If we start assigning UTIs to all objects, this method could
potentially be expanded to evaluate everything (and moved to the main
QSObject code).
9c4c4d0
@skurfer skurfer use the new `conformsToUTI:` method when testing UTIs 170bfbc
@skurfer skurfer remove more (unused) special code for Preference Panes 8286bce
@skurfer skurfer load previews for everything by default (previous behavior)
Users will have the ability to override this by setting a hidden default.
d9a5bb9
@tiennou tiennou Log the error loading message, and don't bother with the stack trace. c037062
@tiennou tiennou Use the AvailabilityMacros for this. a938bd1
@skurfer skurfer referenced this pull request
Closed

Fixes #1119

@skurfer skurfer use the literal value for OS X 10.8 instead of the macro
Developers that aren't on 10.8 will be missing this macro.
b9fbc35
@skurfer
Quicksilver OS X member

There's another potential area for optimization I noticed while resolving conflicts and looking through the changes. -[QSDirectoryParser objectsFromPath:depth:types:excludeTypes:descend:] looks at a lot of the things that are now cached (but it does so prior to creating the QSObject from the file, so the new LSItemInfoRecord cache can't be used). I wonder if we could move things around a bit to avoid reading the same information twice.

@tiennou
Quicksilver OS X member

Be aware that 70d34ac already changes the method. I don't know if it's what NSWorkspace uses, and I still haven't checked how it handles trashing files from network drives. In fact, I was keen to have that checked before, in order to cut the amount of code churn, but that's not really a problem.

@tiennou
Quicksilver OS X member

I don't feel like it's really necessary... And this is already in QSObject, this is a category (yay for separation of concerns ;-)).

Quicksilver OS X member

I don't feel like it's really necessary...

Really? I found three places to use it in the very next commit. That number will most likely grow going forward.

And this is already in QSObject, this is a category (yay for separation of concerns ;-)).

Yes, but it only applies to QSObjects created from files. I was envisioning a distant future where the types we assign to QSObjects are all UTIs (instead of QSXYZType).

Quicksilver OS X member
Quicksilver OS X member

My point was more like "you're exchanging one C function call for a Obj-C message dispatch minus the time to type casts to CFStringRef".

If it introduces any overhead, I think you're right that it's not worth it. I'll revert those two commits.

But now I'm thinking "premature optimisation" against my way ;-). Do we really need a method for that ?

And I probably let my aversion to anything starting with CF get in my way. :-)

Agreed. But keeping in mind that UTIs have both a "physical" (eg. conforms to public.data, the contents) and "logical" (eg. conforms to public.item, a file, directory, ...), fileUTI sounds like a misnomer.

Well, I was thinking fileUTI would stay where it is and still return the info for files, while the new method might eventually check other places in addition to fileUTI, but… never mind.

All in all, I think this is good for file-system things (that explains why it's -fileUTI and not -typeUTI) but changing the core typing system requires a little more thought/testing before we can say UTIs are the way to internally.

Agreed.

@skurfer
Quicksilver OS X member

Be aware that 70d34ac already changes the method.

Yeah, but I didn't think anyone was disputing that change.

I don't know if it's what NSWorkspace uses, and I still haven't checked how it handles trashing files from network drives.

It makes the sound, but nothing happens. So that answers that. I guess we need another way.

skurfer and others added some commits
@skurfer skurfer Revert "use the new `conformsToUTI:` method when testing UTIs"
This reverts commit 170bfbc.
a37c253
@skurfer skurfer Revert "add a new method to see if a file object conforms to a UTI"
This reverts commit 9c4c4d0.

Conflicts:
	Quicksilver/Code-QuickStepCore/QSObject_FileHandling.m
419a1d9
@skurfer skurfer use new method to get the UTI (again)
The last two reverts undid this change.
fcbdcb6
@tiennou tiennou Use NSWorkspace method instead.
Conflicts:
	Quicksilver/Code-QuickStepFoundation/NSFileManager_BLTRExtensions.m
6570ce9
@skurfer
Quicksilver OS X member

After testing some things and reviewing the previous discussion, I've ended up pulling in 16cb992 as well.

I disagree that we need to get the Trash action working for network volumes. The “Delete (Erase)” action is what should work for those, and it does.

I think the correct solution here is to make sure the “Move to Trash” action only appears if the file is on a volume that supports trashing files. So that's what I'm going to work on. Hopefully there's an easy, inexpensive way to figure that out.

@pjrobertson
Quicksilver OS X member
@skurfer
Quicksilver OS X member

Would it be a simpler option to do what Finder does and put a check in the 'move to trash' action. If we can't move to trash (as it's a network volume) then we display a dialogue saying 'Cannot move to trash, only delete'. Then 'Continue' or 'Cancel'

Reimplementing what Finder does doesn't sound simpler. ;-)

I've been reading a similar discussion for ideas. Really, I was hoping we could just hand this off to Finder via Scripting Bridge since it already handles everything correctly, but the SB stuff is all in another branch. (And didn't you already try Trashing via SB with no luck?)

@pjrobertson
Quicksilver OS X member
@skurfer
Quicksilver OS X member

By the way, I've got some working code for the validation approach based on what I saw in the CocoaDev wiki.

Basically, I call statfs() and see if the device the file sits on begins with /dev/ or not. This is cached as a BOOL in the object's new infoRecord that @tiennou added. But it still needs some work.

  1. I don't like yet another trip to the disk for every object (though once it's cached, everything should be very fast). I'm wondering if the information provided by statfs() is available from something we're already doing like LSCopyItemInfoForURL(), but I don't know enough about it.
  2. I'm pretty sure AFP network volumes do support trashing, so it's not as simple as just checking network vs. not. I have an AFP server at home I can test this with. Just haven't had a chance.
skurfer added some commits
@skurfer skurfer determine if the file is on a local volume (and cache the answer in t…
…he infoRecord)

At this point, we primarily need to know if the file can be trashed (or just deleted).
b12bfc2
@skurfer skurfer perform validation for the “Move to Trash” action d9ef123
@skurfer
Quicksilver OS X member

It'd use exactly the same if() statement as you'd put in the validActionsForDirectObject method, but just show a dialog instead of returning NO. Not that much more difficult.

Not if they cancel the operation. But if they want to proceed and permanently delete instead?

I say we do validation for now until we can see what Scripting Bridge offers.

I noticed the trash sound plays whether the operation succeeds or not, so I'd like to fix that and then I'll have you look everything over.

Oh, and on the shares I have access to anyway, AFP doesn't allow trashing either.

@skurfer
Quicksilver OS X member

I guess it would help if I included the commits that take care of validation. I've pushed them now.

skurfer added some commits
@skurfer skurfer update the Open action to use the cached infoRecord 89922a1
@skurfer skurfer add the extension last (in case it's `nil`)
The extension for folders is `nil`, which causes the method to stop checking for any additional object/key pairs, preventing isLocal and uti from being stored.
98e79a7
@skurfer
Quicksilver OS X member

Two more commits.

I noticed the Open action was getting LSItemInfoRecord, so I updated it to used the cached info.

I also noticed that the Trash action wasn't appearing for folders. It's because when the dictionaryWithObjectsAndKeys: method encounters nil, it stops processing. The extension for folders is usually nil. I made it add extension last to avoid this problem, but do you think we should go further? Maybe check the extension and set it to @"" if it's nil? Should we add all the values to the dictionary one by one, so if any one of them is nil, we don't run into this again?

@skurfer
Quicksilver OS X member

To answer the question from IRC on 89922a1:

what's the difference between using [dObject validPaths] and doing what you've done

Basically, validPaths returns an array of strings. I needed an array of QSObjects instead because that's where the LSItemInfoRecord is cached.

I've been looking at that method some more and I'm trying to figure out why all those tests were in there to begin with. Pseudo-code:

if (file not a folder || file is a package || attempt to open with mQSFSBrowser failed)
    if (file is alias)
        resolve alias
        if (thisFile is a child from right-arrowing into an application)
            open with that application
        else
            open with workspace manager

When you right-arrow into an application, the files shown should open in that application, regardless of their default in Launch Services. This doesn't work for folders (in TextMate) which has always bugged me. I think the above should be more like:

if (file is an alias)
    resolve the alias
if (file is a child from right-arrowing into an application)
    open with that application
else if (attempt to open with mQSFSBrowser fails)
    open with workspace manager

To simplify, the current precedence for opening seems to be

  1. File System Browser Handler
  2. Parent application (if right-arrowed into)
  3. Fallback to default

where it should be:

  1. Parent application (if right-arrowed into)
  2. File System Browser Handler
  3. Fallback to default

And I don't see why we need to test for folders and packages.

@skurfer skurfer rearrange the precedence when opening files
before: File System Browser, parent application, workspace manager
now: parent application, File System Browser, workspace manager

Also fixes a bug where files would open in the parent application until rescanned.
f5c80e7
@skurfer
Quicksilver OS X member

I've made the change to “Open” as described above. All files/folders shown when right-arrowing into an application are now opened in that application. I’ve also fixed a long-standing bug where the file would open in that application from then on (until rescanned), regardless of the default.

I’ve tested this with folders and various types of packages and it all seems to work as expected. I even went as far as installing Path Finder and dusting off its plug-in to make sure everything worked correctly with an alternate mQSFSBrowser.

@tiennou tiennou commented on an outdated diff
Quicksilver/Code-QuickStepCore/QSObject_FileHandling.m
((30 lines not shown))
+
+ /* local or network volume? does it support Trash? */
+ struct statfs sfsb;
+ statfs([path UTF8String], &sfsb);
+ NSString *device = [NSString stringWithCString:sfsb.f_mntfromname encoding:NSUTF8StringEncoding];
+ BOOL isLocal = [device hasPrefix:@"/dev/"];
+
+ /* Now build a dictionary with that record */
+ dict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithUnsignedInt:record.flags], @"flags",
+ [NSValue valueWithOSType:record.filetype], @"filetype",
+ [NSValue valueWithOSType:record.creator], @"creator",
+ [NSNumber numberWithBool:isLocal], @"localVolume",
+ uti, @"uti",
+ [(NSString *)record.extension copy], @"extension",
+ nil];
@tiennou Quicksilver OS X member
tiennou added a note

I think you could change dict into an NSMutableDictionary and add -setObject:forKey: calls for uti and extension instead. The others should have valid defaults since they're passed to NSNumber/NSValue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@skurfer
Quicksilver OS X member

I've added another commit that uses a mutable dictionary and adds the questionable values one at a time.

@skurfer
Quicksilver OS X member

for B71

I haven't fixed the fact that the Trash sound plays unconditionally, but that's not new, so feel free to merge. I'm not really sure what to do anyway. As it loops through files, if some fail and others don't, do we play the sound?

@pjrobertson
Quicksilver OS X member
@skurfer skurfer referenced this pull request
Merged

Getting objects by type #1162

@skurfer skurfer deal with failure in the Trash action
* only play the sound if at least one file was trashed successfully
* if any files couldn't be trashed, display a notification listing them
89a3d91
@skurfer
Quicksilver OS X member

OK, I've updated the Trash action. Take a look.

@skurfer
Quicksilver OS X member

Some small issues with this:

  1. The infoRecord method checks for a valid path before checking for cached results. This goes to disk on every cal, which is too expensive. I'm going to change it to check the cache first. If the file no longer exists, that should get picked up elsewhere.
  2. To the greatest extent possible, we want all the disk reads necessary to populate the infoRecord dict to take place during catalog scanning and not in real-time as someone uses the app. The dict isn't preserved across relaunches, but the file objects themselves are, which means they aren't rescanned and the infoRecord isn't repopulated until the user does something with the object in the UI. I'd like to see if we can cache the dict to disk with the objects.
skurfer added some commits
@skurfer skurfer check the cache in infoRecord first to avoid going to disk if possible 0d1672a
@skurfer skurfer allow for the possibility that more than one icon provider is availab…
…le for a file
e2ea085
@skurfer skurfer simplify validation using the new infoRecord-based methods
This can be a bit slower than the old way if a lot of file objects don't have infoRecord cached, but it's not terrible (about 0.1s), and once the info is cached, it's about 10 times faster than the previous code.

This is a proof-of-concept. There are probably many other validation methods we could simplify.
29eee13
@skurfer skurfer show all applications (in the catalog) for the Open With… action
Also, remove the now unused universalApps code.
close #1184
4874f70
@skurfer
Quicksilver OS X member

OK, the commit to add all applications to “Open With…” is in. Let me know if there’s anything else.

@pjrobertson
Quicksilver OS X member

There's something in here (or possible somewhere else between ß70 and here) that breaks right arrowing into text files.

I guess it's most likely here since this is where the fiddling with [QSObject loadChildrenForObject:] went on ;-)

@pjrobertson
Quicksilver OS X member

OK, here's the problem:
skurfer@c09deab

Caught you @tiennou ;-)

Seems like the return NO; shouldn't be there, otherwise the method never looks at the other handlers that could load children (like the text parser)


For @tiennou

And on the matter of that pull request: what if there's a method that say… returns 'NO' if there is an error, or if there's nothing to do or some other reason.

For example, take a look at: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/Reference/Reference.html

This method returns NO if an error occurred OR "if a file, directory, or link already exists at path."
So in this case, how would you check to see if an error occurs? By checking if(error != nil) {} no?!

@pjrobertson
Quicksilver OS X member

Aaaand…. one more thing :)

I don't think that NSLog should be there at all. Try right arrowing into any file that shouldn't be right arrowed into (e.g. a .zip file or .jpg etc.) and watch your console.
Yucky yucky yuck!

Even if you right arrow into a text file, you still get that error log :(

@pjrobertson
Quicksilver OS X member

OK, last comment, I promise… :)

Just wrap lines 402 - 426 in an if ([object isFolder]) {} statement and we're all good :)

Should that method be isDirectory, since that's seems to be the convention, not isFolder?

@skurfer
Quicksilver OS X member

Just wrap lines 402 - 426 in an if ([object isFolder]) {} statement and we're all good :)

I can take care of that.

Should that method be isDirectory, since that's seems to be the convention, not isFolder?

For what it's worth, I had some weird problems when changing the validation around and it was because I was using isDirectory (thinking that was the name). There is a method by that name already, so we'd need need to be specific about the type of object in the loop, but I should have done that anyway.

So sounds like two votes for isDirectory. I'll fix that and the type assigned to the objects during validation.

@pjrobertson
Quicksilver OS X member
@skurfer
Quicksilver OS X member

Two new commits.

@pjrobertson
Quicksilver OS X member

Cool. I'll run with it a little longer incase anything else crops up

@tiennou
Quicksilver OS X member

Sorry guys :-(. @skurfer your fix looks good.

Another quick thing : how do you feel about keeping -isFolder with the real "folder" meaning ? Something akin to return [self isDirectory] && !([self isPackage] || [self isApplication]. I'm sure that test is wrong but you see the point ;-).

@pjrobertson
Quicksilver OS X member
@skurfer
Quicksilver OS X member

I think that's what isDirectory does now. Correct me if I'm wrong, but it returns false if the file object is a package or application, yeah?

Worth testing to find out. You can see I'm looking at isPackage too when populating the third pane for the Move/Copy To action.

@tiennou
Quicksilver OS X member

Yeah, the old tests look way too fishy for me. Testing using ß70, it looks to me .bundle things (a.k.a packages) get a folder quick icon, just before the preview kicks in and override the default. We might want to have both -isDirectory and -isFolder, in case we need precision : the former means low-level multi-children thing, the latter means the thing you expect to open in a new Finder window ;-). But IIRC (I reimplemented File Objects in Ruby, remember ;-)), applications would return YES to -isDirectory calls kind because of this...

@skurfer
Quicksilver OS X member

I've confirmed that isDirectory returnsYES for applications and other packages (which is what I expected). If you want, I can add an isFolder method. I think it just needs to do what the Move/Copy To actions are doing now, which is

return ([self isDirectory] && ![self isPackage]);

Applications are packages, so no need to test isApplication.

@skurfer skurfer add an `isFolder` method for file objects
Looking for directories that aren't packages is a pretty common task.
bd5163a
@pjrobertson pjrobertson merged commit bd5163a into quicksilver:master
@pjrobertson
Quicksilver OS X member

Fixed the conflict and pushed to master. Hopefully I got the merge conflict right ;-)

@pjrobertson
Quicksilver OS X member

Oh yeah - ß71 anyone? :)

@pjrobertson pjrobertson referenced this pull request
Merged

Speedups #1206

@skurfer
Quicksilver OS X member

Oh yeah - ß71 anyone? :)

Heh. I thought this is all we were waiting on, but now there's some tempting stuff out there. :-)

Probably take me all day just to add this one pull to the change log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 15, 2012
  1. @skurfer
  2. @skurfer

    store the UTI with the object

    skurfer committed
    This allows any future operations that depend on UTI to grab it from
    memory instead of (potentially) hitting the disk again.
Commits on Oct 16, 2012
  1. @skurfer

    only generate previews for images, video, music, and PDFs

    skurfer committed
    Other changes:
    * Unused variables/statements removed
    * Code specific to preference panes removed (It was never hit anyway)
    * Attempts to `initWithContentsOfFile:` removed (It was never getting
    hit - Quick Look takes care of it)
    * Special case code from plug-ins is now tried first (and based on UTI
    instead of extension)
    
    close #1149
Commits on Oct 17, 2012
  1. @tiennou @skurfer

    DRY.

    tiennou committed with skurfer
  2. @tiennou @skurfer
  3. @tiennou @skurfer

    Group all UTI-related functions in QSUTI.h/m.

    tiennou committed with skurfer
    - Apply DRY on those that do the same thing. The specific check for 'fold' was dropped because it's a standard type.
    - Replace some UTI constants.
  4. @tiennou @skurfer
  5. @tiennou @skurfer

    Use FSPathMoveObjectToTrashSync for moving files to the Trash.

    tiennou committed with skurfer
    I don't want to know what would happen in case the file wasn't on the same drive than ~/.Trash… Hopefully it looks it's only used by the update code when moving the old QS app to the Trash.
  6. @tiennou @skurfer
  7. @skurfer

    Remove unused stuff + cleanup.

    skurfer committed
    Conflicts:
    	Quicksilver/Code-QuickStepCore/QSObject_FileHandling.h
  8. @tiennou @skurfer

    Use the object's cache to store its LSItemInfoRecord info.

    tiennou committed with skurfer
    Provide accessors for `-isPackage`, `-isAlias`, `-fileExtension`, and `-fileUTI`. Also changes `-isApplication` & `-isFolder` to use the cached LSItemInfoRecord.
  9. @tiennou @skurfer

    Use the new accessors.

    tiennou committed with skurfer
  10. @tiennou @skurfer

    Fix usage of NSError.

    tiennou committed with skurfer
    You should never touch an NSError object before checking that an error was triggered, in case the callee decides to set the pointer to garbage.
  11. @tiennou @skurfer

    Remove static bundlePresetChildren cache.

    tiennou committed with skurfer
    This would force a restart in case a plugin declares one of those and gets loaded late.
  12. @tiennou @skurfer

    Remove unused code.

    tiennou committed with skurfer
    This is a special case for pref panes that draws the pane's icon in the old-style preference pane template. Now that we have _real_ previewing for those, that's useless ;-).
  13. @skurfer

    Merge branch 'filesystem' into file-uti

    skurfer committed
    Conflicts:
    	Quicksilver/Code-QuickStepCore/QSObject_FileHandling.m
  14. @skurfer

    use new method to get the UTI

    skurfer committed
  15. @skurfer

    add a new method to see if a file object conforms to a UTI

    skurfer committed
    If we start assigning UTIs to all objects, this method could
    potentially be expanded to evaluate everything (and moved to the main
    QSObject code).
  16. @skurfer
  17. @skurfer
  18. @skurfer

    load previews for everything by default (previous behavior)

    skurfer committed
    Users will have the ability to override this by setting a hidden default.
  19. @tiennou @skurfer
  20. @tiennou @skurfer
  21. @skurfer

    use the literal value for OS X 10.8 instead of the macro

    skurfer committed
    Developers that aren't on 10.8 will be missing this macro.
Commits on Oct 19, 2012
  1. @skurfer
  2. @skurfer

    Revert "add a new method to see if a file object conforms to a UTI"

    skurfer committed
    This reverts commit 9c4c4d0.
    
    Conflicts:
    	Quicksilver/Code-QuickStepCore/QSObject_FileHandling.m
Commits on Oct 25, 2012
  1. @skurfer

    use new method to get the UTI (again)

    skurfer committed
    The last two reverts undid this change.
  2. @tiennou @skurfer

    Use NSWorkspace method instead.

    tiennou committed with skurfer
    Conflicts:
    	Quicksilver/Code-QuickStepFoundation/NSFileManager_BLTRExtensions.m
Commits on Oct 26, 2012
  1. @skurfer

    determine if the file is on a local volume (and cache the answer in t…

    skurfer committed
    …he infoRecord)
    
    At this point, we primarily need to know if the file can be trashed (or just deleted).
  2. @skurfer
Commits on Oct 31, 2012
  1. @skurfer
  2. @skurfer

    add the extension last (in case it's `nil`)

    skurfer committed
    The extension for folders is `nil`, which causes the method to stop checking for any additional object/key pairs, preventing isLocal and uti from being stored.
  3. @skurfer

    rearrange the precedence when opening files

    skurfer committed
    before: File System Browser, parent application, workspace manager
    now: parent application, File System Browser, workspace manager
    
    Also fixes a bug where files would open in the parent application until rescanned.
Commits on Nov 2, 2012
  1. @skurfer
Commits on Nov 6, 2012
  1. @skurfer

    deal with failure in the Trash action

    skurfer committed
    * only play the sound if at least one file was trashed successfully
    * if any files couldn't be trashed, display a notification listing them
Commits on Nov 9, 2012
  1. @skurfer
  2. @skurfer
  3. @skurfer

    simplify validation using the new infoRecord-based methods

    skurfer committed
    This can be a bit slower than the old way if a lot of file objects don't have infoRecord cached, but it's not terrible (about 0.1s), and once the info is cached, it's about 10 times faster than the previous code.
    
    This is a proof-of-concept. There are probably many other validation methods we could simplify.
  4. @skurfer

    show all applications (in the catalog) for the Open With… action

    skurfer committed
    Also, remove the now unused universalApps code.
    close #1184
Commits on Nov 10, 2012
  1. @skurfer

    isFolder → isDirectory

    skurfer committed
  2. @skurfer
Commits on Nov 12, 2012
  1. @skurfer

    add an `isFolder` method for file objects

    skurfer committed
    Looking for directories that aren't packages is a pretty common task.
View
3 Quicksilver/Code-QuickStepCore/QSCatalogEntry.m
@@ -211,7 +211,8 @@ - (void)setEnabled:(BOOL)enabled {
[[QSLibrarian sharedInstance] setPreset:self isEnabled:enabled];
else
[info setObject:[NSNumber numberWithBool:enabled] forKey:kItemEnabled];
- if (enabled && ![[self contents] count]) [self scanForced:NO];
+ if (enabled && ![[self contents] count])
+ [NSThread detachNewThreadSelector:@selector(scanForcedInThread:) toTarget:self withObject:[NSNumber numberWithBool:NO]];
}
- (void)setDeepEnabled:(BOOL)enabled {
View
21 Quicksilver/Code-QuickStepCore/QSObject_FileHandling.h
@@ -10,10 +10,7 @@
- (NSData *)fileRepresentationForObject:(QSObject *)object;
- (NSString *)filenameForObject:(QSObject *)object;
@end
-
-@interface QSFileSystemObjectHandler : NSObject {
- NSMutableDictionary *applicationIcons;
-}
+@interface QSFileSystemObjectHandler : NSObject
// Added by Patrick Robertson 30/06/11 in Pull #388. QSObject_FileHandling.h/.m are a mess and it's unclear as to wether
// this is required. Any developers working on tidying these files should check the necessity/requirement of this definition
@@ -25,7 +22,7 @@
- (NSString *)validSingleFilePath;
- (NSArray *)validPaths;
- (NSArray *)validPathsResolvingAliases:(BOOL)resolve;
-- (NSInteger) fileCount;
+- (NSInteger)fileCount;
@end
@interface NSObject (QSFilePreviewProvider)
@@ -41,16 +38,18 @@
- (id)initWithArray:(NSArray *)paths;
- (void)getNameFromFiles;
- (NSString *)kindOfFile:(NSString *)path;
- // NSLog(@"name %@ %@ %@", newName, newLabel, [path lastPathComponent]);
- (NSString *)filesContainer;
- (NSString *)filesType;
-//- (QSObject *)fileObjectByMergingWith:(QSObject *)mergeObject;
- (BOOL)isApplication;
-- (BOOL)isFolder;
+- (BOOL)isDirectory; // YES for all directories
+- (BOOL)isFolder; // YES for directories that aren't packages
+- (BOOL)isPackage;
+- (BOOL)isAlias;
+- (BOOL)isOnLocalVolume;
+- (NSString *)fileExtension;
+- (NSString *)fileUTI;
- (NSString *)singleFileType;
-- (NSArray *)validPaths;
-- (NSArray *)validPathsResolvingAliases:(BOOL)resolve;
--(void)previewIcon:(id)file;
+- (void)previewIcon:(id)file;
@end
View
495 Quicksilver/Code-QuickStepCore/QSObject_FileHandling.m
@@ -18,21 +18,15 @@
#import "QSObject_PropertyList.h"
#include "QSLocalization.h"
#import "QSDownloads.h"
+#import <sys/mount.h>
#import "NSApplication_BLTRExtensions.h"
-
-// Ankur, 21 Dec 07: 'useSmallIcons' not used anywhere. Commented out.
-// Ankur, 12 Feb 08: as above for 'applicationIcons'
-
NSString *identifierForPaths(NSArray *paths) {
if ([paths count] == 1) return [paths lastObject];
return [paths componentsJoinedByString:@" "];
}
-static NSDictionary *bundlePresetChildren;
-//static BOOL useSmallIcons = NO;
-
NSArray *recentDocumentsForBundle(NSString *bundleIdentifier) {
if (bundleIdentifier == nil) {
return nil;
@@ -78,25 +72,6 @@ -(NSImage *)prepareImageforIcon:(NSImage *)icon;
@implementation QSFileSystemObjectHandler
-// !!! Andre Berg 20091017: Not so good to disable init when subclassing... re-enabling.
-
-// Object Handler Methods
-// +(void)initialize {
-// useSmallIcons = [[NSUserDefaults standardUserDefaults] boolForKey:kUseSmallIcons];
-// }
-#if 1
-- (id)init {
- self = [super init];
- if (self != nil) {
- applicationIcons = [[NSMutableDictionary alloc] init];
- }
- return self;
-}
-- (NSMutableDictionary *)applicationIcons {
- return applicationIcons;
-}
-#endif
-
- (QSObject *)parentOfObject:(QSObject *)object {
QSObject * parent = nil;
if ([object singleFilePath]) {
@@ -114,76 +89,28 @@ - (BOOL)drawIconForObject:(QSObject *)object inRect:(NSRect)rect flipped:(BOOL)f
if (NSWidth(rect) <= 32)
return NO;
NSString *path = [object singleFilePath];
-
- if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:nil]) {
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path] , kLSRequestBasicFlagsOnly, &infoRec);
- if (infoRec.flags & kLSItemInfoIsApplication) {
- NSString *bundleIdentifier = [[NSBundle bundleWithPath:path] bundleIdentifier];
- NSString *handlerName = [[QSReg tableNamed:@"QSBundleDrawingHandlers"] objectForKey:bundleIdentifier];
- if (handlerName) {
- id handler = [QSReg getClassInstance:handlerName];
- if (handler) {
- if ([handler respondsToSelector:@selector(drawIconForObject:inRect:flipped:)])
- return [handler drawIconForObject:(QSObject *)object inRect:(NSRect)rect flipped:(BOOL)flipped];
- return NO;
- }
- }
- }
- }
- return NO;
-
- if (!path || [[path pathExtension] caseInsensitiveCompare:@"prefpane"] != NSOrderedSame)
- return NO;
-
- NSImage *image = [NSImage imageNamed:@"PrefPaneTemplate"];
-
- [image setSize:[[image bestRepresentationForSize:rect.size] size]];
- //[image adjustSizeToDrawAtSize:rect.size];
- [image drawInRect:rect fromRect:rectFromSize([image size]) operation:NSCompositeSourceOver fraction:1.0f];
-
- if ([object iconLoaded]) {
- NSImage *cornerBadge = [object icon];
- if (cornerBadge != image) {
-
- NSRect badgeRect = NSMakeRect(16+48+rect.origin.x, 16+36+rect.origin.y, 32, 32);
- NSImageRep *bestBadgeRep = [cornerBadge bestRepresentationForSize:badgeRect.size];
-
- [cornerBadge setSize:[bestBadgeRep size]];
-
- [[NSColor colorWithDeviceWhite:1.0 alpha:0.8] set];
- //NSRectFillUsingOperation(NSInsetRect(badgeRect, -14, -14), NSCompositeSourceOver);
- NSBezierPath *path = [NSBezierPath bezierPath];
- [path appendBezierPathWithRoundedRectangle:NSInsetRect(badgeRect, -10, -10) withRadius:4];
-
- [[NSColor colorWithDeviceWhite:1.0 alpha:1.0] setFill];
- [[NSColor colorWithDeviceWhite:0.75 alpha:1.0] setStroke];
- [path fill];
- [path stroke];
+ if ([object isApplication]) {
+ NSString *bundleIdentifier = [[NSBundle bundleWithPath:path] bundleIdentifier];
- NSFrameRectWithWidth(NSInsetRect(badgeRect, -5, -5), 2);
-
- [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationNone];
- [cornerBadge drawInRect:badgeRect fromRect:rectFromSize([cornerBadge size]) operation:NSCompositeSourceOver fraction:1.0];
- [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
- }
- }
- return YES;
+ NSString *handlerName = [[QSReg tableNamed:@"QSBundleDrawingHandlers"] objectForKey:bundleIdentifier];
+ if (handlerName) {
+ id handler = [QSReg getClassInstance:handlerName];
+ if (handler) {
+ if ([handler respondsToSelector:@selector(drawIconForObject:inRect:flipped:)])
+ return [handler drawIconForObject:object inRect:rect flipped:flipped];
+ return NO;
+ }
+ }
+ }
+ return NO;
}
- (NSString *)kindOfObject:(QSObject *)object {
- NSString *path = [object singleFilePath];
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path] , kLSRequestBasicFlagsOnly, &infoRec);
- if (infoRec.flags & kLSItemInfoIsApplication) {
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path] , kLSRequestBasicFlagsOnly, &infoRec);
- if (infoRec.flags & kLSItemInfoIsApplication) {
- return @"QSKindApplication";
- }
- }
- return nil;
+ if ([object isApplication])
+ return @"QSKindApplication";
+
+ return nil;
}
- (NSString *)detailsOfObject:(QSObject *)object {
@@ -198,21 +125,17 @@ - (NSString *)detailsOfObject:(QSObject *)object {
} else {
return [path stringByAbbreviatingWithTildeInPath];
}
- } else if ([theFiles count] >1) {
+ } else if ([theFiles count] > 1) {
return [[theFiles arrayByPerformingSelector:@selector(lastPathComponent)] componentsJoinedByString:@", "];
}
return nil;
}
- (void)setQuickIconForObject:(QSObject *)object {
- NSString *path = [object singleFilePath];
- BOOL isDirectory;
- [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
- if (isDirectory)
- if ([[path pathExtension] isEqualToString:@"app"] || [[NSArray arrayWithObject:@"'APPL'"] containsObject:NSHFSTypeOfFile(path)])
- [object setIcon:[QSResourceManager imageNamed:@"GenericApplicationIcon"]];
- else
- [object setIcon:[QSResourceManager imageNamed:@"GenericFolderIcon"]];
+ if ([object isApplication])
+ [object setIcon:[QSResourceManager imageNamed:@"GenericApplicationIcon"]];
+ else if ([object isDirectory])
+ [object setIcon:[QSResourceManager imageNamed:@"GenericFolderIcon"]];
else
[object setIcon:[QSResourceManager imageNamed:@"UnknownFSObjectIcon"]];
}
@@ -239,7 +162,7 @@ - (BOOL)loadIconForObject:(QSObject *)object {
[set removeObject:@"'fold'"];
[set addObject:@"'fldr'"];
}
-
+
if ([set count] == 1) {
theImage = [w iconForFileType:[set anyObject]];
} else {
@@ -250,7 +173,7 @@ - (BOOL)loadIconForObject:(QSObject *)object {
// set temporary image until preview icon is generated
theImage = [self prepareImageforIcon:theImage];
[object setIcon:theImage];
-
+
// if it's a single file, try to create preview icon
// this has to be started after the temporary icon is set, so the preview icon
// wont be overwritten by the temporary icon
@@ -264,66 +187,54 @@ - (BOOL)loadIconForObject:(QSObject *)object {
return YES;
}
--(void)previewIcon:(id)object {
+- (void)previewIcon:(QSObject *)object {
NSImage *theImage = nil;
- NSArray *theFiles = [object arrayForType:QSFilePathType];
- NSString *path = [theFiles lastObject];
- NSString *firstFile = [theFiles objectAtIndex:0];
+ NSString *path = [[object arrayForType:QSFilePathType] lastObject];
NSFileManager *manager = [NSFileManager defaultManager];
-
+
// the object isn't a file/doesn't exist, so return. shouldn't actually happen
if (![manager fileExistsAtPath:path]) {
return;
}
- LSItemInfoRecord infoRec;
- //OSStatus status=
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path] , kLSRequestBasicFlagsOnly, &infoRec);
-
- // try preview icon
- if (!theImage && [[NSUserDefaults standardUserDefaults] boolForKey:@"QSLoadImagePreviews"]) {
- // do preview icon loading in separate thread (using NSOperationQueue)
- theImage = [NSImage imageWithPreviewOfFileAtPath:path ofSize:QSMaxIconSize asIcon:YES];
- }
-
- // Just for prefpanes?
- if (!theImage && infoRec.flags & kLSItemInfoIsPackage) {
- NSBundle *bundle = [NSBundle bundleWithPath:firstFile];
- NSString *bundleImageName = nil;
- if ([[firstFile pathExtension] isEqualToString:@"prefPane"]) {
- bundleImageName = [[bundle infoDictionary] objectForKey:@"NSPrefPaneIconFile"];
-
- if (!bundleImageName) bundleImageName = [[bundle infoDictionary] objectForKey:@"CFBundleIconFile"];
- if (bundleImageName) {
- NSString *bundleImagePath = [bundle pathForResource:bundleImageName ofType:nil];
- theImage = [[[NSImage alloc] initWithContentsOfFile:bundleImagePath] autorelease];
- }
- }
- }
-
- // try QS's own methods to generate a preview
- if (!theImage && [[NSUserDefaults standardUserDefaults] boolForKey:@"QSLoadImagePreviews"]) {
- NSString *type = [manager typeOfFile:path];
- if ([[NSImage imageUnfilteredFileTypes] containsObject:type])
- theImage = [[[NSImage alloc] initWithContentsOfFile:path] autorelease];
- else {
- id provider = [QSReg instanceForKey:type inTable:@"QSFSFileTypePreviewers"];
- //NSLog(@"provider %@", [QSReg tableNamed:@"QSFSFileTypePreviewers"]);
- theImage = [provider iconForFile:path ofType:type];
- }
- }
-
- // fallback, if no of the other methods worked: just use icon for filetype
+
+ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"QSLoadImagePreviews"]) {
+ // try to create a preview icon
+ NSString *uti = [object fileUTI];
+ // try customized methods (from plug-ins) to generate a preview
+ NSArray *specialTypes = [[QSReg tableNamed:@"QSFSFileTypePreviewers"] allKeys];
+ for (NSString *type in specialTypes) {
+ if (UTTypeConformsTo((CFStringRef)uti, (CFStringRef)type)) {
+ id provider = [QSReg instanceForKey:type inTable:@"QSFSFileTypePreviewers"];
+ if (provider) {
+ //NSLog(@"provider %@", [QSReg tableNamed:@"QSFSFileTypePreviewers"]);
+ theImage = [provider iconForFile:path ofType:type];
+ break;
+ }
+ }
+ }
+ if (!theImage) {
+ NSArray *previewTypes = [[NSUserDefaults standardUserDefaults] objectForKey:@"QSFilePreviewTypes"];
+ for (NSString *type in previewTypes) {
+ if (UTTypeConformsTo((CFStringRef)uti, (CFStringRef)type)) {
+ // do preview icon loading in separate thread
+ theImage = [NSImage imageWithPreviewOfFileAtPath:path ofSize:QSMaxIconSize asIcon:YES];
+ break;
+ }
+ }
+ }
+ }
+ // if no of the other methods worked or previews are disabled: just use icon for filetype
if (!theImage) {
theImage = [[NSWorkspace sharedWorkspace] iconForFile:path];
}
-
+
theImage = [self prepareImageforIcon:theImage];
-
+
[object setIcon:theImage];
[[NSNotificationCenter defaultCenter] postNotificationName:QSObjectIconModified object:object];
}
--(NSImage *)prepareImageforIcon:(NSImage *)theImage {
+- (NSImage *)prepareImageforIcon:(NSImage *)theImage {
// last fallback, other methods didn't work
if (!theImage) theImage = [QSResourceManager imageNamed:@"GenericQuestionMarkIcon"];
@@ -332,7 +243,7 @@ -(NSImage *)prepareImageforIcon:(NSImage *)theImage {
[theImage createRepresentationOfSize:NSMakeSize(32, 32)];
[theImage createRepresentationOfSize:NSMakeSize(16, 16)];
}
-
+
// remove all image representations that are larger then QSMaxIconSize
// not really sure if this is needed or even makes sense
// but it was in here before, but only removing exactly the
@@ -343,7 +254,7 @@ -(NSImage *)prepareImageforIcon:(NSImage *)theImage {
[theImage removeRepresentation:imgRep];
}
}
-
+
return theImage;
}
@@ -351,19 +262,16 @@ - (BOOL)objectHasChildren:(QSObject *)object {
BOOL isDirectory;
NSString *path = [object singleFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) {
-
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path] , kLSRequestBasicFlagsOnly, &infoRec);
-
+
// A plain folder (not a package) has children
- if (isDirectory && !(infoRec.flags & kLSItemInfoIsPackage)) {
+ if ([object isDirectory] && ![object isPackage]) {
return YES;
}
// If it's an app check to see if there's a handler for it (e.g. a plugin) or if there are recent documents
- if (infoRec.flags & kLSItemInfoIsApplication) {
+ if ([object isApplication]) {
NSString *bundleIdentifier = [[NSBundle bundleWithPath:path] bundleIdentifier];
-
+
// Does the app have an external handler? (e.g. a plugin)
NSString *handlerName = [[QSReg tableNamed:@"QSBundleChildHandlers"] objectForKey:bundleIdentifier];
if (handlerName) {
@@ -384,30 +292,27 @@ - (BOOL)objectHasChildren:(QSObject *)object {
}
}
}
-
- // If there is a valid file parser (text or HTML parser) the object has children
- NSString *uti = QSUTIWithLSInfoRec(path, &infoRec);
+
+ // If there is a valid file parser (text or HTML parser) the object has children
+ NSString *uti = [object fileUTI];
id <QSParser> parser = [QSReg instanceForKey:uti inTable:@"QSFSFileTypeParsers"];
if (parser) {
return YES;
}
-
+
// An alias has children (the resolved file)
- if (infoRec.flags & kLSItemInfoIsAliasFile) {
+ if ([object isAlias]) {
return YES;
}
}
-
return NO;
-
}
+
- (BOOL)objectHasValidChildren:(QSObject *)object {
if ([object fileCount] == 1) {
NSString *path = [object singleFilePath];
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path] , kLSRequestBasicFlagsOnly, &infoRec);
- if (infoRec.flags & kLSItemInfoIsApplication) {
+ if ([object isApplication]) {
NSString *bundleIdentifier = [[NSBundle bundleWithPath:path] bundleIdentifier];
NSString *handlerName = [[QSReg tableNamed:@"QSBundleChildHandlers"] objectForKey:bundleIdentifier];
if (handlerName) {
@@ -422,41 +327,41 @@ - (BOOL)objectHasValidChildren:(QSObject *)object {
}
NSTimeInterval modDate = [[[[NSFileManager defaultManager] attributesOfItemAtPath:path error:NULL] fileModificationDate] timeIntervalSinceReferenceDate];
- if (modDate>[object childrenLoadedDate]) return NO;
+ if (modDate > [object childrenLoadedDate]) return NO;
}
return YES;
}
-- (NSDragOperation) operationForDrag:(id <NSDraggingInfo>)sender ontoObject:(QSObject *)dObject withObject:(QSBasicObject *)iObject {
+- (NSDragOperation)operationForDrag:(id <NSDraggingInfo>)sender ontoObject:(QSObject *)dObject withObject:(QSBasicObject *)iObject {
if (![iObject arrayForType:QSFilePathType])
return 0;
- if ([dObject fileCount] >1)
+ if ([dObject fileCount] > 1)
return NSDragOperationGeneric;
NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
if ([dObject isApplication])
return NSDragOperationPrivate;
- else if ([dObject isFolder]) {
- NSDragOperation defaultOp = [[NSFileManager defaultManager] defaultDragOperationForMovingPaths:[iObject validPaths] toDestination:[dObject singleFilePath]];
+ else if ([dObject isDirectory]) {
+ NSDragOperation defaultOp = [[NSFileManager defaultManager] defaultDragOperationForMovingPaths:[iObject validPaths] toDestination:[dObject singleFilePath]];
if (defaultOp == NSDragOperationMove) {
- if (sourceDragMask&NSDragOperationMove)
+ if (sourceDragMask & NSDragOperationMove)
return NSDragOperationMove;
- else if (sourceDragMask&NSDragOperationCopy)
+ else if (sourceDragMask & NSDragOperationCopy)
return NSDragOperationCopy;
} else if (defaultOp == NSDragOperationCopy)
return NSDragOperationCopy;
}
- return sourceDragMask&NSDragOperationGeneric;
+ return sourceDragMask & NSDragOperationGeneric;
}
- (NSString *)actionForDragMask:(NSDragOperation)operation ontoObject:(QSObject *)dObject withObject:(QSBasicObject *)iObject {
- if ([dObject fileCount] >1)
+ if ([dObject fileCount] > 1)
return 0;
if ([dObject isApplication]) {
return @"FileOpenWithAction";
- } else if ([dObject isFolder]) {
- if (operation&NSDragOperationMove)
+ } else if ([dObject isDirectory]) {
+ if (operation & NSDragOperationMove)
return @"FileMoveToAction";
- else if (operation&NSDragOperationCopy)
+ else if (operation & NSDragOperationCopy)
return @"FileCopyToAction";
}
return 0;
@@ -477,50 +382,53 @@ - (BOOL)loadChildrenForObject:(QSObject *)object {
if ([object fileCount] == 1) {
NSString *path = [object singleFilePath];
- if (![path length]) return NO;
- BOOL isDirectory;
+ if (!path || ![path length]) return NO;
NSFileManager *manager = [NSFileManager defaultManager];
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path], kLSRequestAllInfo, &infoRec);
- [(NSString*)infoRec.extension autorelease];
-
- if (infoRec.flags & kLSItemInfoIsAliasFile) {
+ if ([object isAlias]) {
+ /* Resolve the alias before loading its children */
+ BOOL isDirectory;
path = [manager resolveAliasAtPath:path];
- if ([manager fileExistsAtPath:path isDirectory:&isDirectory] && !isDirectory) {
+ if (![manager fileExistsAtPath:path isDirectory:&isDirectory]) {
+ /* Alias can't be resolved : no children */
+ return NO;
+ } else if (!isDirectory) {
+ /* Alias is a file, set it as object's child */
[object setChildren:[NSArray arrayWithObject:[QSObject fileObjectWithPath:path]]];
return YES;
}
}
- NSMutableArray *fileChildren = [NSMutableArray arrayWithCapacity:1];
- NSMutableArray *visibleFileChildren = [NSMutableArray arrayWithCapacity:1];
-
- NSError *err = nil;
- // pre-fetch the required info (hidden key) for the dir contents to speed up the task
- NSArray *dirContents = [manager contentsOfDirectoryAtURL:[NSURL fileURLWithPath:path] includingPropertiesForKeys:[NSArray arrayWithObject:NSURLIsHiddenKey] options:0 error:&err];
- if (err) {
- NSLog(@"Error loading files: %@",err);
- }
- for (NSURL *individualURL in dirContents) {
- [fileChildren addObject:[individualURL path]];
- NSNumber *isHidden = 0;
- [individualURL getResourceValue:&isHidden forKey:NSURLIsHiddenKey error:nil];
- if (![isHidden boolValue]) {
- [visibleFileChildren addObject:[individualURL path]];
+ if ([object isDirectory]) {
+ NSMutableArray *fileChildren = [NSMutableArray arrayWithCapacity:1];
+ NSMutableArray *visibleFileChildren = [NSMutableArray arrayWithCapacity:1];
+
+ NSError *err = nil;
+ // pre-fetch the required info (hidden key) for the dir contents to speed up the task
+ NSArray *dirContents = [manager contentsOfDirectoryAtURL:[NSURL fileURLWithPath:path] includingPropertiesForKeys:[NSArray arrayWithObject:NSURLIsHiddenKey] options:0 error:&err];
+ if (!dirContents) {
+ NSLog(@"Error loading files: %@", err);
+ return NO;
}
- }
- // sort the files like Finder does. Note: Casting array to NSMutable array so don't try and alter these arrays later on
- fileChildren = (NSMutableArray *)[fileChildren sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
- visibleFileChildren = (NSMutableArray *)[visibleFileChildren sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
-
+ for (NSURL *individualURL in dirContents) {
+ [fileChildren addObject:[individualURL path]];
+ NSNumber *isHidden = 0;
+ [individualURL getResourceValue:&isHidden forKey:NSURLIsHiddenKey error:nil];
+ if (![isHidden boolValue]) {
+ [visibleFileChildren addObject:[individualURL path]];
+ }
+ }
+ // sort the files like Finder does. Note: Casting array to NSMutable array so don't try and alter these arrays later on
+ fileChildren = (NSMutableArray *)[fileChildren sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
+ visibleFileChildren = (NSMutableArray *)[visibleFileChildren sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
- newChildren = [QSObject fileObjectsWithPathArray:visibleFileChildren];
- newAltChildren = [QSObject fileObjectsWithPathArray:fileChildren];
+ newChildren = [QSObject fileObjectsWithPathArray:visibleFileChildren];
+ newAltChildren = [QSObject fileObjectsWithPathArray:fileChildren];
- if (newAltChildren) [object setAltChildren:newAltChildren];
+ if (newAltChildren) [object setAltChildren:newAltChildren];
+ }
- if (infoRec.flags & kLSItemInfoIsApplication) {
+ if ([object isApplication]) {
// ***warning * omit other types of bundles
//newChildren = nil;
@@ -533,13 +441,7 @@ - (BOOL)loadChildrenForObject:(QSObject *)object {
if (handler) {
return [handler loadChildrenForObject:object];
} else {
- if (!bundlePresetChildren) {
- bundlePresetChildren = [QSReg tableNamed:@"QSBundleChildPresets"];
- //[[NSDictionary dictionaryWithContentsOfFile:
- // [[NSBundle mainBundle] pathForResource:@"BundleChildPresets" ofType:@"plist"]]retain];
- }
-
- NSString *childPreset = [bundlePresetChildren objectForKey:bundleIdentifier];
+ NSString *childPreset = [[QSReg tableNamed:@"QSBundleChildPresets"] objectForKey:bundleIdentifier];
if (childPreset) {
#ifdef DEBUG
if (VERBOSE) NSLog(@"using preset %@", childPreset);
@@ -562,11 +464,11 @@ - (BOOL)loadChildrenForObject:(QSObject *)object {
}
}
- } else if ((infoRec.flags & kLSItemInfoIsPackage) || !(infoRec.flags & kLSItemInfoIsContainer) ) {
+ } else if ([object isPackage] || ![object isDirectory]) {
//NSString *type = [[NSFileManager defaultManager] typeOfFile:path];
- NSString *uti = QSUTIWithLSInfoRec(path, &infoRec);
-
+ NSString *uti = [object fileUTI];
+
id <QSParser> parser = [QSReg instanceForKey:uti inTable:@"QSFSFileTypeParsers"];
NSArray *children = [parser objectsFromPath:path withSettings:nil];
if (children) {
@@ -581,33 +483,27 @@ - (BOOL)loadChildrenForObject:(QSObject *)object {
}
if (newChildren) [object setChildren:newChildren];
-
+
return YES;
}
- (NSArray *)actionsForDirectObject:(QSObject *)dObject indirectObject:(QSObject *)iObject {
- NSString *path = [dObject objectForType:QSFilePathType];
- if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:nil]) {
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path], kLSRequestBasicFlagsOnly, &infoRec);
- if (infoRec.flags & kLSItemInfoIsApplication) {
- NSMutableArray *actions = (NSMutableArray *)[QSExec validActionsForDirectObject:dObject indirectObject:iObject];
- NSString *bundleIdentifier = [[NSBundle bundleWithPath:path] bundleIdentifier];
-
- //NSLog(@"actions %d", [actions count]);
- NSDictionary *appActions = [[QSReg tableNamed:@"QSApplicationActions"] objectForKey:bundleIdentifier];
- if([appActions count]) {
+ if ([dObject isApplication]) {
+ NSMutableArray *actions = (NSMutableArray *)[QSExec validActionsForDirectObject:dObject indirectObject:iObject];
+ NSString *path = [dObject singleFilePath];
+ NSString *bundleIdentifier = [[NSBundle bundleWithPath:path] bundleIdentifier];
+
+ //NSLog(@"actions %d", [actions count]);
+ NSDictionary *appActions = [[QSReg tableNamed:@"QSApplicationActions"] objectForKey:bundleIdentifier];
+ if([appActions count]) {
for(NSString *actionID in appActions) {
NSDictionary *actionDict = [appActions objectForKey:actionID];
actionDict = [[actionDict copy] autorelease];
[actions addObject:[QSAction actionWithDictionary:actionDict identifier:actionID]];
}
- }
- // NSLog(@"actions %d", [actions count]);
-
- return actions;
-
}
+ // NSLog(@"actions %d", [actions count]);
+ return actions;
}
return nil;
}
@@ -616,8 +512,7 @@ - (NSArray *)actionsForDirectObject:(QSObject *)dObject indirectObject:(QSObject
@implementation QSBasicObject (FileHandling)
-- (NSString *)singleFilePath {return [self objectForType:QSFilePathType];
-}
+- (NSString *)singleFilePath {return [self objectForType:QSFilePathType];}
- (NSString *)validSingleFilePath {
NSString *path = [self objectForType:QSFilePathType];
@@ -713,13 +608,13 @@ - (id)initWithArray:(NSArray *)paths {
[existingObject retain];
return existingObject;
}
-
+
// if no previous object has been created, then create a new one
if (self = [self init]) {
if ([paths count] == 1) {
NSString *path = [paths lastObject];
[[self dataDictionary] setObject:path forKey:QSFilePathType];
- NSString *uti = QSUTIOfFile(path);
+ NSString *uti = [self fileUTI];
id handler = [QSReg instanceForKey:uti inTable:@"QSFileObjectCreationHandlers"];
if (handler) {
if ([handler respondsToSelector:@selector(createFileObject:ofType:)])
@@ -739,27 +634,96 @@ - (id)initWithArray:(NSArray *)paths {
return self;
}
-// Checks to see if the object in question is an application
-- (BOOL)isApplication {
- NSString *path = [self singleFilePath];
- if(!path) {
- return NO;
+- (NSDictionary *)infoRecord {
+ NSDictionary *dict;
+ if (dict = [self objectForCache:@"QSItemInfoRecord"])
+ return dict;
+
+ NSString *path = [self validSingleFilePath];
+ if (!path)
+ return nil;
+
+ /* Try to get information for this file */
+ LSItemInfoRecord record;
+ OSStatus status = LSCopyItemInfoForURL((CFURLRef)[NSURL fileURLWithPath:path], kLSRequestAllInfo, &record);
+ if (status) {
+ NSLog(@"LSCopyItemInfoForURL error: %ld", (long)status);
+ return nil;
+ }
+
+ NSString *uti = QSUTIForExtensionOrType((NSString *)record.extension, record.filetype);
+ NSString *extension = [(NSString *)record.extension copy];
+
+ /* local or network volume? does it support Trash? */
+ struct statfs sfsb;
+ statfs([path UTF8String], &sfsb);
+ NSString *device = [NSString stringWithCString:sfsb.f_mntfromname encoding:NSUTF8StringEncoding];
+ BOOL isLocal = [device hasPrefix:@"/dev/"];
+
+ /* Now build a dictionary with that record */
+ NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithUnsignedInt:record.flags], @"flags",
+ [NSValue valueWithOSType:record.filetype], @"filetype",
+ [NSValue valueWithOSType:record.creator], @"creator",
+ [NSNumber numberWithBool:isLocal], @"localVolume",
+ nil];
+ if (uti) {
+ [tempDict setObject:uti forKey:@"uti"];
}
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path], kLSRequestBasicFlagsOnly, &infoRec);
- return (infoRec.flags & kLSItemInfoIsApplication);
+ if (extension) {
+ [tempDict setObject:extension forKey:@"extension"];
+ }
+ dict = [NSDictionary dictionaryWithDictionary:tempDict];
+ /* Release the file's extension if one was returned */
+ if (record.extension)
+ CFRelease(record.extension);
+
+ [self setObject:dict forCache:@"QSItemInfoRecord"];
+ return dict;
+}
+
+- (BOOL)checkInfoRecordFlags:(LSItemInfoFlags)infoFlags {
+ NSDictionary *infoRec = [self infoRecord];
+ NSNumber *fileFlags = [infoRec objectForKey:@"flags"];
+ if (!fileFlags)
+ return NO;
+ unsigned int numFlags = [fileFlags unsignedIntValue];
+ return numFlags & infoFlags;
+}
+
+- (BOOL)isApplication {
+ return [self checkInfoRecordFlags:kLSItemInfoIsApplication];
+}
+
+- (BOOL)isDirectory {
+ return [self checkInfoRecordFlags:kLSItemInfoIsContainer];
}
- (BOOL)isFolder {
- BOOL isDirectory;
- return ([[NSFileManager defaultManager] fileExistsAtPath:[self singleFilePath] isDirectory:&isDirectory]) ? isDirectory : NO;
+ return ([self isDirectory] && ![self isPackage]);
+}
+
+- (BOOL)isPackage {
+ return [self checkInfoRecordFlags:kLSItemInfoIsPackage];
+}
+
+- (BOOL)isAlias {
+ return [self checkInfoRecordFlags:kLSItemInfoIsAliasFile];
}
-- (NSString *)localizedPrefPaneKind {
- static NSString *prefPaneKindString = nil;
- if (!prefPaneKindString)
- prefPaneKindString = [[[NSBundle bundleWithPath:[[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"com.apple.systempreferences"]] localizedStringForKey:@"PREF_PANE" value:@" Preferences" table:nil] retain];
- return prefPaneKindString;
+- (BOOL)isOnLocalVolume {
+ NSDictionary *infoRec = [self infoRecord];
+ return [[infoRec objectForKey:@"localVolume"] boolValue];
+}
+
+- (NSString *)fileExtension {
+ NSDictionary *infoRec = [self infoRecord];
+ return [infoRec objectForKey:@"extension"];
+}
+
+- (NSString *)fileUTI {
+ NSDictionary *infoRec = [self infoRecord];
+ return [infoRec objectForKey:@"uti"];
}
- (NSString *)bundleNameFromInfoDict:(NSDictionary *)infoDict {
@@ -806,7 +770,6 @@ - (NSString *)descriptiveNameForPackage:(NSString *)path withKindSuffix:(BOOL)in
}
- (void)getNameFromFiles {
- NSFileManager *manager = [[NSFileManager alloc] init];
NSString *newName = nil;
NSString *newLabel = nil;
if ([self count] >1) {
@@ -827,10 +790,8 @@ - (void)getNameFromFiles {
newName = [path lastPathComponent];
}
// check packages for a descriptive name
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:path] , kLSRequestBasicFlagsOnly, &infoRec);
- if (infoRec.flags & kLSItemInfoIsPackage) {
- newLabel = [self descriptiveNameForPackage:(NSString *)path withKindSuffix:!(infoRec.flags & kLSItemInfoIsApplication)];
+ if ([self isPackage]) {
+ newLabel = [self descriptiveNameForPackage:path withKindSuffix:![self isApplication]];
}
// look for a more suitable display name
if (!newLabel || [newLabel isEqualToString:newName]) {
@@ -840,13 +801,12 @@ - (void)getNameFromFiles {
newLabel = [(NSString *)MDItemCopyAttribute(mdItem, kMDItemDisplayName) autorelease];
}
if (!newLabel) {
- newLabel = [manager displayNameAtPath:path];
+ newLabel = [[NSFileManager defaultManager] displayNameAtPath:path];
}
}
// discard the label if it's still identical to name
if ([newLabel isEqualToString:newName]) newLabel = nil;
}
- [manager release];
[self setName:newName];
[self setLabel:newLabel];
}
@@ -908,12 +868,5 @@ - (NSString *)kindOfFile:(NSString *)path {
return (!path || LSCopyKindStringForURL((CFURLRef) [NSURL fileURLWithPath:path], (CFStringRef *)&kind)) ? nil : [kind autorelease];
}
-#if 0
-- (QSObject *)fileObjectByMergingWith:(QSObject *)mergeObject {
- // NSArray *moreFiles = [[mergeObject dataDictionary] objectForKey:QSFilePathType;
- return nil;
-}
-#endif
-
@end
View
7 Quicksilver/Code-QuickStepCore/QSPlugIn.m
@@ -746,13 +746,8 @@ - (BOOL)registerPlugIn {
@try {
[self _registerPlugIn];
} @catch (NSException *exc) {
-#ifdef DEBUG
NSString *errorMessage = [NSString stringWithFormat:@"An error ocurred while loading plugin \"%@\": %@", self, exc];
- if (VERBOSE) {
- NSLog(@"%@", errorMessage);
- [exc printStackTrace];
- }
-#endif
+ NSLog(@"%@", errorMessage);
[self setLoadError:[exc reason]];
}
// write an empty file to the state location since QS launched fine
View
41 Quicksilver/Code-QuickStepCore/QSRegistry.m
@@ -142,33 +142,36 @@ - (id)getClassInstance:(NSString *)className {
if (instance = [classInstances objectForKey:className]) return instance;
Class providerClass = NSClassFromString(className);
- //NSLog(@"Class <%@>", NSStringFromClass(providerClass) );
if (!providerClass) {
NSBundle * bundle = [classBundles objectForKey:className];
- NSError *err = nil;
- if([bundle loadAndReturnError:&err] == NO) {
- NSLog(@"Failed loading bundle %@", bundle);
-#ifdef DEBUG
- if (err) {
- NSLog(@"Error: %@",err);
+ if (bundle) {
+ NSError *err = nil;
+ if (![bundle loadAndReturnError:&err]) {
+ NSLog(@"Failed loading bundle %@ error: %@", bundle, err);
}
-#endif
+ providerClass = NSClassFromString(className);
}
- providerClass = NSClassFromString(className);
}
- if (providerClass) {
- if ([providerClass respondsToSelector:@selector(sharedInstance)])
- instance = [providerClass sharedInstance];
- else
- instance = [[[providerClass alloc] init] autorelease];
- [classInstances setObject:instance forKey:className];
- return instance;
+ if (!providerClass) {
#ifdef DEBUG
- } else {
if (VERBOSE) NSLog(@"Can't find class %@ %@", className, [classBundles objectForKey:className]);
#endif
- }
- return nil;
+ return nil;
+ }
+ if ([providerClass respondsToSelector:@selector(sharedInstance)])
+ instance = [providerClass sharedInstance];
+ else {
+ @try {
+ instance = [[[providerClass alloc] init] autorelease];
+ }
+ @catch (NSException *exception) {
+#ifdef DEBUG
+ NSLog(@"Failed to instantiate provider for class %@, exception: %@", className, exception);
+#endif
+ }
+ }
+ [classInstances setObject:instance forKey:className];
+ return instance;
}
- (NSMutableDictionary *)identifierBundles { return identifierBundles; }
View
2 Quicksilver/Code-QuickStepFoundation/NSFileManager_BLTRExtensions.h
@@ -7,8 +7,6 @@
//
#import <Foundation/Foundation.h>
-NSString *QSUTIOfFile(NSString *path);
-NSString *QSUTIWithLSInfoRec(NSString *path, LSItemInfoRecord *infoRec);
@interface NSFileManager (Carbon)
- (BOOL)isVisible:(NSString *)chem;
View
86 Quicksilver/Code-QuickStepFoundation/NSFileManager_BLTRExtensions.m
@@ -39,7 +39,16 @@ - (NSString *)humanReadableFiletype:(NSString *)path {
#endif
- (BOOL)movePathToTrash:(NSString *)filepath {
- return [self moveItemAtPath:filepath toPath:[[[@"~/.Trash/" stringByStandardizingPath] stringByAppendingPathComponent:[filepath lastPathComponent]] firstUnusedFilePath] error:nil];
+ NSWorkspace *ws = [NSWorkspace sharedWorkspace];
+ BOOL result = [ws performFileOperation:NSWorkspaceRecycleOperation
+ source:[filepath stringByDeletingLastPathComponent]
+ destination:@""
+ files:[NSArray arrayWithObject:[filepath lastPathComponent]]
+ tag:nil];
+ if (!result) {
+ NSLog(@"Failed to move file %@ to Trash", filepath);
+ }
+ return result;
}
#if 0
@@ -77,52 +86,6 @@ - (BOOL)movePathToTrashUsingFinder:(NSString *)filepath {
@end
-NSString *QSUTIWithLSInfoRec(NSString *path, LSItemInfoRecord *infoRec);
-
-NSString *QSUTIOfURL(NSURL *fileURL) {
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef)fileURL, kLSRequestTypeCreator|kLSRequestBasicFlagsOnly, &infoRec);
- return QSUTIWithLSInfoRec([fileURL path], &infoRec);
-}
-
-NSString *QSUTIOfFile(NSString *path) {
- LSItemInfoRecord infoRec;
- LSCopyItemInfoForURL((CFURLRef)[NSURL fileURLWithPath:path], kLSRequestTypeCreator|kLSRequestBasicFlagsOnly, &infoRec);
- return QSUTIWithLSInfoRec(path, &infoRec);
-}
-
-NSString *QSUTIWithLSInfoRec(NSString *path, LSItemInfoRecord *infoRec) {
- NSString *extension = [path pathExtension];
- if (![extension length])
- extension = nil;
- BOOL isDirectory;
- if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory])
- return nil;
-
- if (infoRec->flags & kLSItemInfoIsAliasFile)
- return (NSString *)kUTTypeAliasFile;
- if (infoRec->flags & kLSItemInfoIsVolume)
- return (NSString *)kUTTypeVolume;
-
- NSString *extensionUTI = [(NSString *)UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, (CFStringRef)extension, NULL) autorelease];
- if (extensionUTI && ![extensionUTI hasPrefix:@"dyn"])
- return extensionUTI;
-
- NSString *hfsType = [(NSString *)UTCreateStringForOSType(infoRec->filetype) autorelease];
- if (![hfsType length] && isDirectory)
- return (NSString *)kUTTypeFolder;
-
- NSString *hfsUTI = [(NSString *)UTTypeCreatePreferredIdentifierForTag (kUTTagClassOSType, (CFStringRef)hfsType, NULL) autorelease];
- if (![hfsUTI hasPrefix:@"dyn"])
- return hfsUTI;
-
- if ([[NSFileManager defaultManager] isExecutableFileAtPath:path])
- return @"public.executable";
-
- return (extensionUTI?extensionUTI:hfsUTI);
-}
-
-
@implementation NSFileManager (Scanning)
- (NSString *)UTIOfFile:(NSString *)path {
return QSUTIOfFile(path);
@@ -202,32 +165,27 @@ - (NSString *)fullyResolvedPathForPath:(NSString *)sourcePath {
}
- (NSString *)resolveAliasAtPath:(NSString *)aliasFullPath {
- NSString *outString = nil;
- NSURL *url;
- FSRef aliasRef;
- Boolean targetIsFolder;
- Boolean wasAliased;
-
- if (!CFURLGetFSRef((CFURLRef) [NSURL fileURLWithPath:aliasFullPath], &aliasRef) || FSResolveAliasFileWithMountFlags(&aliasRef, true, &targetIsFolder, &wasAliased, kResolveAliasFileNoUI) != noErr)
- return nil;
- if (url = (NSURL *)CFURLCreateFromFSRef(kCFAllocatorDefault, &aliasRef)) {
- outString = [url path];
- CFRelease(url);
- return outString;
- }
- return nil;
+ return [self resolveAliasAtPath:aliasFullPath usingUI:NO];
}
- (NSString *)resolveAliasAtPathWithUI:(NSString *)aliasFullPath {
+ return [self resolveAliasAtPath:aliasFullPath usingUI:YES];
+}
+
+- (NSString *)resolveAliasAtPath:(NSString *)aliasFullPath usingUI:(BOOL)usingUI {
NSString *outString = nil;
- NSURL *url;
+ NSURL *url = [NSURL fileURLWithPath:aliasFullPath];
FSRef aliasRef;
Boolean targetIsFolder;
Boolean wasAliased;
- if (!CFURLGetFSRef((CFURLRef) [NSURL fileURLWithPath:aliasFullPath] , &aliasRef) || FSResolveAliasFileWithMountFlags(&aliasRef, true, &targetIsFolder, &wasAliased, 0) != noErr)
+ if (!CFURLGetFSRef((CFURLRef)url, &aliasRef))
return nil;
- if (url = (NSURL *)CFURLCreateFromFSRef(kCFAllocatorDefault, &aliasRef) ) {
+
+ if (FSResolveAliasFileWithMountFlags(&aliasRef, true, &targetIsFolder, &wasAliased, (!usingUI ? kResolveAliasFileNoUI : 0)))
+ return nil;
+
+ if (url = (NSURL *)CFURLCreateFromFSRef(kCFAllocatorDefault, &aliasRef)) {
outString = [url path];
CFRelease(url);
return outString;
View
3 Quicksilver/Code-QuickStepFoundation/QSUTI.h
@@ -8,5 +8,8 @@
*/
+NSString *QSUTIOfFile(NSString *path);
+NSString *QSUTIOfURL(NSURL *url);
+NSString *QSUTIWithLSInfoRec(NSString *path, LSItemInfoRecord *infoRec);
NSString *QSUTIForAnyTypeString(NSString *type);
NSString *QSUTIForExtensionOrType(NSString *extension, OSType filetype);
View
67 Quicksilver/Code-QuickStepFoundation/QSUTI.m
@@ -9,6 +9,49 @@
#include "QSUTI.h"
+NSString *QSUTIOfURL(NSURL *fileURL) {
+ LSItemInfoRecord infoRec;
+ LSCopyItemInfoForURL((CFURLRef)fileURL, kLSRequestTypeCreator|kLSRequestBasicFlagsOnly, &infoRec);
+ return QSUTIWithLSInfoRec([fileURL path], &infoRec);
+}
+
+NSString *QSUTIOfFile(NSString *path) {
+ LSItemInfoRecord infoRec;
+ LSCopyItemInfoForURL((CFURLRef)[NSURL fileURLWithPath:path], kLSRequestTypeCreator|kLSRequestBasicFlagsOnly, &infoRec);
+ return QSUTIWithLSInfoRec(path, &infoRec);
+}
+
+NSString *QSUTIWithLSInfoRec(NSString *path, LSItemInfoRecord *infoRec) {
+ NSString *extension = [path pathExtension];
+ if (![extension length])
+ extension = nil;
+ BOOL isDirectory;
+ if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory])
+ return nil;
+
+ if (infoRec->flags & kLSItemInfoIsAliasFile)
+ return (NSString *)kUTTypeAliasFile;
+ if (infoRec->flags & kLSItemInfoIsVolume)
+ return (NSString *)kUTTypeVolume;
+
+ NSString *extensionUTI = [(NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, NULL) autorelease];
+ if (extensionUTI && ![extensionUTI hasPrefix:@"dyn"])
+ return extensionUTI;
+
+ NSString *hfsType = [(NSString *)UTCreateStringForOSType(infoRec->filetype) autorelease];
+ if (![hfsType length] && isDirectory)
+ return (NSString *)kUTTypeFolder;
+
+ NSString *hfsUTI = [(NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, (CFStringRef)hfsType, NULL) autorelease];
+ if (![hfsUTI hasPrefix:@"dyn"])
+ return hfsUTI;
+
+ if ([[NSFileManager defaultManager] isExecutableFileAtPath:path])
+ return @"public.executable";
+
+ return (extensionUTI ? extensionUTI : hfsUTI);
+}
+
NSString *QSUTIForAnyTypeString(NSString *type) {
NSString *itemUTI = NULL;
@@ -20,30 +63,26 @@
else
extension = type;
itemUTI = QSUTIForExtensionOrType(extension, filetype);
- if ([itemUTI hasPrefix:@"dyn"]) itemUTI = nil;
+ if ([itemUTI hasPrefix:@"dyn"])
+ itemUTI = nil;
return itemUTI;
}
NSString *QSUTIForExtensionOrType(NSString *extension, OSType filetype) {
NSString *itemUTI = nil;
- //NSLog(@"type %@ %@", extension, UTCreateStringForOSType(filetype) );
- if ( extension != nil ) {
- itemUTI = (NSString *)UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, (CFStringRef) extension, NULL);
+// NSLog(@"type %@ %@", extension, NSFileTypeForHFSTypeCode(filetype));
+ if (extension != nil) {
+ itemUTI = (NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, NULL);
} else {
- if (filetype == 'fold') return @"public.folder";
- itemUTI = (NSString *)UTTypeCreatePreferredIdentifierForTag (kUTTagClassOSType, (CFStringRef) [(NSString *)UTCreateStringForOSType(filetype) autorelease], NULL );
+ CFStringRef fileTypeUTI = UTCreateStringForOSType(filetype);
+ itemUTI = (NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, fileTypeUTI, NULL);
+ CFRelease(fileTypeUTI);
}
return [itemUTI autorelease];
}
+/* Deprecated */
NSString *QSUTIForInfoRec(NSString *extension, OSType filetype) {
- NSString *itemUTI = NULL;
- //NSLog(@"type %@ %@", extension, UTCreateStringForOSType(filetype) );
- if ( extension != NULL ) {
- itemUTI = (NSString *)UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, (CFStringRef) extension, NULL);
- } else {
- itemUTI = (NSString *)UTTypeCreatePreferredIdentifierForTag (kUTTagClassOSType, (CFStringRef)[(NSString *)UTCreateStringForOSType(filetype) autorelease], NULL );
- }
- return [itemUTI autorelease];
+ return QSUTIForExtensionOrType(extension, filetype);
}
View
3 Quicksilver/PlugIns-Main/QSCorePlugIn/Code/QSActionProvider_EmbeddedProviders.h
@@ -22,10 +22,7 @@
@interface FSActions : QSActionProvider {
- NSArray *universalApps;
}
-- (NSArray *)universalApps;
-- (void)setUniversalApps:(NSArray *)anUniversalApps;
- (BOOL)filesExist:(NSArray *)paths;
- (QSObject *)moveFiles:(QSObject *)dObject toFolder:(QSObject *)iObject;
- (QSObject *)copyFiles:(QSObject *)dObject toFolder:(QSObject *)iObject NS_RETURNS_NOT_RETAINED;
View
165 Quicksilver/PlugIns-Main/QSCorePlugIn/Code/QSActionProvider_EmbeddedProviders.m
@@ -276,36 +276,12 @@ - (QSObject *)performAction:(QSAction *)action directObject:(QSBasicObject *)dOb
@implementation FSActions
-- (NSArray *)universalApps {
- if (!universalApps) {
- QSTaskController *qstc = [QSTaskController sharedInstance];
- [qstc updateTask:@"Updating Application Database" status:@"Updating Applications" progress:-1];
- universalApps = (NSArray *)LSCopyApplicationURLsForURL((CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"wildcard" ofType:@"*"]], kLSRolesAll);
- [qstc removeTask:@"Updating Application Database"];
- }
- [self performSelector:@selector(setUniversalApps:) withObject:nil afterDelay:10*MINUTES extend:YES];
- return universalApps;
-}
-
-- (void)setUniversalApps:(NSArray *)anUniversalApps {
- if (universalApps != anUniversalApps) {
- [universalApps release];
- universalApps = [anUniversalApps retain];
- }
-}
-
-- (void)dealloc {
- [universalApps release];
- [super dealloc];
-}
-
// This method validates the 3rd pane for the core plugin actions
- (NSArray *)validIndirectObjectsForAction:(NSString *)action directObject:(QSObject *)dObject {
// Only return an array if the dObject is a file
if(![dObject validPaths]) {
return nil;
}
- NSMutableArray *validIndirects = [NSMutableArray arrayWithCapacity:1];
if ([action isEqualToString:kFileOpenWithAction]) {
NSURL *fileURL = nil;
// comma trick - get a list of apps based on the 1st selected file
@@ -315,19 +291,21 @@ - (NSArray *)validIndirectObjectsForAction:(NSString *)action directObject:(QSOb
if (fileURL) LSGetApplicationForURL((CFURLRef) fileURL, kLSRolesAll, NULL, (CFURLRef *)&appURL);
- NSMutableSet *set = [NSMutableSet set];
-
- [set addObjectsFromArray:[(NSArray *)LSCopyApplicationURLsForURL((CFURLRef)fileURL, kLSRolesAll) autorelease]];
- [set addObjectsFromArray:[self universalApps]];
-
- validIndirects = [[QSLibrarian sharedInstance] scoredArrayForString:nil inSet:[QSObject fileObjectsWithURLArray:[set allObjects]]];
-
+ NSMutableArray *fileObjects = [[QSLib arrayForType:QSFilePathType] mutableCopy];
+
id preferred = [QSObject fileObjectWithPath:[appURL path]];
- if (!preferred)
- preferred = [NSNull null];
+ if (preferred) {
+ [fileObjects removeObject:preferred];
+ [fileObjects insertObject:preferred atIndex:0];
+ }
+
+ NSIndexSet *applicationIndexes = [fileObjects indexesOfObjectsWithOptions:NSEnumerationConcurrent passingTest:^BOOL(QSObject *thisObject, NSUInteger i, BOOL *stop) {
+ return ([thisObject isApplication]);
+ }];
[appURL release];
- return [NSArray arrayWithObjects:preferred, validIndirects, nil];
+ [fileObjects autorelease];
+ return [fileObjects objectsAtIndexes:applicationIndexes];
} else if ([action isEqualToString:kFileRenameAction]) {
// return a text object (empty text box) to rename a file
NSString *path = [dObject singleFilePath];
@@ -336,9 +314,8 @@ - (NSArray *)validIndirectObjectsForAction:(NSString *)action directObject:(QSOb
} else if ([action isEqualToString:@"QSNewFolderAction"]) {
return [NSArray arrayWithObject:[QSObject textProxyObjectWithDefaultValue:@"untitled folder"]];
} else if ([action isEqualToString:kFileMoveToAction] || [action isEqualToString:kFileCopyToAction]) {
- // We only want folders for the move to / copy to actions (can't move to anything else)
+ // We only want folders for the move to / copy to actions (can't move to anything else)
NSMutableArray *fileObjects = [[[QSLibrarian sharedInstance] arrayForType:QSFilePathType] mutableCopy];
- BOOL isDirectory;
NSString *currentFolderPath = [[[[dObject splitObjects] lastObject] singleFilePath] stringByDeletingLastPathComponent];
// if it wasn't in the catalog, create it from scratch
if (currentFolderPath) {
@@ -346,19 +323,11 @@ - (NSArray *)validIndirectObjectsForAction:(NSString *)action directObject:(QSOb
[fileObjects removeObject:currentFolderObject];
[fileObjects insertObject:currentFolderObject atIndex:0];
}
- NSWorkspace *ws = [[NSWorkspace sharedWorkspace] retain];
- NSFileManager *fm = [[NSFileManager alloc] init];
- for(QSObject *thisObject in fileObjects) {
- NSString *path = [thisObject singleFilePath];
- if ([fm fileExistsAtPath:path isDirectory:&isDirectory]) {
- if (isDirectory && ![ws isFilePackageAtPath:path])
- [validIndirects addObject:thisObject];
- }
- }
- [fileObjects release];
- [ws release];
- [fm release];
- return validIndirects;
+ NSIndexSet *folderIndexes = [fileObjects indexesOfObjectsWithOptions:NSEnumerationConcurrent passingTest:^BOOL(QSObject *thisObject, NSUInteger i, BOOL *stop) {
+ return [thisObject isFolder];
+ }];
+ [fileObjects autorelease];
+ return [fileObjects objectsAtIndexes:folderIndexes];
}
return nil;
}
@@ -377,6 +346,13 @@ - (NSArray *)validActionsForDirectObject:(QSObject *)dObject indirectObject:(QSO
[newActions addObject:kFileGetInfoAction];
// !!! Andre Berg 20091112: shouldn't the following also be added?
[newActions addObject:kFileAlwaysOpenWithAction];
+ // can all files be trashed?
+ for (QSObject *file in [dObject splitObjects]) {
+ if (![file isOnLocalVolume]) {
+ [newActions removeObject:kFileToTrashAction];
+ break;
+ }
+ }
}
if ([dObject validSingleFilePath])
[newActions addObject:kFileRenameAction];
@@ -395,30 +371,25 @@ - (BOOL)filesExist:(NSArray *)paths {
- (QSObject *)openFile:(QSObject *)dObject {
NSFileManager *manager = [NSFileManager defaultManager];
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
- LSItemInfoRecord infoRec;
- for(NSString *thisFile in [dObject validPaths]) {
- LSCopyItemInfoForURL((CFURLRef) [NSURL fileURLWithPath:thisFile] , kLSRequestBasicFlagsOnly, &infoRec);
- if (!(infoRec.flags & kLSItemInfoIsContainer) || (infoRec.flags & kLSItemInfoIsPackage) || ![mQSFSBrowser openFile:thisFile]) {
- if (infoRec.flags & kLSItemInfoIsAliasFile) {
- NSString *aliasFile = [manager resolveAliasAtPathWithUI:thisFile];
- if (aliasFile && [manager fileExistsAtPath:aliasFile])
- thisFile = aliasFile;
- }
- NSString *fileHandler = [dObject objectForMeta:@"QSPreferredApplication"];
- if (fileHandler) {
+ for (QSObject *thisFile in [dObject splitObjects]) {
+ NSString *thisPath = [thisFile singleFilePath];
+ if ([thisFile isAlias]) {
+ NSString *aliasFile = [manager resolveAliasAtPathWithUI:thisPath];
+ if (aliasFile && [manager fileExistsAtPath:aliasFile])
+ thisPath = aliasFile;
+ }
+ NSString *fileHandler = [thisFile objectForMeta:@"QSPreferredApplication"];
+ if (fileHandler) {
#ifdef DEBUG
- if (VERBOSE) NSLog(@"Using %@", fileHandler);
+ if (VERBOSE) NSLog(@"Using %@", fileHandler);
#endif
- [ws openFile:thisFile withApplication:[ws absolutePathForAppBundleWithIdentifier:fileHandler]];
- } else {
-// if (![QSAction modifiersAreIgnored] && (GetCurrentKeyModifiers() & shiftKey)) { // Open in background
-// NSLog(@"Launching in Background");
-// [ws openFileInBackground:thisFile];
-// } else {
- [ws openFile:thisFile];
-// }
- }
- }
+ // don't open with this app on subsequent calls
+ [thisFile setObject:nil forMeta:@"QSPreferredApplication"];
+ [ws openFile:thisPath withApplication:[ws absolutePathForAppBundleWithIdentifier:fileHandler]];
+ } else if (![mQSFSBrowser openFile:thisPath]) { // try the File System Browser handler
+ // fallback to the workspace manager
+ [ws openFile:thisPath];
+ }
}
return nil;
}
@@ -514,29 +485,47 @@ - (QSBasicObject *)deleteFile:(QSObject *)dObject {
}
- (QSBasicObject *)trashFile:(QSObject *)dObject {
- NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSString *lastDeletedFile = nil;
+ BOOL trashed = NO;
+ NSMutableSet *failed = [[NSMutableSet alloc] init];
for(NSString *thisFile in [dObject arrayForType:QSFilePathType]) {
- [ws performFileOperation:NSWorkspaceRecycleOperation source:[thisFile stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[thisFile lastPathComponent]] tag:nil];
- [ws noteFileSystemChanged:[thisFile stringByDeletingLastPathComponent]];
- lastDeletedFile = thisFile;
+ // if at least one file was trashed
+ if ([[NSFileManager defaultManager] movePathToTrash:thisFile]) {
+ trashed = YES;
+ lastDeletedFile = thisFile;
+ } else {
+ [failed addObject:[thisFile lastPathComponent]];
+ }
}
- // get settings for playing sound
- Boolean isSet;
- CFIndex val = CFPreferencesGetAppIntegerValue(CFSTR("com.apple.sound.uiaudio.enabled"),
- CFSTR("com.apple.systemsound"),
- &isSet);
- if (val == 1 || !isSet) {
- // play trash sound
- CFURLRef soundURL = (CFURLRef)[NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"dragToTrash" ofType:@"aif"]];
- SystemSoundID soundId;
- AudioServicesCreateSystemSoundID(soundURL, &soundId);
- AudioServicesPlaySystemSound(soundId);
- }
+ if (trashed) {
+ // get settings for playing sound
+ Boolean isSet;
+ CFIndex val = CFPreferencesGetAppIntegerValue(CFSTR("com.apple.sound.uiaudio.enabled"),
+ CFSTR("com.apple.systemsound"),
+ &isSet);
+ if (val == 1 || !isSet) {
+ // play trash sound
+ CFURLRef soundURL = (CFURLRef)[NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"dragToTrash" ofType:@"aif"]];
+ SystemSoundID soundId;
+ AudioServicesCreateSystemSoundID(soundURL, &soundId);
+ AudioServicesPlaySystemSound(soundId);
+ }
+ }
+ if ([failed count]) {
+ //NSLog(@"unable to trash: %@", failed);
+ NSString *localizedErrorFormat = NSLocalizedStringFromTableInBundle(@"Unable to Trash:\n%@", nil, [NSBundle bundleForClass:[self class]], nil);
+ NSString *localizedTitle = NSLocalizedStringFromTableInBundle(@"Quicksilver Move to Trash", nil, [NSBundle bundleForClass:[self class]], nil);
+ NSString *errorMessage = [NSString stringWithFormat:localizedErrorFormat, [[failed allObjects] componentsJoinedByString:@", "]];
+ QSShowNotifierWithAttributes([NSDictionary dictionaryWithObjectsAndKeys:@"QSTrashFileFailed", QSNotifierType, [QSResourceManager imageNamed:@"AlertCautionIcon"], QSNotifierIcon, localizedTitle, QSNotifierTitle, errorMessage, QSNotifierText, nil]);
+ }
+ [failed release];
// return folder that contained the last file that was deleted
- return [QSObject fileObjectWithPath:[lastDeletedFile stringByDeletingLastPathComponent]];;
+ if (lastDeletedFile) {
+ return [QSObject fileObjectWithPath:[lastDeletedFile stringByDeletingLastPathComponent]];;
+ }
+ return nil;
}
- (QSObject *)openItemAtLogin:(QSObject *)dObject {
View
2 Quicksilver/PlugIns-Main/QSCorePlugIn/Code/QSAppleScriptActions.m