Automatic File sorting based on extended parameters and watching multiple folders

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
The Lich Omoi
Posts: 6
Joined: 01 Dec 2021, 09:06

Automatic File sorting based on extended parameters and watching multiple folders

Post by The Lich Omoi » 21 May 2022, 13:02

I think I bit off more than I can chew, but i'm determined to get this all sorted out.... if I can.

As a baseline, the objective is to watch folders and move files if parameters are met.

My thought of getting this completed was to
  • Decide what folders need to be watched
  • Set up what files need to be moved either by file type or by an extended parameter
  • send that file to the appropriate folder based on when it was created/extended parameter
This uses viewtopic.php?f=6&t=59882&hilit=camera+model to look for the camera model
and then viewtopic.php?f=6&t=8384&hilit=watchfolder to watch the specific folders in question

so here is the background code needed for the watch folders and the extended properties

Code: Select all

#Persistent
WatchFolder(Folder, UserFunc, SubTree := False, Watch := 0x03) {
   Static DummyObject := {Base: {__Delete: Func("WatchFolder").Bind("**END", "")}}
   Static TimerID := "**" . A_TickCount
   Static TimerFunc := Func("WatchFolder").Bind(TimerID, "")
   Static MAXIMUM_WAIT_OBJECTS := 64
   Static MAX_DIR_PATH := 260 - 12 + 1
   Static SizeOfLongPath := MAX_DIR_PATH << !!A_IsUnicode
   Static SizeOfFNI := 0xFFFF ; size of the FILE_NOTIFY_INFORMATION structure buffer (64 KB)
   Static SizeOfOVL := 32     ; size of the OVERLAPPED structure (64-bit)
   Static WatchedFolders := {}
   Static EventArray := []
   Static WaitObjects := 0
   Static BytesRead := 0
   Static Paused := False
   ; ===============================================================================================================================
   If (Folder = "")
      Return False
   SetTimer, % TimerFunc, Off
   RebuildWaitObjects := False
   ; ===============================================================================================================================
   If (Folder = TimerID) { ; called by timer
      If (ObjCount := EventArray.Count()) && !Paused {
         ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", &WaitObjects, "Int", 0, "UInt", 0, "UInt")
         While (ObjIndex >= 0) && (ObjIndex < ObjCount) {
            Event := NumGet(WaitObjects, ObjIndex * A_PtrSize, "UPtr")
            Folder := EventArray[Event]
            If DllCall("GetOverlappedResult", "Ptr", Folder.Handle, "Ptr", Folder.OVLAddr, "UIntP", BytesRead, "Int", True) {
               Changes := []
               FNIAddr := Folder.FNIAddr
               FNIMax := FNIAddr + BytesRead
               OffSet := 0
               PrevIndex := 0
               PrevAction := 0
               PrevName := ""
               Loop {
                  FNIAddr += Offset
                  OffSet := NumGet(FNIAddr + 0, "UInt")
                  Action := NumGet(FNIAddr + 4, "UInt")
                  Length := NumGet(FNIAddr + 8, "UInt") // 2
                  Name   := Folder.Name . "\" . StrGet(FNIAddr + 12, Length, "UTF-16")
                  IsDir  := InStr(FileExist(Name), "D") ? 1 : 0
                  If (Name = PrevName) {
                     If (Action = PrevAction)
                        Continue
                     If (Action = 1) && (PrevAction = 2) {
                        PrevAction := Action
                        Changes.RemoveAt(PrevIndex--)
                        Continue
                     }
                  }
                  If (Action = 4)
                     PrevIndex := Changes.Push({Action: Action, OldName: Name, IsDir: 0})
                  Else If (Action = 5) && (PrevAction = 4) {
                     Changes[PrevIndex, "Name"] := Name
                     Changes[PrevIndex, "IsDir"] := IsDir
                  }
                  Else
                     PrevIndex := Changes.Push({Action: Action, Name: Name, IsDir: IsDir})
                  PrevAction := Action
                  PrevName := Name
               } Until (Offset = 0) || ((FNIAddr + Offset) > FNIMax)
               If (Changes.Length() > 0)
                  Folder.Func.Call(Folder.Name, Changes)
               DllCall("ResetEvent", "Ptr", Event)
               DllCall("ReadDirectoryChangesW", "Ptr", Folder.Handle, "Ptr", Folder.FNIAddr, "UInt", SizeOfFNI
                                              , "Int", Folder.SubTree, "UInt", Folder.Watch, "UInt", 0
                                              , "Ptr", Folder.OVLAddr, "Ptr", 0)
            }
            ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", &WaitObjects, "Int", 0, "UInt", 0, "UInt")
            Sleep, 0
         }
      }
   }
   ; ===============================================================================================================================
   Else If (Folder = "**PAUSE") { ; called to pause/resume watching
      Paused := !!UserFunc
      RebuildObjects := Paused
   }
   ; ===============================================================================================================================
   Else If (Folder = "**END") { ; called to stop watching
      For Event, Folder In EventArray {
         DllCall("CloseHandle", "Ptr", Folder.Handle)
         DllCall("CloseHandle", "Ptr", Event)
      }
      WatchedFolders := {}
      EventArray := []
      Paused := False
      Return True
   }
   ; ===============================================================================================================================
   Else { ; called to add, update, or remove folders
      Folder := RTrim(Folder, "\")
      VarSetCapacity(LongPath, MAX_DIR_PATH << !!A_IsUnicode, 0)
      If !DllCall("GetLongPathName", "Str", Folder, "Ptr", &LongPath, "UInt", MAX_DIR_PATH)
         Return False
      VarSetCapacity(LongPath, -1)
      Folder := LongPath
      If (WatchedFolders.HasKey(Folder)) { ; update or remove
         Event :=  WatchedFolders[Folder]
         FolderObj := EventArray[Event]
         DllCall("CloseHandle", "Ptr", FolderObj.Handle)
         DllCall("CloseHandle", "Ptr", Event)
         EventArray.Delete(Event)
         WatchedFolders.Delete(Folder)
         RebuildWaitObjects := True
      }
      If InStr(FileExist(Folder), "D") && (UserFunc <> "**DEL") && (EventArray.Count() < MAXIMUM_WAIT_OBJECTS) {
         If (IsFunc(UserFunc) && (UserFunc := Func(UserFunc)) && (UserFunc.MinParams >= 2)) && (Watch &= 0x017F) {
            Handle := DllCall("CreateFile", "Str", Folder . "\", "UInt", 0x01, "UInt", 0x07, "Ptr",0, "UInt", 0x03
                                          , "UInt", 0x42000000, "Ptr", 0, "UPtr")
            If (Handle > 0) {
               Event := DllCall("CreateEvent", "Ptr", 0, "Int", 1, "Int", 0, "Ptr", 0)
               FolderObj := {Name: Folder, Func: UserFunc, Handle: Handle, SubTree: !!SubTree, Watch: Watch}
               FolderObj.SetCapacity("FNIBuff", SizeOfFNI)
               FNIAddr := FolderObj.GetAddress("FNIBuff")
               DllCall("RtlZeroMemory", "Ptr", FNIAddr, "Ptr", SizeOfFNI)
               FolderObj["FNIAddr"] := FNIAddr
               FolderObj.SetCapacity("OVLBuff", SizeOfOVL)
               OVLAddr := FolderObj.GetAddress("OVLBuff")
               DllCall("RtlZeroMemory", "Ptr", OVLAddr, "Ptr", SizeOfOVL)
               NumPut(Event, OVLAddr + 8, A_PtrSize * 2, "Ptr")
               FolderObj["OVLAddr"] := OVLAddr
               DllCall("ReadDirectoryChangesW", "Ptr", Handle, "Ptr", FNIAddr, "UInt", SizeOfFNI, "Int", SubTree
                                              , "UInt", Watch, "UInt", 0, "Ptr", OVLAddr, "Ptr", 0)
               EventArray[Event] := FolderObj
               WatchedFolders[Folder] := Event
               RebuildWaitObjects := True
            }
         }
      }
      If (RebuildWaitObjects) {
         VarSetCapacity(WaitObjects, MAXIMUM_WAIT_OBJECTS * A_PtrSize, 0)
         OffSet := &WaitObjects
         For Event In EventArray
            Offset := NumPut(Event, Offset + 0, 0, "Ptr")
      }
   }
   ; ===============================================================================================================================
   If (EventArray.Count() > 0)
      SetTimer, % TimerFunc, -100
   Return (RebuildWaitObjects) ; returns True on success, otherwise False
}

Filexpro( sFile := "", Kind := "", P* ) {           ; v.90 By SKAN on D1CC @ goo.gl/jyXFo9
Local
Static xDetails

  If ( sFile = "" )
    {                                                           ;   Deinit static variable
        xDetails := ""
        Return
    }

  fex := {}, _FileExt := ""

  Loop, Files, % RTrim(sfile,"\*/."), DF
    {
        If not FileExist( sFile:=A_LoopFileLongPath )
          {
              Return
          }

        SplitPath, sFile, _FileExt, _Dir, _Ext, _File, _Drv

        If ( p[p.length()] = "xInfo" )                          ;  Last parameter is xInfo
          {
              p.Pop()                                           ;         Delete parameter
              fex.SetCapacity(11)                               ; Make room for Extra info
              fex["_Attrib"]    := A_LoopFileAttrib
              fex["_Dir"]       := _Dir
              fex["_Drv"]       := _Drv
              fex["_Ext"]       := _Ext
              fex["_File"]      := _File
              fex["_File.Ext"]  := _FileExt
              fex["_FilePath"]  := sFile
              fex["_FileSize"]  := A_LoopFileSize
              fex["_FileTimeA"] := A_LoopFileTimeAccessed
              fex["_FileTimeC"] := A_LoopFileTimeCreated
              fex["_FileTimeM"] := A_LoopFileTimeModified
          }
        Break
    }

  If Not ( _FileExt )                                   ;    Filepath not resolved
    {
        Return
    }


  objShl := ComObjCreate("Shell.Application")
  objDir := objShl.NameSpace(_Dir)
  objItm := objDir.ParseName(_FileExt)

  If ( VarSetCapacity(xDetails) = 0 )                           ;     Init static variable
    {
        i:=-1,  xDetails:={},  xDetails.SetCapacity(309)

        While ( i++ < 309 )
          {
            xDetails[ objDir.GetDetailsOf(0,i) ] := i
          }

        xDetails.Delete("")
    }

  If ( Kind and Kind <> objDir.GetDetailsOf(objItm,11) )        ;  File isn't desired kind
    {
        Return
    }

  i:=0,  nParams:=p.Count(),  fex.SetCapacity(nParams + 11)

  While ( i++ < nParams )
    {
        Prop := p[i]

        If ( (Dot:=InStr(Prop,".")) and (Prop:=(Dot=1 ? "System":"") . Prop) )
          {
              fex[Prop] := objItm.ExtendedProperty(Prop)
              Continue
          }

        If ( PropNum := xDetails[Prop] ) > -1
          {
              fex[Prop] := ObjDir.GetDetailsOf(objItm,PropNum)
              Continue
          }
    }

  fex.SetCapacity(-1)
Return fex

} ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

And this is the code as i have it is so far

Code: Select all


WatchFolder("C:\Users\The Lich Omoi\Pictures\Drawings", "myFunc"),
WatchFolder("C:\Users\The Lich Omoi\Pictures\Camera Roll", "myFunc")
																	; These are the folders to watch for new files being added. I think I may need to make a var with all the folder paths to watch somehow instead?
return

watchedDir := "C:\Users\The Lich Omoi\Pictures\"
watchedFolders := "Camera Roll", "Drawings"
																	; maybe something like this?
		
myFunc( directory, changes) {
    for k, change in changes
        ; 1 means new file was added
        if (change.action = 1) {
            gosub foldersorter 												;????? this is a confusion point for me. I'm trying to go to the relevant function based on what folder got a new file
            return
        }
}

foldersorterCameraRoll:
;Camera Roll folder organizer
srcPath := "C:\Users\The Lich Omoi\Pictures\Camera Roll"
destPath := "C:\Users\The Lich Omoi\Pictures\Camera Roll"
extsToSort := jpg,png,tiff
cameraModel := Filexpro( A_LoopFileLongPath,, "Camera Model" )
FormatTime, dateTaken, Filexpro( A_LoopFileLongPath,, "Date Taken" ), MMM yyyy

Loop, Files, %srcPath% "\*." %extsToSort% 										; Goes through src Path and looks for exts we have defined
{
	file := A_LoopFileFullPath													; save file full path
	SubFolder := %cameraModel% "\" %destPath% "\" %dateTaken%					; look for subfolder named after month day and hour of the file mmddhh
	if !InStr(FileExist(SubFolder), "D")										; if that folder does not exist
		FileCreateDir, % SubFolder												; create folder
	FileMove, % file , % SubFolder "\" *.*										; move file to destination
}

foldersorterDrawings:
;Drawings folder organizer
srcPath := "C:\Users\The Lich Omoi\Pictures\Drawings"
destPath := "C:\Users\The Lich Omoi\Pictures\Drawings"
extsToSort := jpg,png,tiff,clip
cameraModel := Filexpro( A_LoopFileLongPath,, "Camera Model" )
FormatTime, dateCreated, Filexpro( A_LoopFileLongPath,, "Date Created" ), MMM yyyy

Loop, Files, %srcPath% "\*." %extsToSort% 										; Goes through src Path and looks for exts we have defined
{
	file := A_LoopFileFullPath													; save file full path
	SubFolder := %dateCreated% "\" %destPath% "\" %dateTaken%					; look for subfolder named after month day and hour of the file mmddhh
	if !InStr(FileExist(SubFolder), "D")										; if that folder does not exist
		FileCreateDir, % SubFolder												; create folder
	FileMove, % file , % SubFolder "\" *.*										; move file to destination
}
I was trying to save and address the code as I thought it would work and I realized I don't really understand how to approach fixing it.

my thought was to make it go to the appropriate function based on where the new file was found EX: new drawing was placed in the drawings folder, and then it would fire the appropriate sorter based on that directory. However, I don't think I've set up my functions correctly and i don't know how to make it go to a subroutine while also looping as most of this code has been frankensteined together.

any help would be appreciated as I continue to see how i can fix this! Thank you!

BoBo
Posts: 6564
Joined: 13 May 2014, 17:15

Re: Automatic File sorting based on extended parameters and watching multiple folders

Post by BoBo » 21 May 2022, 13:37

Review not finished, but how is this string supposed to work? :think:
extsToSort := jpg,png,tiff
vs.
extsToSort := "jpg,png,tiff"

Loop, Files, %srcPath% "\*." %extsToSort%
{
file := A_LoopFileFullPath
SubFolder := %dateCreated% "\" %destPath% "\" %dateTaken%
if !InStr(FileExist(SubFolder), "D")
FileCreateDir, % SubFolder
FileMove, % file , % SubFolder "\" *.*
}
:?:
vs.
Loop, Files, % srcPath "\*." extsToSort
{
file := A_LoopFileFullPath
SubFolder := dateCreated "\" destPath "\" dateTaken
if !InStr(FileExist(SubFolder), "D")
FileCreateDir, % SubFolder
FileMove, % file , % SubFolder "\*.*"
}


gosub foldersorter
Can't see a label that is named like this? :arrow: foldersorterCameraRoll/foldersorterDrawings

Please have a look at AHK's Help regarding legacy vs expression style syntax: https://www.autohotkey.com/docs/Variables.htm#Expressions

The Lich Omoi
Posts: 6
Joined: 01 Dec 2021, 09:06

Re: Automatic File sorting based on extended parameters and watching multiple folders

Post by The Lich Omoi » 22 May 2022, 17:59

[Duplicated Post]
Last edited by The Lich Omoi on 22 May 2022, 22:11, edited 1 time in total.

The Lich Omoi
Posts: 6
Joined: 01 Dec 2021, 09:06

Re: Automatic File sorting based on extended parameters and watching multiple folders

Post by The Lich Omoi » 22 May 2022, 18:13

Thanks for explaining that! I think I am definitely getting sucked into unproductivity trying to learn the code and syntax I need for the applications I am trying to make
I edited the string to have all the different file tips the sorter should be looking for, instead of the amalgam of those different types as one value.

I didn't think it was wise to make a sorter for each file type as i thought it was possible to load and search all the listed exts.
I'm also finding that the looping process isn't getting all the types even with the variable change so it's hard to even know where I should begin for this... My first assumption I might need to use an Array (somehow) of what to search and where to move things to but i don't know how blank / undefined paths would work out when sorting large numbers of files


here's the updated syntax

Code: Select all

foldersorterCameraRoll:
  ;Camera Roll folder organizer
  srcPath := "C:\Users\The Lich Omoi\Pictures\Camera Roll"
  destFolder := "C:\Users\The Lich Omoi\Pictures\Camera Roll"
  extsToSort := "jpg", "png", "tiff", "JPG", "PNG"
   
  Loop, Files, % srcPath "\*." extsToSort 										; Goes through src Path and looks for exts we have defined above
  {
  	 loopedFile := A_LoopFileFullPath													; save the file full path of the files that meet our extensions we are looking for
  	 cameraModel := Filexpro( loopedFile,, "Camera model" )                     ;Grab the camera model of the file
     If (cameraModel = "")                                                        ;If cameramodel is blank
        cameraModel := "Unknown Camera"                                           ;it is now "unknown camera mode"
        dateTaken := Filexpro( looped File,, "Date taken" )                     ;Grab the date of the file

     subFolder :=  destFolder  "\" cameraModel "\" dateTaken
    if !InStr(FileExist(subFolder), "D")										; if that folder does not exist
  		FileCreateDir, % subFolder												; create folder
  	FileMove, % loopedFile , % subFolder "\*.*"									; move file to destination
        	MsgBox It's been moved
   *
  }
  return

User avatar
boiler
Posts: 16955
Joined: 21 Dec 2014, 02:44

Re: Automatic File sorting based on extended parameters and watching multiple folders

Post by boiler » 22 May 2022, 19:59

@The Lich Omoi — BoBo showed you how to write the line where you assign the extensions, and for some reason you did this instead:

Code: Select all

extsToSort := "jpg", "png", "tiff", "JPG", "PNG"
…which is neither a string assignment nor an array assignment. I don’t know which you were trying to achieve.

Also, it appears that you are trying to make both of the lines following this if statement conditional:

Code: Select all

     If (cameraModel = "")                                                        ;If cameramodel is blank
        cameraModel := "Unknown Camera"                                           ;it is now "unknown camera mode"
        dateTaken := Filexpro( looped File,, "Date taken" )
…but indenting doesn’t affect the logic of the code. You have to use { } to define a code block and group those lines so that they are both execute conditionally on the if statement.

There may be more issues. That’s just what I saw at first glance.

The Lich Omoi
Posts: 6
Joined: 01 Dec 2021, 09:06

Re: Automatic File sorting based on extended parameters and watching multiple folders

Post by The Lich Omoi » 22 May 2022, 22:21

Code: Select all

 foldersorterCameraRoll:
  ;Camera Roll folder organizer
  srcPath := "C:\Users\The Lich Omoi\Pictures\Camera Roll"
  destFolder := "C:\Users\The Lich Omoi\Pictures\Camera Roll"
  extsToSort := "jpg, png, tiff, JPG, PNG"
   
  Loop, Files, % srcPath "\*." extsToSort 										; Goes through src Path and looks for exts we have defined above
  {
  	 loopedFile := A_LoopFileFullPath													; save the file full path of the files that meet our extensions we are looking for
  	 cameraModel := Filexpro( loopedFile,, "Camera model" )                     ;Grab the camera model of the file
     If (cameraModel = "")                                                                   ;If cameramodel is blank
        cameraModel := "Unknown Camera"                                           ;it is now "unknown camera mode"
   
     dateTaken := Filexpro( looped File,, "Date taken" )                        ;Grab the date of the file

     subFolder :=  destFolder  "\" cameraModel "\" dateTaken
    if !InStr(FileExist(subFolder), "D")										; if that folder does not exist
  		FileCreateDir, % subFolder												; create folder
  	FileMove, % loopedFile , % subFolder "\*.*"									; move file to destination
        	MsgBox files have been moved
  }
  return
I thought that by having the quotations on all of the choices it was reading all the values as one. I switched back to the provided line.
as for the indention, I only want the camera model to be added if there isn't one, then keep going through the code so i believe i did that right. (when I'm copying over the code to the forum I can't seem to get everything lined up how i want to yet)

just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Automatic File sorting based on extended parameters and watching multiple folders

Post by just me » 23 May 2022, 01:00

1. You cannot not use multiple extensions in the pattern of a file-loop.
2. String comparisons are not case sensitive by default.

The following might do what you want:

Code: Select all

extsToSort := "jpg,png,tiff" 			; removed spaces and duplicates
Loop, Files, %srcPath%\*.*				; goes through srcPath
{
	If A_LoopFileExt In %extsToSort%    ; looks for exts we have defined above
	{
		...
		...
	}
}

Post Reply

Return to “Ask for Help (v1)”