It works! Sorry for lack of pushes. :p

This commit is contained in:
sarah committed 2026-04-03 21:23:13 +02:00
1 parent bb2056ed68
commit 3b06574b62
4 files changed
+194 -80

No files matched your search

+10 -9
View File
@@ -2,10 +2,10 @@ Title = "SaraSortd Config"
SafeMode = 1 SafeMode = 1
SilentMode = 0 SilentMode = 0
CheckInput = 10 CheckInput = 10
OutputDir = [ "Log",] DotFiles = 1
[Variables] [Variables]
NextNum = "*" NextNum = "!"
NextChar = "&" NextChar = "&"
Parent = "@" Parent = "@"
OrgFileName = "~" OrgFileName = "~"
@@ -20,20 +20,21 @@ VarCall = "%%"
[DirectoryPaths] [DirectoryPaths]
RootDir = "/home/sarah/TESTDIR" RootDir = "/home/sarah/TESTDIR"
InputDir = [ "%RootDir%",] InputDir = [ "%RootDir%"]
OutputDir = [ "%RootDir%/Output",] OutputDir = [ "%RootDir%/Output"]
LogDir = "%RootDir%/.Logs" LogDir = "%RootDir%/.Logs"
FailedDir = "%RootDir%/.Failed" FailedDir = "%RootDir%/.Failed"
[Names] [Names]
LogFileName = "[y].[m].[d]._*" LogFileName = "[y].[m].[d]._!"
DirConfName = ".@Config" DirConfName = ".@Config"
[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."
Sorted = "Sorted: %% to %%." MatchPattern = "Checking %%."
ValueSet = "Set: %% to be %% in %%." Sorted = "Sorted %%."
WrongType = "Error: %% is not a %%." NotSorted = "Error: Failed to sort %%. No valid output directory."
ValueSet = "Set %%."
Unset = "Error: %% is not set." Unset = "Error: %% is not set."
+1 -1
View File
@@ -1,7 +1,7 @@
Title = "Unset" Title = "Unset"
Files = [ Files = [
{Pattern = "?", NewFileName = "~^", NextNum = 1, NextChar = "A"}, {Pattern = "*.png", NewFileName = "~^", NextNum = 1, NextChar = "A"},
] ]
LastFile = "Unset" LastFile = "Unset"
+2 -2
View File
@@ -69,8 +69,8 @@ Title = "Unset" # NOTE: Do not change the title.
Files = [ Files = [
{Pattern = "?", NewFileName = "~^", NextNum = 1, NextChar = "A"}, {Pattern = "?", NewFileName = "~^", NextNum = 1, NextChar = "A"},
] ]
# Pattern: the pattern used to sort files here. ? is a wildcard. # Pattern: the pattern used to sort files here. * is a wildcard, ? is a one-character wildcard.
# E.G: if Pattern is "?.png", all files ending with .png will end up here. # E.G: if Pattern is "*.png", all files ending with .png will end up here.
# NewFileName: the pattern for the new names of the files that get sorted here. # NewFileName: the pattern for the new names of the files that get sorted here.
+181 -68
View File
@@ -1,4 +1,6 @@
import toml, os, datetime, re, shutil import toml, os, datetime, re, shutil, fnmatch, time
ConfPath = "./Config.toml"
Conf = None Conf = None
ConfVars = None ConfVars = None
@@ -15,10 +17,10 @@ def Clear():
os.system('clear') os.system('clear')
def LoadConf(): def LoadGlobalConf():
global Conf, ConfVars, ConfDirs, ConfNames, ConfLog global Conf, ConfVars, ConfDirs, ConfNames, ConfLog
LocalConf = toml.load("Config.toml") LocalConf = toml.load(ConfPath)
Conf = LocalConf Conf = LocalConf
ConfVars = Conf["Variables"] ConfVars = Conf["Variables"]
ConfDirs = Conf["DirectoryPaths"] ConfDirs = Conf["DirectoryPaths"]
@@ -26,41 +28,31 @@ def LoadConf():
ConfLog = Conf["Log"] ConfLog = Conf["Log"]
def CheckConf(): def CheckConf(Path = ConfPath):
for Parameter, Value in ConfVars.items(): ConfData = toml.load(Path)
if (Value == None or Value == "Unset"):
TextOutput = Parse(ConfLog["Unset"], Parameter) def CheckDict(Data):
for Key, Value in Data.items():
if isinstance(Value, dict):
CheckDict(Value)
else:
if Value is None or Value == "Unset":
TextOutput = Parse(String = ConfLog["Unset"], Path = Path, VarCall = Key)
LogWrite(TextOutput) LogWrite(TextOutput)
Speak(TextOutput) Speak(TextOutput)
Error() Error()
for Parameter, Value in ConfDirs.items(): CheckDict(ConfData)
if (Value == None or Value == "Unset"):
TextOutput = Parse(ConfLog["Unset"], Parameter)
LogWrite(TextOutput)
Speak(TextOutput)
Error()
for Parameter, Value in ConfNames.items():
if (Value == None or Value == "Unset"):
TextOutput = Parse(ConfLog["Unset"], Parameter)
LogWrite(TextOutput)
Speak(TextOutput)
Error()
for Parameter, Value in ConfLog.items():
if (Value == None or Value == "Unset"):
TextOutput = Parse(ConfLog["Unset"], Parameter)
LogWrite(TextOutput)
Speak(TextOutput)
Error()
def GetConf(Parameter): def GetConf(Parameter, *Paths):
if Parameter in Conf: for Path in Paths:
return (Conf[Parameter]) ConfData = toml.load(Path)
for Key, Value in Conf.items(): if Parameter in ConfData:
return (ConfData[Parameter])
for Key, Value in ConfData.items():
if (isinstance(Value, dict)): if (isinstance(Value, dict)):
if (Parameter in Value): if (Parameter in Value):
return Value[Parameter] return Value[Parameter]
@@ -71,24 +63,41 @@ def GetConf(Parameter):
def UpdateConf(Path, Parameter, Value, Append = False): def UpdateConf(Path, Parameter, Value, Append = False):
ConfData = toml.load(Path) ConfData = toml.load(Path)
if (not Append): Section = None
ConfData[Parameter] = Value for Table, Data in ConfData.items():
if (isinstance(Data, dict) and Parameter in Data):
Section = Table
break
if (Section == None):
TargetData = ConfData
else: else:
if (Parameter not in ConfData): TargetData = ConfData[Section]
ConfData[Parameter] = []
elif (not isinstance(ConfData[Parameter], list)): if (not Append):
TextOutput = Parse(ConfLog["WrongType"], Parameter, "list") TargetData[Parameter] = Value
LogWrite(TextOutput)
Speak(TextOutput)
if (Value not in ConfData[Parameter]): else:
ConfData[Parameter].append(Value) if (Parameter not in TargetData):
TargetData[Parameter] = []
if (Value not in TargetData[Parameter]):
TargetData[Parameter].append(Value)
if (Section == None):
ConfData = TargetData
else:
ConfData[Section] = TargetData
with open(Path, 'w', encoding='utf-8') as File: with open(Path, 'w', encoding='utf-8') as File:
toml.dump(ConfData, File) toml.dump(ConfData, File)
TextOutput = Parse(String = ConfLog["ValueSet"], VarCall = f"{Parameter} to {Value}")
LogWrite(TextOutput)
Speak(TextOutput)
def Error(): def Error():
global Start global Start
@@ -101,36 +110,47 @@ def Speak(Text):
print(Text) print(Text)
def Parse(String = None, Var = None, Path = None): def Parse(String = None, Path = ConfPath,
NextNum = None,
NextChar = None,
Parent = None,
OrgFile = None,
VarCall = None):
def Replacer(Match): def Replacer(Match):
Content = Match.group(1) Content = Match.group(1)
if (Content == None or Content == ""): if (Content == None or Content == ""):
if (Var != None): if (VarCall != None):
return str(Var) return str(VarCall)
else: else:
return "" return ""
Value = GetConf(Content) Value = GetConf(Content, Path)
if (Value != None): if (Value != None):
return Value return Value
Now = datetime.datetime.now() Now = datetime.datetime.now()
if (OrgFile != None):
OrgFileName, OrgFileType = os.path.splitext(OrgFile)
else:
OrgFileName = None
OrgFileType = None
VarValues = { VarValues = {
ConfVars["NextNum"]: "NOT ADDED", ConfVars["NextNum"]: NextNum,
ConfVars["NextChar"]: "NOT ADDED", ConfVars["NextChar"]: NextChar,
ConfVars["Parent"]: Var, ConfVars["Parent"]: Parent,
ConfVars["OrgFileName"]: Var, ConfVars["OrgFileName"]: OrgFileName,
ConfVars["OrgFileType"]: Var, ConfVars["OrgFileType"]: OrgFileType,
ConfVars["Year"]: str(Now.year), ConfVars["Year"]: str(Now.year),
ConfVars["Month"]: str(Now.month), ConfVars["Month"]: str(Now.month),
ConfVars["Day"]: str(Now.day), ConfVars["Day"]: str(Now.day),
ConfVars["Hour"]: str(Now.hour), ConfVars["Hour"]: str(Now.hour),
ConfVars["Minute"]: str(Now.minute), ConfVars["Minute"]: str(Now.minute),
ConfVars["Second"]: str(Now.second), ConfVars["Second"]: str(Now.second),
ConfVars["VarCall"]: Var ConfVars["VarCall"]: VarCall
} }
for Key, Value in VarValues.items(): for Key, Value in VarValues.items():
@@ -146,52 +166,126 @@ def Parse(String = None, Var = None, Path = None):
return String return String
def Clone(Source, Destination, NewName): def Clone(Source, Destination, NewName, Delete = False):
shutil.copy(Source, Destination) Dir(Destination, Output = False, CopyConf = False)
shutil.copy2(Source, Destination)
NewPath = f"{Destination}/{NewName}" BaseName = os.path.basename(Source)
shutil.move(f"{Destination}/{Source}", NewPath) shutil.move(os.path.join(Destination, BaseName), os.path.join(Destination, NewName))
if (Delete):
os.remove(Source)
def Dir(Path, Output = True, CopyConf = True): def Dir(Path, Output = True, CopyConf = True):
ParentName = os.path.basename(os.path.dirname(Path)) ParentName = os.path.basename(Path)
os.makedirs(Path, exist_ok=True) os.makedirs(Path, exist_ok=True)
if (CopyConf): if (CopyConf):
Clone("DirConfig.toml", Path, f"{Parse(ConfNames["DirConfName"], ParentName)}.toml") Clone("DirConfig.toml", Path, f"{Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName)}.toml")
UpdateConf(f"{Path}/{Parse(ConfNames["DirConfName"], ParentName)}.toml", "ParentDir", ParentName) UpdateConf(f"{Path}/{Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName)}.toml", "ParentDir", ParentName)
UpdateConf(f"{Path}/{Parse(ConfNames["DirConfName"], ParentName)}.toml", "Title", f"{ParentName} Config") UpdateConf(f"{Path}/{Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName)}.toml", "Title", f"{ParentName} Config")
if (Output): if (Output):
UpdateConf("Config.toml", "OutputDir", Output, True) ParsedPath = Parse(String = Path)
ParsedOutputs = [Parse(String = Dir) for Dir in ConfDirs["OutputDir"]]
if (ParsedPath not in ParsedOutputs):
UpdateConf(ConfPath, "OutputDir", Path, True)
def LogWrite(Text): def LogWrite(Text):
if (ConfDirs["LogDir"] != None and ConfNames != "Unset"): if (ConfDirs["LogDir"] != None and ConfNames != "Unset"):
if (ConfNames["LogFileName"] != None and ConfNames["LogFileName"] != "Unset"): if (ConfNames["LogFileName"] != None and ConfNames["LogFileName"] != "Unset"):
Dir(Parse(ConfDirs["LogDir"]), "Log") Dir(Parse(String = ConfDirs["LogDir"], Path = ConfPath), Output = False, CopyConf = False)
LogFile = f"{Parse(ConfDirs["LogDir"])}/{Parse(ConfNames["LogFileName"])}.log" LogFile = f"{Parse(String = ConfDirs["LogDir"], Path = ConfPath)}/{Parse(String = ConfNames["LogFileName"], Path = ConfPath)}.log"
with open(LogFile, "w", encoding="utf-8") as File: with open(LogFile, "a", encoding="utf-8", buffering=1) as File:
File.write(f"{Parse(ConfLog["All"])}{Text}\n") File.write(f"{Parse(String = ConfLog["All"], Path = ConfPath)}{Text}\n")
def DecideNewPath(FilePath):
UnsortedFile = os.path.basename(FilePath)
for Output in ConfDirs["OutputDir"]:
Output = Parse(String = Output, Path = ConfPath)
ParentName = os.path.basename(Output)
DirConfName = Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName)
DirConfPath = f"{Output}/{DirConfName}.toml"
if not os.path.exists(DirConfPath):
continue
FileConf = GetConf("Files", DirConfPath)
if not FileConf:
continue
for File in FileConf:
Pattern = File["Pattern"]
TextOutput = Parse(ConfLog["MatchPattern"], VarCall = f"{UnsortedFile} against {Pattern}")
LogWrite(TextOutput)
Speak(TextOutput)
if fnmatch.fnmatch(UnsortedFile, Pattern):
NewFileName = File["NewFileName"]
NewFileName = Parse(String = NewFileName, Path = DirConfPath, OrgFile = UnsortedFile)
return f"{Output}/{NewFileName}"
def Sort(FilePath):
NewPath = DecideNewPath(FilePath)
if (NewPath == None):
Dir(Parse(String = ConfDirs["FailedDir"]), Output = False, CopyConf = False)
NewName = os.path.basename(FilePath)
Clone(FilePath, Parse(String = ConfDirs["FailedDir"]), NewName, True)
return
NewDirPath = os.path.dirname(NewPath)
NextNum = GetConf("NextNum", f"{NewDirPath}/{Parse(String = ConfNames["DirConfName"], Parent = os.path.basename(NewDirPath))}.toml")
NextChar = GetConf("NextChar", f"{NewDirPath}/{Parse(String = ConfNames["DirConfName"], Parent = os.path.basename(NewDirPath))}.toml")
NewName = Parse(os.path.basename(FilePath), NextNum = NextNum, NextChar = NextChar)
try:
Clone(FilePath, NewDirPath, NewName, True)
TextOutput = Parse(String = ConfLog["Sorted"], VarCall = f"{FilePath} to {NewDirPath}/{NewName}")
LogWrite(TextOutput)
Speak(TextOutput)
except Exception:
Dir(Parse(String = ConfDirs["FailedDir"]), Output = False, CopyConf = False)
NewName = os.path.basename(FilePath)
Clone(FilePath, Parse(String = ConfDirs["FailedDir"]), NewName, True)
TextOutput = Parse(String = ConfLog["NotSorted"], VarCall = FilePath)
LogWrite(TextOutput)
Speak(TextOutput)
return
def Init(): def Init():
global Conf, ConfVars, ConfDirs, ConfNames, ConfLog global Conf, ConfVars, ConfDirs, ConfNames, ConfLog
LoadConf() LoadGlobalConf()
if (Conf != None): if (Conf != None):
CheckConf() CheckConf()
if (Start): if (Start):
TextOutput = Parse(ConfLog["Start"]) TextOutput = Parse(String = ConfLog["Start"], Path = ConfPath)
LogWrite(TextOutput) LogWrite(TextOutput)
Speak(TextOutput) Speak(TextOutput)
Main() Main()
else: else:
TextOutput = Parse(ConfLog["NotStart"]) TextOutput = Parse(String = ConfLog["NotStart"])
LogWrite(TextOutput) LogWrite(TextOutput)
Speak(TextOutput) Speak(TextOutput)
@@ -200,8 +294,27 @@ def Init():
def Main(): def Main():
for Output in ConfDirs["OutputDir"]:
Output = Parse(String = Output)
Dir(Output)
while True: while True:
pass for Input in ConfDirs["InputDir"]:
InputPath = Parse(Input)
if not os.path.exists(InputPath):
continue
for FileName in os.listdir(InputPath):
FilePath = os.path.join(InputPath, FileName)
if os.path.isfile(FilePath):
FileName = os.path.basename(FilePath)
if (FileName.startswith(".") and Conf["DotFiles"] == 0):
continue
Sort(FilePath)
time.sleep(Conf.get("CheckInput", 10))
Init() Init()