Hier das Skript, habe es nun einige Tage am laufen und scheint soweit das zu tun was ich will. Bitte vor Benutzung am Produktivsystem mal irgendwo mit Beispielordnern testen um zu sehen: Die Standardpfade müssen dann halt bei jedem angepasst werden. Es ist mit einer GUI, was eigentlich dann wenig sinn macht bzw. nur für die Erstkonfiguration der .ini, Ich habe es dann letztlich über die Windows Aufgabenplanung beim Systemstart eingebaut. Leider ist dadurch das Tray Icon später nicht zu sehen aber der Task Manager zeigt an dass es läuft und wenn es dies tut erscheinen im gewünschten Zielordner die PDF dateien... Aber alles ohne Gewähr und auf eigene Verantwortung. Die Globalen Variablen für ini File und HistoryFile müssen noch angepasst werden. Natürlich braucht man Autohotkey V2 auf dem Server.
Code: Alles auswählen
#Requires AutoHotkey v2.0
#SingleInstance Force
SetWorkingDir(A_ScriptDir)
A_IconTip := "KIMSync - Arztbrief Überwachung" ; Der Text, der beim Drüberfahren erscheint
TraySetIcon("shell32.dll", 283) ; Ein Standard-Windows-Icon (z.B. ein kleines Dokument)
; --- Globale Variablen ---
global IniFile := "D:\Eigene Skripte\KIMSync\KIMSync.ini"
global HistoryFile := "D:\Eigene Skripte\KIMSync\KIMSync_History.txt"
global ProcessedFiles := Map()
global IsSyncing := false
try {
FileAppend(FormatTime(,"yyyy-MM-dd HH:mm:ss") . " - Skript gestartet.`n", A_ScriptDir "\debug.log")
} catch as e {
MsgBox("Konnte debug.log nicht schreiben: " . e.Message)
}
; Lade bereits verarbeitete Dateien aus der History
if FileExist(HistoryFile) {
Loop Read HistoryFile {
if (A_LoopReadLine != "") {
; Teilt die Zeile beim "|" Zeichen. Format: Pfad|Zeitstempel
parts := StrSplit(A_LoopReadLine, "|")
if (parts.Length == 2)
ProcessedFiles[parts[1]] := parts[2] ; Speichert Pfad + Zeitstempel
else
ProcessedFiles[A_LoopReadLine] := "ALT" ; Abwärtskompatibilität für alte History
}
}
}
; --- INI Initialisierung (Defaults) ---
If !FileExist(IniFile) {
IniWrite("D:\TurboMed\Daten\Var\eArztbrief\Empfang", IniFile, "Pfade", "Quelle")
IniWrite("\\fritz.nas\fritzbox\USB-SanDisk3-2Gen1-01\FRITZ\faxbox", IniFile, "Pfade", "Ziel")
IniWrite("pdf", IniFile, "Typen", "Alle")
IniWrite("pdf", IniFile, "Typen", "Aktiv")
IniWrite("0", IniFile, "Einstellungen", "ErledigtVerschieben")
IniWrite("1", IniFile, "Einstellungen", "InitBaseline") ; Standardmäßig Altbestand ignorieren
IniWrite("0", IniFile, "Einstellungen", "AutoStart") ; Standardmäßig Autostart aus
}
; --- Konfiguration laden ---
global QuellPfad := IniRead(IniFile, "Pfade", "Quelle", "D:\TurboMed\Daten\Var\eArztbrief\Empfang")
global ZielPfad := IniRead(IniFile, "Pfade", "Ziel", "\\muss\angepasst\werden")
global AlleTypen := IniRead(IniFile, "Typen", "Alle", "pdf")
global AktiveTypen := IniRead(IniFile, "Typen", "Aktiv", "pdf")
global MoveErledigt := IniRead(IniFile, "Einstellungen", "ErledigtVerschieben", "0")
global InitBaseline := IniRead(IniFile, "Einstellungen", "InitBaseline", "1")
global AutoStart := IniRead(IniFile, "Einstellungen", "AutoStart", "0")
; --- GUI Aufbau ---
MainGui := Gui("+Resize -MaximizeBox", "KIMSync - Verzeichnisüberwachung")
MainGui.OnEvent("Close", GuiClose)
; Sektion 1: Pfade & Hilfe
MainGui.Add("Text", "xm ym+4 w330", "Quellverzeichnis (Überwachung):")
MainGui.Add("Button", "x+5 ym w55 h24", "Hilfe").OnEvent("Click", ShowHelp)
global EdtQuelle := MainGui.Add("Edit", "xm y+5 w350", QuellPfad)
MainGui.Add("Button", "x+5 yp w40", "...").OnEvent("Click", (*) => SelectFolder(EdtQuelle))
MainGui.Add("Text", "xm y+15 w400", "Zielverzeichnis (Kopie):")
global EdtZiel := MainGui.Add("Edit", "xm y+5 w350", ZielPfad)
MainGui.Add("Button", "x+5 yp w40", "...").OnEvent("Click", (*) => SelectFolder(EdtZiel))
; Sektion 2: Dateitypen
MainGui.Add("Text", "xm y+20 w350", "Zu kopierende Dateitypen (mit Haken aktiv):")
global LV_Ext := MainGui.Add("ListView", "xm y+5 w350 r6 Checked -Multi", ["Dateiendung"])
; ListView Daten befüllen
AktiveMap := Map()
For Ext in StrSplit(AktiveTypen, ",")
if Trim(Ext) != ""
AktiveMap[StrLower(Trim(Ext))] := True
For Ext in StrSplit(AlleTypen, ",") {
cleanExt := StrLower(Trim(Ext))
if (cleanExt != "") {
RowId := LV_Ext.Add("", cleanExt)
if AktiveMap.Has(cleanExt)
LV_Ext.Modify(RowId, "Check")
}
}
; Werkzeuge für Dateitypen
MainGui.Add("Text", "xm y+10 w120", "Neue Endung (z.B. xml):")
global EdtNeuExt := MainGui.Add("Edit", "x+5 yp-3 w80", "")
global BtnAdd := MainGui.Add("Button", "x+10 yp w65", "Neu")
BtnAdd.OnEvent("Click", BtnAddExt)
global BtnDel := MainGui.Add("Button", "x+10 yp w65", "Löschen")
BtnDel.OnEvent("Click", BtnDelExt)
; Sektion 3: Einstellungen & Steuerung
global ChkBaseline := MainGui.Add("CheckBox", "xm y+25 " (InitBaseline ? "Checked" : ""), "Einmalig: Vorhandenen Altbestand ignorieren (nur künftige kopieren)")
global ChkErledigt := MainGui.Add("CheckBox", "xm y+5 " (MoveErledigt ? "Checked" : ""), "Nach Erfolg: Überordner in 'erledigt' verschieben")
global BtnStart := MainGui.Add("Button", "xm y+15 w150 h40 default", "Überwachung Starten")
BtnStart.OnEvent("Click", ToggleSync)
global SB := MainGui.Add("StatusBar",, " Bereit.")
MainGui.Show()
; --- Autostart Logik ---
; Wenn das Programm beim letzten Schließen aktiv war, starte die Überwachung sofort
if (AutoStart == "1") {
ToggleSync()
}
; --- Logik-Funktionen ---
ShowHelp(*) {
HelpText := (
"Guten Tag!`n`n"
"Dieses Programm arbeitet wie ein unsichtbarer, fleißiger Helfer im Hintergrund. "
"Sobald in TurboMed ein neuer Arztbrief ankommt, macht das Programm automatisch eine Kopie "
"davon und legt sie zur Sicherheit auf der FritzBox ab.`n`n"
"Hier ist die kurze Erklärung der Knöpfe und Felder:`n`n"
"1. Quellverzeichnis: Das ist der Ordner, in dem TurboMed die neuen Briefe ablegt.`n"
"2. Zielverzeichnis: Das ist das Ziel (z.B. die FritzBox), wo die Kopie sicher aufbewahrt wird.`n"
"3. Dateitypen: Hier steht, dass z.B. nur PDFs beachtet werden sollen.`n`n"
"WICHTIG - 'Vorhandenen Altbestand ignorieren':`n"
"Wenn Sie das Programm zum allerersten Mal starten, setzen Sie hier den Haken. Das Programm "
"merkt sich dann blitzschnell alle bisherigen alten Briefe, OHNE sie zu kopieren. Danach "
"werden nur noch brandneue Briefe kopiert. Der Haken verschwindet dann von selbst.`n`n"
"WICHTIG - 'Überordner in erledigt verschieben':`n"
"Setzen Sie diesen Haken, wenn das Programm nach dem Kopieren aufräumen soll. Der Ordner des "
"Arztbriefs wird dann in einen neuen Unterordner namens 'erledigt' verschoben. "
"So bleibt der TurboMed-Ordner immer schön aufgeräumt und übersichtlich.`n`n"
"Automatische Fortsetzung:`n"
"Wenn Sie das Programm abends schließen (oder der PC heruntergefahren wird), während die "
"Überwachung noch lief, merkt sich das Programm das! Beim nächsten Start legt es sofort "
"wieder automatisch los.`n`n"
"Bedienung:`n"
"Klicken Sie einfach auf 'Überwachung Starten' und lassen Sie das Fenster offen (Sie können "
"es klein machen). Das Programm passt nun von allein auf."
)
MsgBox(HelpText, "Anleitung & Hilfe", "Iconi")
}
SelectFolder(EditControl) {
SelectedPath := DirSelect(EditControl.Value, 3, "Bitte Ordner auswählen:")
if (SelectedPath != "")
EditControl.Value := SelectedPath
}
BtnAddExt(*) {
newExt := StrLower(StrReplace(Trim(EdtNeuExt.Value), ".", ""))
if (newExt != "") {
Loop LV_Ext.GetCount() {
if (LV_Ext.GetText(A_Index, 1) == newExt) {
EdtNeuExt.Value := ""
return
}
}
LV_Ext.Add("Check", newExt)
EdtNeuExt.Value := ""
}
}
BtnDelExt(*) {
RowNumber := 0
RowsToDelete := []
Loop {
RowNumber := LV_Ext.GetNext(RowNumber)
if not RowNumber
break
RowsToDelete.Push(RowNumber)
}
Loop RowsToDelete.Length {
idx := RowsToDelete.Length - A_Index + 1
LV_Ext.Delete(RowsToDelete[idx])
}
}
SaveConfig() {
IniWrite(EdtQuelle.Value, IniFile, "Pfade", "Quelle")
IniWrite(EdtZiel.Value, IniFile, "Pfade", "Ziel")
IniWrite(ChkErledigt.Value, IniFile, "Einstellungen", "ErledigtVerschieben")
IniWrite(ChkBaseline.Value, IniFile, "Einstellungen", "InitBaseline")
IniWrite((IsSyncing ? "1" : "0"), IniFile, "Einstellungen", "AutoStart")
AllArr := [], ActArr := []
Loop LV_Ext.GetCount() {
txt := LV_Ext.GetText(A_Index, 1)
AllArr.Push(txt)
if (LV_Ext.GetNext(A_Index - 1, "Checked") == A_Index)
ActArr.Push(txt)
}
IniWrite(Join(AllArr, ","), IniFile, "Typen", "Alle")
IniWrite(Join(ActArr, ","), IniFile, "Typen", "Aktiv")
}
Join(Arr, Sep) {
str := ""
For v in Arr
str .= (str == "" ? "" : Sep) v
return str
}
GuiClose(*) {
SaveConfig()
ExitApp
}
ToggleSync(*) {
global IsSyncing
SaveConfig()
if IsSyncing {
SetTimer(SyncRoutine, 0)
BtnStart.Text := "Überwachung Starten"
IsSyncing := false
SB.SetText(" Überwachung gestoppt.")
} else {
Q := EdtQuelle.Value
if !DirExist(Q) {
SB.SetText(" Fehler: Quellpfad nicht gefunden.")
return
}
if (ChkBaseline.Value) {
SB.SetText(" Erfasse Altbestand. Bitte warten...")
AktiveMap := Map()
Loop LV_Ext.GetCount() {
if (LV_Ext.GetNext(A_Index - 1, "Checked") == A_Index)
AktiveMap[StrLower(LV_Ext.GetText(A_Index, 1))] := True
}
Loop Files, Q "\*.*", "R" {
if InStr(A_LoopFilePath, Q "\erledigt\")
continue
if !AktiveMap.Has(StrLower(A_LoopFileExt))
continue
; Wenn Datei noch nicht bekannt ist ODER wenn das Änderungsdatum abweicht
if !ProcessedFiles.Has(A_LoopFilePath) || (ProcessedFiles[A_LoopFilePath] != "ALT" && ProcessedFiles[A_LoopFilePath] != A_LoopFileTimeModified) {
ProcessedFiles[A_LoopFilePath] := A_LoopFileTimeModified
FileAppend A_LoopFilePath "|" A_LoopFileTimeModified "`n", HistoryFile
}
}
ChkBaseline.Value := 0
SaveConfig()
}
SetTimer(SyncRoutine, 5000)
BtnStart.Text := "Überwachung Stoppen"
IsSyncing := true
SB.SetText(" Überwachung aktiv...")
SyncRoutine()
}
}
SyncRoutine() {
Q := EdtQuelle.Value
Z := EdtZiel.Value
if !DirExist(Q) || !DirExist(Z) {
SB.SetText(" Fehler: Quell- oder Zielpfad nicht gefunden.")
return
}
AktiveMap := Map()
RowId := 0
Loop {
RowId := LV_Ext.GetNext(RowId, "Checked")
if not RowId
break
AktiveMap[StrLower(LV_Ext.GetText(RowId, 1))] := True
}
FoldersToMove := Map()
Loop Files, Q "\*.*", "R" {
if InStr(A_LoopFilePath, Q "\erledigt\")
continue
if !AktiveMap.Has(StrLower(A_LoopFileExt))
continue
; PRÜFUNG: Ist die Datei schon bekannt UND hat sich das Datum NICHT geändert?
if ProcessedFiles.Has(A_LoopFilePath) {
; Wenn alter Eintrag ohne Datum ODER Datum ist exakt gleich -> überspringen
if (ProcessedFiles[A_LoopFilePath] == "ALT" || ProcessedFiles[A_LoopFilePath] == A_LoopFileTimeModified)
continue
}
try {
FreeMB := DriveGetSpaceFree(Z)
if (FreeMB < ((A_LoopFileSize / 1024 / 1024) + 10)) {
SB.SetText(" Fehler: Zielspeicher fast voll!")
continue
}
}
TargetFile := Z "\" A_LoopFileName
if FileExist(TargetFile) {
SplitPath A_LoopFileName, &NameNoExt, , &OutExt
c := 1
while FileExist(Z "\" NameNoExt "_" c "." OutExt)
c++
TargetFile := Z "\" NameNoExt "_" c "." OutExt
}
try {
FileCopy A_LoopFilePath, TargetFile
if FileExist(TargetFile) && (FileGetSize(TargetFile) == A_LoopFileSize) {
; Erfolgreich kopiert: Neuen Zeitstempel in die History aufnehmen
ProcessedFiles[A_LoopFilePath] := A_LoopFileTimeModified
FileAppend A_LoopFilePath "|" A_LoopFileTimeModified "`n", HistoryFile
if (ChkErledigt.Value) {
SplitPath A_LoopFilePath, , &FDir
if (FDir != Q)
FoldersToMove[FDir] := True
}
}
}
}
if (ChkErledigt.Value && FoldersToMove.Count > 0) {
ErledigtBase := Q "\erledigt"
if !DirExist(ErledigtBase)
DirCreate(ErledigtBase)
For FDir in FoldersToMove {
if !DirExist(FDir)
continue
SplitPath FDir, &DirName
TargetFolder := ErledigtBase "\" DirName
if DirExist(TargetFolder) {
c := 1
while DirExist(ErledigtBase "\" DirName "_" c)
c++
TargetFolder := ErledigtBase "\" DirName "_" c
}
try {
DirMove FDir, TargetFolder
}
}
}
}