AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

ShellFileOperation Library

 
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
Rapte_Of_Suzaku



Joined: 29 Feb 2008
Posts: 900
Location: Vault 7

PostPosted: Sun Mar 14, 2010 9:51 pm    Post subject: ShellFileOperation Library Reply with quote

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
View user's profile Send private message Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Mon Mar 15, 2010 8:33 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Rapte_Of_Suzaku



Joined: 29 Feb 2008
Posts: 900
Location: Vault 7

PostPosted: Mon Mar 15, 2010 3:53 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Rapte_Of_Suzaku



Joined: 29 Feb 2008
Posts: 900
Location: Vault 7

PostPosted: Sat Jul 10, 2010 1:37 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Rapte_Of_Suzaku



Joined: 29 Feb 2008
Posts: 900
Location: Vault 7

PostPosted: Tue Sep 14, 2010 8:47 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group