Monday, January 10, 2011

Trashing Files: Part 1 (Mac OS)

Most operating systems provide support for sending files to the "trash can" (or sometimes "recycle bin"). Inspired by a python project called "send2trash", I thought Factor should have a similar cross-platform library for trashing files.


First, we are going to define a trash vocabulary, and use a HOOK: that dispatches to the proper implementation, depending on which operating system you are running.

USING: combinators system vocabs.loader ;

IN: trash

HOOK: send-to-trash os ( path -- )

    { [ os macosx? ] [ "trash.macosx"  ] }
    { [ os unix?   ] [ "trash.unix"    ] }
    { [ os winnt?  ] [ "" ] }
} cond require


Next, we will create the trash.macosx vocabulary.

USING: alien.c-types alien.strings alien.syntax classes.struct
core-foundation io.encodings.utf8 kernel system trash ;

IN: trash.macosx

On the Mac OS, there are several methods of moving files to the trash. A good discussion on CocoaDev lists some of them. We are going to use the alien vocabulary to make calls into the File Manager in the CarbonCore.framework. Some functions will return an OSStatus flag (a signed 32-bit integer) to indicate if the operation succeeded. We will add a TYPEDEF: for it, and then define the GetMacOSStatusCommentString function that converts the status flag into a human readable error.

TYPEDEF: SInt32 OSStatus

FUNCTION: char* GetMacOSStatusCommentString ( OSStatus err ) ;

: check-err ( err -- )
    [ GetMacOSStatusCommentString utf8 alien>string throw ] 
    unless-zero ;

Many of the file operations act on an FSRef structure which represents a path within the file system. We will define the FSPathMakeRefWithOptions function which will allow us to create these references:

STRUCT: FSRef { hidden UInt8[80] } ;

TYPEDEF: UInt32 OptionBits

FUNCTION: OSStatus FSPathMakeRefWithOptions (
    UInt8* path,
    OptionBits options,
    FSRef* ref,
    Boolean* isDirectory
) ;

We can then make a <fs-ref> word for creating references, given a path to a file (or directory).

CONSTANT: kFSPathMakeRefDoNotFollowLeafSymlink HEX: 01

: <fs-ref> ( path -- fs-ref )
    utf8 string>alien
    FSRef <struct>
    [ f FSPathMakeRefWithOptions check-err ] keep ;

There are several ways of "trashing" files, but one recommended way is implemented by the FSMoveObjectToTrashSync function:

FUNCTION: OSStatus FSMoveObjectToTrashSync (
    FSRef* source,
    FSRef* target,
    OptionBits options
) ;

Implementing the send-to-trash word is now pretty straightforward:

CONSTANT: kFSFileOperationDefaultOptions HEX: 00

M: macosx send-to-trash ( path -- )
    <fs-ref> f kFSFileOperationDefaultOptions
    FSMoveObjectToTrashSync check-err ;

You can test this by creating a temporary file (e.g., /tmp/foo), sending it to the trash, and then verifying that it exists by looking in the Finder's Trash.

( scratchpad ) USING: trash io.encodings.ascii io.files ;

( scratchpad ) "" "/tmp/foo" ascii set-file-contents

( scratchpad ) "/tmp/foo" send-to-trash

Note: This method does not appear to support the "Put Back" functionality (to "undo" the trash operation). Perhaps there is some metadata that we can add (or a different function we can call) that will track the original file location so that the Finder knows where it should be restored to.

The code for this is on my Github.

No comments: