42 Commits
Author SHA1 Message Date
sarah 2bf4457981 Merge pull request #56 from SRHTNIN/SarahTest
Sarah test
2026-05-02 14:54:31 +02:00
sarah 5c39166a66 Update README.md 2026-05-02 14:54:03 +02:00
sarah 6a6e7dd494 Update README.md 2026-05-02 14:53:45 +02:00
sarah a83b2e3c94 Update README.md 2026-05-02 14:52:14 +02:00
sarah 9be5938a87 Update README.md 2026-05-02 14:51:09 +02:00
sarah 4e00617e09 Update README.md 2026-05-02 14:50:17 +02:00
sarah c5612654f8 Added new install video to README. 2026-05-02 14:49:49 +02:00
sarah 324d06d6b4 Set RootDir to "Unset". I am dumb. 2026-05-02 14:34:26 +02:00
sarah b5afad3895 Changed file structure and moved assets to separate repository to keep
stuff clean. Also moved LICENSE up.
2026-05-02 14:24:48 +02:00
sarah a314614055 Merge pull request #53 from SRHTNIN/SarahTest
Sarah test
2026-05-02 00:50:12 +02:00
sarah 3aa28f76cf Update README.md 2026-05-02 00:49:36 +02:00
sarah 89f4e59041 Update README.md 2026-05-02 00:48:18 +02:00
sarah dc60a9828e Update README.md 2026-05-02 00:48:03 +02:00
sarah 4901d346f2 Added a new banner and icons for SaraSortd. (Calindra is the demon's
name.)
2026-05-02 00:47:32 +02:00
sarah b04c3426a7 Merge pull request #50 from SRHTNIN/SarahTest
Sarah test
2026-04-30 10:46:07 +02:00
sarah 15ced89964 Update README.md
Fixed pathing for images.
2026-04-30 10:44:47 +02:00
Sarah Aadalen-Aasheim 475eda8dd2 Added banners. 2026-04-30 10:42:52 +02:00
Sarah Aadalen-Aasheim e9974f90ee Added banners to make README look prettier. 2026-04-30 10:42:03 +02:00
sarah 68e33861a9 Merge pull request #49 from SRHTNIN/SarahTest
SarahTest=>Main
2026-04-29 07:52:33 +02:00
sarah f0b2ec62ef Added function to InputDirConf, including pattern and if the input
should sort directories or not. Also added zipping functions to move
said directories. TODO: Add unzipping functions.
2026-04-29 07:50:04 +02:00
sarah 37af52eaa0 Merge pull request #48 from SRHTNIN/SarahTest
Sarah test
2026-04-28 18:53:47 +02:00
sarah 2afadaebc8 Fixed NextNum and NextChar bug. Also changed all mentions of Version to
be (Version = "5.1") for easier replacement for new updates.
2026-04-28 15:36:44 +02:00
sarah 84d61d9381 Update README.md 2026-04-21 11:20:08 +02:00
sarah 0f2f6673aa Renamed files. 2026-04-21 11:12:44 +02:00
sarah f2bc2abf9c Merge pull request #45 from SRHTNIN/SarahTest
Sarah test
2026-04-20 20:09:53 +02:00
sarah 5933dd94d2 Merge branch 'SarahTest' of https://github.com/SRHTNIN/Sarasort into SarahTest 2026-04-20 20:09:20 +02:00
sarah 24c52c17ce Added versioning in files, and fixed config files to be unset. 2026-04-20 20:09:07 +02:00
sarah fd4f7256df Merge pull request #44 from SRHTNIN/main
I want the readme.
2026-04-20 20:04:53 +02:00
sarah d43085de71 Update README.md 2026-04-20 20:04:20 +02:00
sarah dd00cf0780 Merge pull request #43 from SRHTNIN/SarahTest
Sarah test
2026-04-20 20:00:19 +02:00
sarah 51c557d228 Added FileHistoryName to config, allowing an input and output dir to be
the same and stopping recursion by saving the new file name. Also fixed
hour, minute, second, and so to be at least 2 wide (19:2:3 => 19:02:03)
2026-04-20 19:58:29 +02:00
sarah e91ce510fc Merge pull request #41 from SRHTNIN/main
Main=>SarahTest
2026-04-20 07:56:04 +02:00
sarah d68550d0a9 Added delete parameter to dirconf. 2026-04-14 07:20:38 +02:00
sarah 9fbdec9120 Update README.md 2026-04-13 06:56:15 +02:00
sarah 0b54a729ff Update README.md 2026-04-13 06:55:40 +02:00
sarah a3bc726086 Update README.md 2026-04-13 05:49:37 +02:00
sarah 739f91a5bf Update README.md 2026-04-13 05:39:12 +02:00
sarah fc6f9bd399 Update README.md 2026-04-10 22:10:41 +02:00
sarah 077cbbb2c1 Update README.md 2026-04-10 22:10:21 +02:00
sarah ffd95fdfce Merge pull request #37 from SRHTNIN/SarahTest
Sarah test
2026-04-10 22:07:20 +02:00
sarah 49b5549b1d Merge branch 'SarahTest' of https://github.com/SRHTNIN/Sarasort into SarahTest 2026-04-10 22:04:31 +02:00
sarah cbf1a4049c Added stacking of NextNum and NextChar. E.G: && into AA => "AB" instead
of && into AA => "BB". Also, !! into 01 => 02 instead of !! into 11 =>
22.

This was surprisingly difficult to achieve.
2026-04-10 22:01:30 +02:00
6 changed files with 329 additions and 94 deletions

No files matched your search

+9 -3
View File
@@ -1,7 +1,8 @@
Title = "SaraSortd Config" Title = "SaraSortd Config"
Version = "6.3"
SafeMode = 1 SafeMode = 1
SilentMode = 0 SilentMode = 0
CheckInput = 3 CheckInput = 5
DotFiles = 0 DotFiles = 0
Logging = 1 Logging = 1
OverwriteDirConf = 0 OverwriteDirConf = 0
@@ -29,15 +30,20 @@ FailedDir = "%RootDir%/.Failed"
[Names] [Names]
LogFileName = "[y].[m].[d]" LogFileName = "[y].[m].[d]"
DirConfName = ".@Config" InDirConfName = ".@Config"
OutDirConfName = ".@Config"
FileHistoryName = ".@History"
[Log] [Log]
All = "[h]:[n]:[s] | " All = "[h]:[n]:[s] | "
Start = "Started SaraSortd." Start = "Started SaraSortd."
NotStart = "Error: Failed to start SaraSortd." NotStart = "Error: Failed to start SaraSortd."
Stop = "\nStopped SaraSortd." Stop = "Stopped SaraSortd.\n"
MatchPattern = "Checking %%." MatchPattern = "Checking %%."
Sorted = "Sorted %%." Sorted = "Sorted %%."
NotSorted = "Error: Failed to sort %%. No valid output directory." NotSorted = "Error: Failed to sort %%. No valid output directory."
ValueSet = "Set %%." ValueSet = "Set %%."
Unset = "Error: %% is not set." Unset = "Error: %% is not set."
Zipping = "Zipping: %%."
Unzipping = "Unzipping: %%."
NoPermission = "Error: No permission to %%."
+8
View File
@@ -0,0 +1,8 @@
Title = "Unset"
Version = "6.3"
ParentDir = "Unset"
SortDirs = 0
[[Files]]
Pattern = "*"
CaseSensitive = 0
View File
File renamed without changes.
+3 -1
View File
@@ -1,13 +1,15 @@
Title = "Unset" Title = "Unset"
Version = "6.3"
LastFile = "Unset" LastFile = "Unset"
ParentDir = "Unset" ParentDir = "Unset"
ValidInputDirs = ["*"] ValidInputDirs = ["*"]
FileLimit = 0 FileLimit = 0
DeleteOrg = 1
[[Files]] [[Files]]
Pattern = "*" Pattern = "*"
NewFileName = "~^" NewFileName = "~^"
NextNum = 1 NextNum = "1"
NextChar = "A" NextChar = "A"
CaseSensitive = 0 CaseSensitive = 0
Overwrite = 1 Overwrite = 1
+102 -15
View File
@@ -1,17 +1,64 @@
# SaraSortd <img align="center" src="https://github.com/SRHTNIN/RepoAssets/blob/main/SaraSortd/Banners/SaraSortd.png" width="100%" alt="SaraSortd: a file and directory sorting daemon built with python."/>
A python daemon that can automatically sort your files to your liking without using AI.
(Version = "6.3")
It's made by Sarah, hence the name. :3 It's made by Sarah, hence the name. :3
## Dependencies If the first number in the version goes up, it means that old config files will not work with the new SaraSortd.py.
If the second number in the version goes up, something new got added, but your old config files will still work just fine.
---
<img align="center" src="https://github.com/SRHTNIN/RepoAssets/blob/main/SaraSortd/Banners/Usage.png" width="100%" alt="Usage"/>
SaraSortd is for people who don't want to manually sort files, people who want to organise files, and people who want an automatic naming standard for their files.
Example use-cases:
- Sorting log files from multiple programs into one directory.
- Sorting downloaded github repositories to a directory for your IDE.
- Regularly cloning important files into a backup directory.
---
<img align="center" src="https://github.com/SRHTNIN/RepoAssets/blob/main/SaraSortd/Banners/Dependencies.png" width="100%" alt="Dependencies"/>
python
toml: Use ```pip install toml``` to install toml. toml: Use ```pip install toml``` to install toml.
## Config explanation ---
<img align="center" src="https://github.com/SRHTNIN/RepoAssets/blob/main/SaraSortd/Banners/Installation.png" width="100%" alt="Installation"/>
Here's an [installation video](https://youtu.be/QkMZVSJ-PLQ?si=Nd20jDyHT7CN6Ztb) on Linux.
Here's a step-by-step tutorial (in case you don't want to watch the video).
1. Install ZIP-file by clicking the green "Code" button on the top right, then clicking "Download Zip".
2. Unzip ZIP-file wherever you want the program to live.
3. Edit your config files to your liking. Make sure to edit your paths! (E.G. RootDir = "/home/janedoe/Programs/SaraSortd")
4. Navigate to the new SaraSortd directory in your terminal (CMD / PowerShell on Windows).
5. Run ```python SaraSortd.py``` to run the program. (This will make new directories for you.)
6. Edit your new output directories' configs.
---
<img align="center" src="https://github.com/SRHTNIN/RepoAssets/blob/main/SaraSortd/Banners/ConfigInfo.png" width="100%" alt="Config Info"/>
<img align="center" src="https://github.com/SRHTNIN/RepoAssets/blob/main/SaraSortd/Banners/GlobalConf.png" width="100%" alt="GlobalConf"/>
### SaraSortd Config (Config.toml)
``` ```
Title = "SaraSortd Config" # NOTE: Do not change the title. Title = "SaraSortd Config" # NOTE: Do not change the title. This is so that the program knows which config file is which.
Version = "6.3" # Just the version number for the config file.
SafeMode = 1 # If SaraSortd should stop if an error with the config appears. 1 is stop, 0 is continue. SafeMode = 1 # If SaraSortd should stop if an error with the config appears. 1 is stop, 0 is continue.
@@ -23,10 +70,10 @@ DotFiles = 0 # If SaraSortd should sort files that begin with ".". This is mainl
Logging = 1 # If SaraSortd's logging system should be active. If this is 0, nothing will be logged into files. Logging = 1 # If SaraSortd's logging system should be active. If this is 0, nothing will be logged into files.
OverwriteDirConf = 0 # If SaraSortd should overwrite directory configs with DirConfig.toml. 0 is no, 1 is yes. OverwriteDirConf = 0 # If SaraSortd should overwrite directory configs with the config templates. 0 is no, 1 is yes.
[Variables] # Only use these in config files, not the actual file names themselves. [Variables] # Only use these in config files, not the actual file names themselves.
NextNum = "*" # The next available number. If 1 already exists, 2 will be used. Starts at 1. NextNum = "*" # The next available number. If 1 already exists, 2 will be used.
NextChar = "&" # The next available character. If A already exists, B will be used. NextChar = "&" # The next available character. If A already exists, B will be used.
@@ -66,6 +113,8 @@ LogFileName = "[y].[m].[d]._*" # Name for log files.
DirConfName = ".@Config" # Name for config files for directories. DirConfName = ".@Config" # Name for config files for directories.
FileHistoryName = ".@History" # Name for file history files. This keeps track of sorted files if an input and output are the same directory.
[Log] [Log]
All = "[h]:[n]:[s] | " # What to put at the start of all lines in Log file. All = "[h]:[n]:[s] | " # What to put at the start of all lines in Log file.
@@ -89,22 +138,37 @@ ValueSet = "Set: %%." # When a value in a config is set.
Unset = "Error: %% is not set." # When a value isn't set. Unset = "Error: %% is not set." # When a value isn't set.
# %% here is just the parameter who's value was either "Unset" or None. E.G: "RootDir". # %% here is just the parameter who's value was either "Unset" or None. E.G: "RootDir".
Zipping = "Zipping: %%." # When a directory is zipped.
# %% here is the path to the directory being zipped.
Unzipping = "Unzipping: %%." # When a directory gets unzipped.
# %% is the path to the directory being unzipped.
NoPermission = "Error: No permission to %%." # When SaraSortd lacks permission to do something.
# %% is the action that the program had no permission to do. E.G. "remove directory /home/bob/exampledir".
``` ```
---
### Output Directory Config (DirConfig.toml) <img align="center" src="https://github.com/SRHTNIN/RepoAssets/blob/main/SaraSortd/Banners/OutputDirConf.png" width="100%" alt="OutputDirConf"/>
The output directory config references the SaraSortd config (Config.toml). This means that you can, for example, use the character assigned to "NextNum" in Config.toml in this config (DirConfig.toml). This applies to all Variables in the SaraSortd config (Config.toml).
The output directory config references the SaraSortd config (GlobalConf.toml). This means that you can, for example, use the character assigned to "NextNum" in GlobalConf.toml in this config (OutputDirConf.toml). This applies to all Variables in the SaraSortd config (GlobalConf.toml).
``` ```
Title = "Unset" # The title of the config. This currently does nothing for SaraSortd, so it can be anything. It gets automatically updated when DirConfig.toml gets copied to an output directory. Title = "Unset" # NOTE: Do not change the title. This is so that the program knows which config file is which.
Version = "6.3" # The version number of the config.
LastFile = "Unset" # The last file that was sorted to this directory. It gets automatically updated whenever a file gets sorted to this directory. LastFile = "Unset" # The last file that was sorted to this directory. It gets automatically updated whenever a file gets sorted to this directory.
ParentDir = "Unset" # The name of this directory. It gets automatically updated when DirConfig.toml gets copied to an output directory. ParentDir = "Unset" # The name of this directory. It gets automatically updated when this config gets copied to an output directory.
ValidInputDirs = ["*"] # Directories that are valid for sorting. Basically, if a file is from one of these directories, they can be sorted here. * is a wildcard; ? is a one-character wilcard. E.G: if "ValidInputDirs = ["/TestDir/*"]" any file from any directory in "/TestDir" will be allowed to be sorted here. ValidInputDirs = ["*"] # Directories that are valid for sorting. Basically, if a file is from one of these directories, they can be sorted here. * is a wildcard; ? is a one-character wilcard. E.G: if "ValidInputDirs = ["/TestDir/*"]" any file from any directory in "/TestDir" will be allowed to be sorted here.
FileLimit = 0 FileLimit = 0 # The max limit of files a directory can have. If this is 0, then there's no limit.
DeleteOrg = 1 # If SaraSortd should delete the original file or not. If this is 1, then sorted files will be 'moved' to an output directory. If not, they will be 'copied' to an output directory.
[[Files]] # The double brackets means that you can add multiple types of files to be sorted here. Just copy [[Files]] and all the parameters under it, and paste it under or over this section, then you may change the values of the new parameters. E.G: [[Files]] # The double brackets means that you can add multiple types of files to be sorted here. Just copy [[Files]] and all the parameters under it, and paste it under or over this section, then you may change the values of the new parameters. E.G:
@@ -128,11 +192,34 @@ Pattern = "*" # A pattern used to sort files to this directory. * is a wildcard;
NewFileName = "~^" # A pattern for the new names of the files that get sorted to this directory from matching this "Pattern". NewFileName = "~^" # A pattern for the new names of the files that get sorted to this directory from matching this "Pattern".
NextNum = 1 # Used to keep track of the next available number for file names. It gets automatically incremented when NextNum is used in NewFileName, and a file gets sorted to this directory and renamed. NextNum = 1 # Used to keep track of the next available number for file names. If you change NextNum to be 01 or 001 instead of 1, it'll still work. 09 => 10. It gets automatically incremented when NextNum is used in NewFileName, and a file gets sorted to this directory and renamed.
NextChar = "A" # Used to keep track of the next available letter for file names. It gets automatically incremented (E.G: A into B) when NextChar is used in NewFileName, and a file gets sorted to this directory and renamed. NextChar = "A" # Used to keep track of the next available letter for file names. If you change NextChar to be AA or AAA instead of A, it'll still work. AZ => BA. It gets automatically incremented (E.G: A into B or AA into AB then AZ into BA) when NextChar is used in NewFileName, and a file gets sorted to this directory and renamed.
CaseSensitive = 0 # Whether this "Pattern" is case-sensitive or not. 1 is yes, 0 is no. E.G: if CaseSensitive = 1 and this "Pattern" is "*.PNG", a file named "picture.png" won't get sorted to this directory. If CaseSensitive = 0 and this "Pattern" is "*.PNG", a file named "picture.png" *will* get sorted to this directory. CaseSensitive = 0 # Whether this "Pattern" is case-sensitive or not. 1 is yes, 0 is no. E.G: if CaseSensitive = 1 and this "Pattern" is "*.PNG", a file named "picture.png" won't get sorted to this directory. If CaseSensitive = 0 and this "Pattern" is "*.PNG", a file named "picture.png" *will* get sorted to this directory.
Overwrite = 1 # Whether this specific file type can be overwritten by others of the same type. 0 is no, 1 is yes. Overwrite = 1 # Whether this specific file type can be overwritten by others of the same type. 0 is no, 1 is yes.
``` ```
---
<img align="center" src="https://github.com/SRHTNIN/RepoAssets/blob/main/SaraSortd/Banners/InputDirConf.png" width="100%" alt="InputDirConf"/>
```
Title = "Unset" # NOTE: Do not change the title. This is so that the program knows which config file is which.
Version = "6.3"
ParentDir = "Unset" # The name of this directory. It gets automatically updated when this config gets copied to an output directory.
SortDirs = 0 # If the input directory should be allowed to sort directories within it. (Currently needs admin privileges to delete the original directory after sorting it).
[[Files]] # The double brackets means that you can add multiple types of files to be sorted here. Just copy [[Files]] and all the parameters under it, and paste it under or over this section, then you may change the values of the new parameters.
Pattern = "*" # A pattern used to decide if a file should be sorted or not. * is a wildcard; ? is a one-character wildcard. E.G: if Pattern is "*.png", all files ending with .png in this directory will try to be sorted.
CaseSensitive = 0 # Whether Pattern is case-sensitive or not. If CaseSensitive = 1 and your pattern is "*.PNG", then a file named "example.png" won't be sorted.
```
---
+207 -75
View File
@@ -6,7 +6,9 @@ import shutil
import time import time
import toml import toml
ConfPath = "./Config.toml" # Version = "6.3"
ConfPath = "./GlobalConf.toml"
Conf = None Conf = None
ConfVars = None ConfVars = None
@@ -166,11 +168,11 @@ def Parse(
ConfVars["OrgFileName"]: str(OrgFileName), ConfVars["OrgFileName"]: str(OrgFileName),
ConfVars["OrgFileType"]: str(OrgFileType), ConfVars["OrgFileType"]: str(OrgFileType),
ConfVars["Year"]: str(Now.year), ConfVars["Year"]: str(Now.year),
ConfVars["Month"]: str(Now.month), ConfVars["Month"]: str(Now.month).rjust(2, "0"),
ConfVars["Day"]: str(Now.day), ConfVars["Day"]: str(Now.day).rjust(2, "0"),
ConfVars["Hour"]: str(Now.hour), ConfVars["Hour"]: str(Now.hour).rjust(2, "0"),
ConfVars["Minute"]: str(Now.minute), ConfVars["Minute"]: str(Now.minute).rjust(2, "0"),
ConfVars["Second"]: str(Now.second), ConfVars["Second"]: str(Now.second).rjust(2, "0"),
ConfVars["VarCall"]: str(VarCall), ConfVars["VarCall"]: str(VarCall),
} }
@@ -188,6 +190,23 @@ def Parse(
def Clone(Source, Destination, NewName, Delete = False): def Clone(Source, Destination, NewName, Delete = False):
if (os.path.dirname(Source) == Destination):
FileHistoryPath = f"{Destination}/{Parse(ConfNames["FileHistoryName"], Parent = os.path.basename(Destination))}.toml"
print("FileHistoryPath:",FileHistoryPath)
if not os.path.exists(FileHistoryPath):
with open(FileHistoryPath, "w", encoding = "utf-8") as HistoryFile:
toml.dump({"Title": f"{os.path.basename(Destination)} History", "History": []}, HistoryFile)
HistoryFileData = toml.load(FileHistoryPath)
if (NewName not in HistoryFileData["History"]):
HistoryFileData["History"].append(NewName)
with open(FileHistoryPath, "w", encoding = "utf-8") as HistoryFile:
toml.dump(HistoryFileData, HistoryFile)
os.rename(Source, os.path.join(Destination, NewName))
return
Dir(Destination, Output = False, CopyConf = False) Dir(Destination, Output = False, CopyConf = False)
shutil.copy2(Source, Destination) shutil.copy2(Source, Destination)
@@ -198,26 +217,44 @@ def Clone(Source, Destination, NewName, Delete = False):
os.remove(Source) os.remove(Source)
def Dir(Path, Output = True, CopyConf = True): def Dir(Path, Output = True, CopyConf = "OutputDirConf.toml"):
ParentName = os.path.basename(Path) ParentName = os.path.basename(Path)
os.makedirs(Path, exist_ok=True) os.makedirs(Path, exist_ok=True)
if CopyConf: if CopyConf:
NewDirConf = f"{Parse(String = ConfNames['DirConfName'], Path = ConfPath, Parent = ParentName)}.toml" if "Out" in CopyConf:
if Conf["OverwriteDirConf"] == 1 or not os.path.exists( NewDirConf = f"{Parse(String = ConfNames['OutDirConfName'], Path = ConfPath, Parent = ParentName)}.toml"
os.path.join(f"{Path}/{NewDirConf}")
): if Conf["OverwriteDirConf"] == 1 or not os.path.exists(os.path.join(f"{Path}/{NewDirConf}")):
Clone("DirConfig.toml", Path, NewDirConf) Clone(CopyConf, Path, NewDirConf)
UpdateConf( UpdateConf(
f"{Path}/{Parse(String = ConfNames['DirConfName'], Path = ConfPath, Parent = ParentName)}.toml", f"{Path}/{Parse(String = ConfNames['OutDirConfName'], Path = ConfPath, Parent = ParentName)}.toml",
"ParentDir", "ParentDir",
ParentName, ParentName,
) )
UpdateConf(
f"{Path}/{Parse(String = ConfNames['DirConfName'], Path = ConfPath, Parent = ParentName)}.toml", UpdateConf(
"Title", f"{Path}/{Parse(String = ConfNames['OutDirConfName'], Path = ConfPath, Parent = ParentName)}.toml",
f"{ParentName} Config", "Title",
) f"{ParentName} Config",
)
if "In" in CopyConf:
NewDirConf = f"{Parse(String = ConfNames['InDirConfName'], Path = ConfPath, Parent = ParentName)}.toml"
if Conf["OverwriteDirConf"] == 1 or not os.path.exists(os.path.join(f"{Path}/{NewDirConf}")):
Clone(CopyConf, Path, NewDirConf)
UpdateConf(
f"{Path}/{Parse(String = ConfNames['InDirConfName'], Path = ConfPath, Parent = ParentName)}.toml",
"ParentDir",
ParentName,
)
UpdateConf(
f"{Path}/{Parse(String = ConfNames['InDirConfName'], Path = ConfPath, Parent = ParentName)}.toml",
"Title",
f"{ParentName} Config",
)
if Output: if Output:
ParsedPath = Parse(String = Path) ParsedPath = Parse(String = Path)
@@ -247,13 +284,13 @@ def DecideNewPath(FilePath):
ParentName = os.path.basename(Output) ParentName = os.path.basename(Output)
DirConfName = Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName) OutDirConfName = Parse(String = ConfNames["OutDirConfName"], Path = ConfPath, Parent = ParentName)
DirConfPath = f"{Output}/{DirConfName}.toml" OutDirConfPath = f"{Output}/{OutDirConfName}.toml"
ValidInputDirs = GetConf("ValidInputDirs", DirConfPath) ValidInputDirs = GetConf("ValidInputDirs", OutDirConfPath)
for ValidInputDir in ValidInputDirs: for ValidInputDir in ValidInputDirs:
ValidInputDir = Parse(String = ValidInputDir, Parent = GetConf("ParentDir", DirConfPath)) ValidInputDir = Parse(String = ValidInputDir, Parent = GetConf("ParentDir", OutDirConfPath))
TextOutput = Parse(String = ConfLog["MatchPattern"], VarCall = f"{os.path.dirname(FilePath)} against {ValidInputDir}") TextOutput = Parse(String = ConfLog["MatchPattern"], VarCall = f"{os.path.dirname(FilePath)} against {ValidInputDir}")
LogWrite(TextOutput) LogWrite(TextOutput)
@@ -263,13 +300,12 @@ def DecideNewPath(FilePath):
if (InputDirValid): if (InputDirValid):
break break
if not os.path.exists(DirConfPath) or not InputDirValid: if not os.path.exists(OutDirConfPath) or not InputDirValid:
continue continue
OutputFiles = 0 OutputFiles = 0
FileLimitConf = GetConf("FileLimit", DirConfPath) FileLimitConf = GetConf("FileLimit", OutDirConfPath)
if FileLimitConf > 0: if FileLimitConf > 0:
for OutputFile in os.listdir(Output): for OutputFile in os.listdir(Output):
@@ -281,7 +317,7 @@ def DecideNewPath(FilePath):
if OutputFiles >= FileLimitConf: if OutputFiles >= FileLimitConf:
continue continue
FileConf = GetConf("Files", DirConfPath) FileConf = GetConf("Files", OutDirConfPath)
if not FileConf: if not FileConf:
continue continue
@@ -302,30 +338,63 @@ def DecideNewPath(FilePath):
if Match: if Match:
NewFileName = File["NewFileName"] NewFileName = File["NewFileName"]
NewFileName = Parse( NewFileName = Parse(String = NewFileName, Path = OutDirConfPath, OrgFile = UnsortedFile, NextNum = File["NextNum"], NextChar = File["NextChar"], Parent = ParentName)
String = NewFileName,
Path = DirConfPath,
OrgFile = UnsortedFile,
NextNum = File["NextNum"],
NextChar = File["NextChar"],
Parent = ParentName,
)
if File["Overwrite"] == 0 and os.path.exists(os.path.join(Output, NewFileName)): if File["Overwrite"] == 0 and os.path.exists(os.path.join(Output, NewFileName)):
continue continue
if ConfVars["NextNum"] in NewFileName: if ConfVars["NextNum"] in File["NewFileName"]:
UpdateConf( ConfData = toml.load(OutDirConfPath)
DirConfPath, Width = len(File["NextNum"])
"NextNum", NewNum = str((int(File["NextNum"]) + 1)).rjust(Width, "0")
(int(GetConf("NextNum", DirConfPath)) + 1), print(File["NextNum"],"=>",NewNum)
)
if ConfVars["NextChar"] in NewFileName: for Item in ConfData.get("Files", []):
CurrentChar = GetConf("NextChar", DirConfPath) if Item.get("Pattern") == File["Pattern"]:
if CurrentChar and len(CurrentChar) == 1 and CurrentChar.isalpha(): Item["NextNum"] = NewNum
NewChar = chr(((ord(CurrentChar.upper()) - 65 + 1) % 26) + 65) break
UpdateConf(DirConfPath, "NextChar", NewChar)
with open(OutDirConfPath, "w", encoding="utf-8") as ConfFile:
toml.dump(ConfData, ConfFile)
TextOutput = Parse(String = ConfLog["ValueSet"], VarCall = f"NextNum to {NewNum}")
LogWrite(TextOutput)
Speak(TextOutput)
if ConfVars["NextChar"] in File["NewFileName"]:
CurrentChar = File["NextChar"]
CurrentChar = CurrentChar.upper()
Result = list(CurrentChar)
Count = len(Result) - 1
while Count >= 0:
if Result[Count] == "Z":
Result[Count] = "A"
Count -= 1
else:
Result[Count] = chr(ord(Result[Count]) + 1)
break
else:
Result.insert(0, "A")
NewChar = "".join(Result)
ConfData = toml.load(OutDirConfPath)
for Item in ConfData.get("Files", []):
if Item.get("Pattern") == File["Pattern"]:
Item["NextChar"] = NewChar
break
with open(OutDirConfPath, "w", encoding="utf-8") as ConfFile:
toml.dump(ConfData, ConfFile)
TextOutput = Parse(String = ConfLog["ValueSet"], VarCall = f"NextChar to {NewChar}")
LogWrite(TextOutput)
Speak(TextOutput)
return f"{Output}/{NewFileName}" return f"{Output}/{NewFileName}"
@@ -346,24 +415,24 @@ def Sort(FilePath):
NewDirPath = os.path.dirname(NewPath) NewDirPath = os.path.dirname(NewPath)
NewName = os.path.basename(NewPath) NewName = os.path.basename(NewPath)
try: if (GetConf("DeleteOrg", f"{NewDirPath}/{Parse(String = ConfNames['OutDirConfName'], Parent = (os.path.basename(NewDirPath)))}.toml") == 1):
Clone(FilePath, NewDirPath, NewName, True) Delete = True
UpdateConf(
f"{NewDirPath}/{Parse(String = ConfNames['DirConfName'], Parent = os.path.basename(NewDirPath))}.toml",
"LastFile",
NewName,
)
TextOutput = Parse( else:
String = ConfLog["Sorted"], VarCall = f"{FilePath} to {NewDirPath}/{NewName}" Delete = False
)
try:
Clone(FilePath, NewDirPath, NewName, Delete)
UpdateConf(f"{NewDirPath}/{Parse(String = ConfNames['OutDirConfName'], Parent = os.path.basename(NewDirPath))}.toml", "LastFile", NewName)
TextOutput = Parse(String = ConfLog["Sorted"], VarCall = f"{FilePath} to {NewDirPath}/{NewName}")
LogWrite(TextOutput) LogWrite(TextOutput)
Speak(TextOutput) Speak(TextOutput)
except Exception: except Exception:
Dir(Parse(String = ConfDirs["FailedDir"]), Output = False, CopyConf = False) Dir(Parse(String = ConfDirs["FailedDir"]), Output = False, CopyConf = False)
NewName = os.path.basename(NewPath) NewName = os.path.basename(NewPath)
Clone(FilePath, Parse(String = ConfDirs["FailedDir"]), NewName, True) Clone(FilePath, Parse(String = ConfDirs["FailedDir"]), NewName, Delete)
TextOutput = Parse(String = ConfLog["NotSorted"], VarCall = FilePath) TextOutput = Parse(String = ConfLog["NotSorted"], VarCall = FilePath)
LogWrite(TextOutput) LogWrite(TextOutput)
@@ -381,12 +450,29 @@ def Init():
if Start: if Start:
for InputDir in ConfDirs["InputDir"]: for InputDir in ConfDirs["InputDir"]:
InputDir = Parse(String = InputDir, Path = ConfPath, Parent = os.path.basename(os.path.dirname(InputDir))) InputDir = Parse(String = InputDir, Path = ConfPath, Parent = os.path.basename(os.path.dirname(InputDir)))
Dir(InputDir, Output = False, CopyConf = False) Dir(InputDir, Output = False, CopyConf = "InputDirConf.toml")
for Output in ConfDirs["OutputDir"]:
Output = Parse(String = Output, Parent = os.path.basename(os.path.dirname(Output)))
Dir(Output)
OutDirConfPath = f"{Output}/{Parse(String = ConfNames["OutDirConfName"], Parent = os.path.basename(Output))}.toml"
ConfData = toml.load(OutDirConfPath)
for Item in ConfData.get("Files", []):
Character = re.escape(ConfVars["NextChar"])
Matches = re.findall(f"{Character}+", Item["NewFileName"])
Width = max((len(Match) for Match in Matches), default=0)
if Width > 0:
Item["NextChar"] = Item["NextChar"].upper().rjust(Width, "A")
with open(OutDirConfPath, "w", encoding = "utf-8") as ConfFile:
toml.dump(ConfData, ConfFile)
TextOutput = Parse(String = ConfLog["Start"], Path = ConfPath) TextOutput = Parse(String = ConfLog["Start"], Path = ConfPath)
LogWrite(TextOutput) LogWrite(TextOutput)
Speak(TextOutput) Speak(TextOutput)
Main() Main()
else: else:
@@ -395,31 +481,78 @@ def Init():
Speak(TextOutput) Speak(TextOutput)
else: else:
print( print("NO CONFIG FOUND! Make sure the 'GlobalConf.toml' is in the same directory as 'SaraSortd.py'")
"NO CONFIG FOUND! Make sure the 'Config.toml' is in the same directory as 'SaraSortd.py'"
)
def Main(): def Main():
for Output in ConfDirs["OutputDir"]:
Output = Parse(String = Output, Parent = os.path.basename(os.path.dirname(Output)))
Dir(Output)
try: try:
while True: while True:
for Input in ConfDirs["InputDir"]: for Input in ConfDirs["InputDir"]:
RecurseDir = False
InputPath = Parse(Input) InputPath = Parse(Input)
if not os.path.exists(InputPath): if not os.path.exists(InputPath):
continue continue
InputDirConfPath = f"{InputPath}/{Parse(ConfNames["InDirConfName"], Parent = os.path.basename(InputPath))}.toml"
InputDirConf = toml.load(InputDirConfPath)
for Output in ConfDirs["OutputDir"]:
OutputPath = Parse(Output)
if (OutputPath == InputPath):
RecurseDir = True
FileHistoryPath = f"{InputPath}/{Parse(ConfNames["FileHistoryName"], Parent = os.path.basename(InputPath))}.toml"
if not os.path.exists(FileHistoryPath):
with open(FileHistoryPath, "w", encoding = "utf-8") as HistoryFile:
toml.dump({"Title": f"{os.path.basename(InputPath)} History", "History": [] }, HistoryFile)
for FileName in os.listdir(InputPath): for FileName in os.listdir(InputPath):
FilePath = os.path.join(InputPath, FileName) FilePath = f"{InputPath}/{FileName}"
if os.path.isdir(FilePath) and InputDirConf["SortDirs"] == 0:
continue
FileName = os.path.basename(FilePath)
if FileName.startswith(".") and Conf["DotFiles"] == 0:
continue
for ConfFile in InputDirConf["Files"]:
if ConfFile["CaseSensitive"] == 1:
Match = fnmatch.fnmatchcase(FileName, Parse(ConfFile["Pattern"], Parent = os.path.basename(InputPath)))
else:
Match = fnmatch.fnmatchcase(FileName.lower(), (Parse(ConfFile["Pattern"], Parent = os.path.basename(InputPath))).lower())
if Match:
break
if Match:
if os.path.isdir(FilePath) and InputDirConf["SortDirs"] == 1:
TextOutput = Parse(String = ConfLog["Zipping"], VarCall = FilePath)
LogWrite(TextOutput)
Speak(TextOutput)
shutil.make_archive(FilePath, "zip", FilePath)
try:
os.rmdir(FilePath)
except PermissionError:
TextOutput = Parse(String = ConfLog["NoPermission"], VarCall = f"remove directory {InputPath}/{FileName}")
LogWrite(TextOutput)
Speak(TextOutput)
continue
FilePath = f"{FilePath}.zip"
if RecurseDir:
HistoryFile = toml.load(FileHistoryPath)
if (Parse(FileName, Parent = os.path.basename(InputPath)) in HistoryFile["History"]):
continue
if os.path.isfile(FilePath):
FileName = os.path.basename(FilePath)
if FileName.startswith(".") and Conf["DotFiles"] == 0:
continue
Sort(FilePath) Sort(FilePath)
time.sleep(Conf.get("CheckInput", 10)) time.sleep(Conf.get("CheckInput", 10))
@@ -429,5 +562,4 @@ def Main():
LogWrite(TextOutput) LogWrite(TextOutput)
Speak(TextOutput) Speak(TextOutput)
Init() Init()