 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Rapte_Of_Suzaku
Joined: 29 Feb 2008 Posts: 900 Location: Vault 7
|
Posted: Sun Mar 14, 2010 9:51 pm Post subject: ShellFileOperation Library |
|
|
Provides access to Windows' built-in file operation system (move / copy / rename / delete files or folders with the standard Windows dialog and error UI). Based on SKAN's ShellFileOperation.
Important differences:- works with any build
- flags and such can be passed as strings. "FOF_SILENT|FOF_ALLOWUNDO" easier to understand/use than 0x44
- for users with objects (AutoHotkey_L and related), you get a more detailed return object containing rename mappings if requested
Requirements:
Content
Last edited by Rapte_Of_Suzaku on Thu Sep 30, 2010 3:27 pm; edited 12 times in total |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7295 Location: Australia
|
Posted: Mon Mar 15, 2010 8:33 am Post subject: |
|
|
It has one error: the loops replace bytes rather than characters. As you may know, each Unicode character is typically two bytes. It works okay when the string contains only ASCII characters (i.e. because the upper byte is always 0, not 124), but some Unicode characters might cause problems.
*(Unary deref) can only retrieve one byte at a time. Since that script was written, NumGet was introduced - it is more flexible, usually easier to understand, and (iirc) actually faster. Likewise for RtlFillMemory vs NumPut. The following should work in place of the current loops (untested):
| Code: | char_size := A_IsUnicode ? 2 : 1
char_type := A_IsUnicode ? "UShort" : "Char"
Loop % StrLen(fSource)
if NumGet(fSource, (A_Index-1)*char_size, char_type) = 124
NumPut(0, fSource, (A_Index-1)*char_size, char_type)
Loop % StrLen(fTarget)
if NumGet(fTarget, (A_Index-1)*char_size, char_type) = 124
NumPut(0, fTarget, (A_Index-1)*char_size, char_type)
|
To support Unicode and ANSI builds, you must call A_IsUnicode ? SHFileOperationW : SHFileOperationA.
| Code: | DllCall( "Shell32\SHFileOperation" . (A_IsUnicode ? "W" : "A"), UInt,&SHFILEOPSTRUCT )
| If the suffix is omitted in this case, AHKL will add the appropriate one automatically whereas standard AHK will fail.
Script Compatibility outlines some of this (specifically, the DllCall and NumPut/NumGet sections). |
|
| Back to top |
|
 |
Rapte_Of_Suzaku
Joined: 29 Feb 2008 Posts: 900 Location: Vault 7
|
Posted: Mon Mar 15, 2010 3:53 pm Post subject: |
|
|
I knew it was looping through bytes, but neglected to consider the possibility of catching a Unicode character by mistake...
Thank you for the reminder and then catching the DllCall inflexibility!!
Updated version:
| Code: |
; a function for letting windows handle the file operations (nice dialog and error handling etc.)
; Thanks to SKAN on the AHK forums for this code!
; Updated to work with AutoHotkey_L (Lexikos then helped make it truly correct)
FO_MOVE := 0x1
FO_COPY := 0x2
FO_DELETE := 0x3
FO_RENAME := 0x4
FOF_MULTIDESTFILES := 0x1 ; Indicates that the to member specifies multiple destination files (one for each source file) rather than one directory where all source files are to be deposited.
FOF_SILENT := 0x4 ; Does not display a progress dialog box.
FOF_RENAMEONCOLLISION := 0x8 ; Gives the file being operated on a new name (such as "Copy #1 of...") in a move, copy, or rename operation if a file of the target name already exists.
FOF_NOCONFIRMATION := 0x10 ; Responds with "yes to all" for any dialog box that is displayed.
FOF_ALLOWUNDO := 0x40 ; Preserves undo information, if possible. With del, uses recycle bin.
FOF_FILESONLY := 0x80 ; Performs the operation only on files if a wildcard filename (*.*) is specified.
FOF_SIMPLEPROGRESS := 0x100 ; Displays a progress dialog box, but does not show the filenames.
FOF_NOCONFIRMMKDIR := 0x200 ; Does not confirm the creation of a new directory if the operation requires one to be created.
FOF_NOERRORUI := 0x400 ; don't put up error UI
FOF_NOCOPYSECURITYATTRIBS := 0x800 ; dont copy file security attributes
FOF_NORECURSION := 0x1000 ; Only operate in the specified directory. Don't operate recursively into subdirectories.
FOF_NO_CONNECTED_ELEMENTS := 0x2000 ; Do not move connected files as a group (e.g. html file together with images). Only move the specified files.
FOF_WANTNUKEWARNING := 0x4000 ; Send a warning if a file is being destroyed during a delete operation rather than recycled. This flag partially overrides FOF_NOCONFIRMATION.
ShellFileOperation( fileO=0x0, fSource="", fTarget="", flags=0x0, ghwnd=0x0 )
{
If ( SubStr(fSource,0) != "|" )
fSource := fSource . "|"
If ( SubStr(fTarget,0) != "|" )
fTarget := fTarget . "|"
char_size := A_IsUnicode ? 2 : 1
char_type := A_IsUnicode ? "UShort" : "Char"
fsPtr := &fSource
Loop % StrLen(fSource)
if NumGet(fSource, (A_Index-1)*char_size, char_type) = 124
NumPut(0, fSource, (A_Index-1)*char_size, char_type)
ftPtr := &fTarget
Loop % StrLen(fTarget)
if NumGet(fTarget, (A_Index-1)*char_size, char_type) = 124
NumPut(0, fTarget, (A_Index-1)*char_size, char_type)
VarSetCapacity( SHFILEOPSTRUCT, 60, 0 ) ; Encoding SHFILEOPSTRUCT
NextOffset := NumPut( ghwnd, &SHFILEOPSTRUCT ) ; hWnd of calling GUI
NextOffset := NumPut( fileO, NextOffset+0 ) ; File operation
NextOffset := NumPut( fsPtr, NextOffset+0 ) ; Source file / pattern
NextOffset := NumPut( ftPtr, NextOffset+0 ) ; Target file / folder
NextOffset := NumPut( flags, NextOffset+0, 0, "Short" ) ; options
DllCall( "Shell32\SHFileOperation" . (A_IsUnicode ? "W" : "A"), UInt,&SHFILEOPSTRUCT )
Return NumGet( NextOffset+0 )
}
|
|
|
| Back to top |
|
 |
Rapte_Of_Suzaku
Joined: 29 Feb 2008 Posts: 900 Location: Vault 7
|
Posted: Sat Jul 10, 2010 1:37 am Post subject: |
|
|
I recently updated this function again. The new features this time are the ability to pass strings rather than numbers (pass "FO_MOVE" rather than 0x1) and a return code interpreter function.
example:
| Code: |
ShellFileOperation("FO_MOVE", "::::", "::::","FOF_ALLOWUNDO|FOF_SIMPLEPROGRESS|FOF_NOCONFIRMMKDIR|FOF_MULTIDESTFILES")
MsgBox % ErrorLevel
|
Here is the updated code:
| Code: |
ShellFileOperation( fileO=0x0, fSource="", fTarget="", flags=0x0, ghwnd=0x0 )
{
;dout_f(A_ThisFunc)
FO_MOVE := 0x1
FO_COPY := 0x2
FO_DELETE := 0x3
FO_RENAME := 0x4
FOF_MULTIDESTFILES := 0x1 ; Indicates that the to member specifies multiple destination files (one for each source file) rather than one directory where all source files are to be deposited.
FOF_SILENT := 0x4 ; Does not display a progress dialog box.
FOF_RENAMEONCOLLISION := 0x8 ; Gives the file being operated on a new name (such as "Copy #1 of...") in a move, copy, or rename operation if a file of the target name already exists.
FOF_NOCONFIRMATION := 0x10 ; Responds with "yes to all" for any dialog box that is displayed.
FOF_ALLOWUNDO := 0x40 ; Preserves undo information, if possible. With del, uses recycle bin.
FOF_FILESONLY := 0x80 ; Performs the operation only on files if a wildcard filename (*.*) is specified.
FOF_SIMPLEPROGRESS := 0x100 ; Displays a progress dialog box, but does not show the filenames.
FOF_NOCONFIRMMKDIR := 0x200 ; Does not confirm the creation of a new directory if the operation requires one to be created.
FOF_NOERRORUI := 0x400 ; don't put up error UI
FOF_NOCOPYSECURITYATTRIBS := 0x800 ; dont copy file security attributes
FOF_NORECURSION := 0x1000 ; Only operate in the specified directory. Don't operate recursively into subdirectories.
FOF_NO_CONNECTED_ELEMENTS := 0x2000 ; Do not move connected files as a group (e.g. html file together with images). Only move the specified files.
FOF_WANTNUKEWARNING := 0x4000 ; Send a warning if a file is being destroyed during a delete operation rather than recycled. This flag partially overrides FOF_NOCONFIRMATION.
; no more annoying numbers to deal with (but they should still work, if you really want them to)
fileO := %fileO% ? %fileO% : fileO
; the double ternary was too fun to pass up
_flags := 0
Loop Parse, flags, |
_flags |= %A_LoopField%
flags := _flags ? _flags : (%flags% ? %flags% : flags)
If ( SubStr(fSource,0) != "|" )
fSource := fSource . "|"
If ( SubStr(fTarget,0) != "|" )
fTarget := fTarget . "|"
char_size := A_IsUnicode ? 2 : 1
char_type := A_IsUnicode ? "UShort" : "Char"
fsPtr := &fSource
Loop % StrLen(fSource)
if NumGet(fSource, (A_Index-1)*char_size, char_type) = 124
NumPut(0, fSource, (A_Index-1)*char_size, char_type)
ftPtr := &fTarget
Loop % StrLen(fTarget)
if NumGet(fTarget, (A_Index-1)*char_size, char_type) = 124
NumPut(0, fTarget, (A_Index-1)*char_size, char_type)
VarSetCapacity( SHFILEOPSTRUCT, 60, 0 ) ; Encoding SHFILEOPSTRUCT
NextOffset := NumPut( ghwnd, &SHFILEOPSTRUCT ) ; hWnd of calling GUI
NextOffset := NumPut( fileO, NextOffset+0 ) ; File operation
NextOffset := NumPut( fsPtr, NextOffset+0 ) ; Source file / pattern
NextOffset := NumPut( ftPtr, NextOffset+0 ) ; Target file / folder
NextOffset := NumPut( flags, NextOffset+0, 0, "Short" ) ; options
code := DllCall( "Shell32\SHFileOperation" . (A_IsUnicode ? "W" : "A"), UInt,&SHFILEOPSTRUCT )
ErrorLevel := ShellFileOperation_InterpretReturn(code)
Return NumGet( NextOffset+0 )
}
ShellFileOperation_InterpretReturn(c)
{
static dict
if !dict
{
dict := Object()
dict[0x0] := ""
dict[0x71] := "DE_SAMEFILE - The source and destination files are the same file."
dict[0x72] := "DE_MANYSRC1DEST - Multiple file paths were specified in the source buffer, but only one destination file path."
dict[0x73] := "DE_DIFFDIR - Rename operation was specified but the destination path is a different directory. Use the move operation instead."
dict[0x74] := "DE_ROOTDIR - The source is a root directory, which cannot be moved or renamed."
dict[0x75] := "DE_OPCANCELLED - The operation was cancelled by the user, or silently cancelled if the appropriate flags were supplied to SHFileOperation."
dict[0x76] := "DE_DESTSUBTREE - The destination is a subtree of the source."
dict[0x78] := "DE_ACCESSDENIEDSRC - Security settings denied access to the source."
dict[0x79] := "DE_PATHTOODEEP - The source or destination path exceeded or would exceed MAX_PATH."
dict[0x7A] := "DE_MANYDEST - The operation involved multiple destination paths, which can fail in the case of a move operation."
dict[0x7C] := "DE_INVALIDFILES - The path in the source or destination or both was invalid."
dict[0x7D] := "DE_DESTSAMETREE - The source and destination have the same parent folder."
dict[0x7E] := "DE_FLDDESTISFILE - The destination path is an existing file."
dict[0x80] := "DE_FILEDESTISFLD - The destination path is an existing folder."
dict[0x81] := "DE_FILENAMETOOLONG - The name of the file exceeds MAX_PATH."
dict[0x82] := "DE_DEST_IS_CDROM - The destination is a read-only CD-ROM, possibly unformatted."
dict[0x83] := "DE_DEST_IS_DVD - The destination is a read-only DVD, possibly unformatted."
dict[0x84] := "DE_DEST_IS_CDRECORD - The destination is a writable CD-ROM, possibly unformatted."
dict[0x85] := "DE_FILE_TOO_LARGE - The file involved in the operation is too large for the destination media or file system."
dict[0x86] := "DE_SRC_IS_CDROM - The source is a read-only CD-ROM, possibly unformatted."
dict[0x87] := "DE_SRC_IS_DVD - The source is a read-only DVD, possibly unformatted."
dict[0x88] := "DE_SRC_IS_CDRECORD - The source is a writable CD-ROM, possibly unformatted."
dict[0xB7] := "DE_ERROR_MAX - MAX_PATH was exceeded during the operation."
dict[0x402] := "An unknown error occurred. This is typically due to an invalid path in the source or destination. This error does not occur on Windows Vista and later."
dict[0x10000] := "RRORONDEST - An unspecified error occurred on the destination."
dict[0x10074] := "E_ROOTDIR | ERRORONDEST - Destination is a root directory and cannot be renamed."
}
return dict[c] ? dict[c] : "Error code not recognized"
} |
|
|
| Back to top |
|
 |
Rapte_Of_Suzaku
Joined: 29 Feb 2008 Posts: 900 Location: Vault 7
|
Posted: Tue Sep 14, 2010 8:47 pm Post subject: |
|
|
Just updated the function again, latest version is now in the first post.
Oh, and I've finally discovered a solid way to track file operations by tricking ShFileOperation into putting all the results into its returned mappings! Here's the story/explanation, if anyone is interested:
While working on my FileContainer Library, I've been trying to find a way to track file operations through ShFileOperation. By using the FOF_WANTMAPPINGHANDLE flag, it is possible to get a list of any renames that occur. And you can sorta check for completed move/delete operations by checking if those files still exist. But what happens if the user cancels a file copy in the middle due to a conflicting destination file? ShFileOperation doesn't provide that information naturally. I tried using WatchDirectory() by HotKeyIt, but ReadDirectoryChangesW is susceptible to overflow and takes way too many resources to reliably cover all areas of any given operation. I thought about giving up on ShFileOperation and rolling my own file moving program... but that didn't seem like fun to me! I even tried using the USN journal, but that won't work in all situations.
But recently, I discovered a way to trick ShFileOperation into telling me everything I need to know. I found that if you give it folder names that end in a slash, it feels compelled to rename them for you. And so those folders will end up in the returned mappings! For files, you can achieve the same thing by adding a trailing space to the path. The only problem is that any actual renames (due to conflicts) will get butchered. The ShFileOperation UI will offer to rename the file to, say "file (2).txt", but will actually end up renaming it to "file.txt (2)". However, this is a very easy problem to fix. In my FileContainer library, I now have a postprocessing routine that picks out the butchered renames and manually fixes them (will be posting the new version today or tomorrow). And then finally, you'll have a list of exactly what files/folders resulted from the requested ShFileOperation. |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|