AutoHotkey Community

It is currently May 27th, 2012, 5:39 am

All times are UTC [ DST ]




Post new topic Reply to topic  [ 5 posts ] 
Author Message
PostPosted: March 14th, 2010, 10:51 pm 
Offline

Joined: February 29th, 2008, 8:36 pm
Posts: 901
Location: Vault 7
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 September 30th, 2010, 4:27 pm, edited 12 times in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: March 15th, 2010, 9:33 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7503
Location: Australia
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).


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: March 15th, 2010, 4:53 pm 
Offline

Joined: February 29th, 2008, 8:36 pm
Posts: 901
Location: Vault 7
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 )
}


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: July 10th, 2010, 2:37 am 
Offline

Joined: February 29th, 2008, 8:36 pm
Posts: 901
Location: Vault 7
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"
}


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 14th, 2010, 9:47 pm 
Offline

Joined: February 29th, 2008, 8:36 pm
Posts: 901
Location: Vault 7
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.


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 

All times are UTC [ DST ]


Who is online

Users browsing this forum: sks, Stigg and 12 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group