diff --git a/.gitignore b/.gitignore index 3ca8125..011085e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,102 @@ +# Modern Delphi 12.3 .gitignore +# +#------------------------------------------------------------------------------- +# Delphi / C++Builder Compiled Output & Binaries +#------------------------------------------------------------------------------- *.exe *.dll +*.so +*.a +*.lib +*.bpl +*.dcp *.dcu -*.bkm +*.obj +*.o + +# Compiled resource files +*.res + +#------------------------------------------------------------------------------- +# IDE Generated, User-Specific & Intermediate Files +#------------------------------------------------------------------------------- +*.~* +*.bak +*.old + +# Delphi 7 and Delphi 6 Diagram Portfolio +*.ddp + +# Delphi 7 (and older) Project Configuration filed automatically created by command line compiler +*.cfg + +# General temporary files +*.tmp + +# General temporary files +*.temp + +*.local +*.dsk +*.cfg +*.map +*.tds *.drc -*.zip -dcu \ No newline at end of file +*.rsm +*.log +*.pch +*.ipch +*.stat +*.identcache +*.dproj.local +*.dproj.user +*.groupproj.local +*.groupproj.user + +# Delphi IDE bookmarks +*.bkm + +# TestInsight configuration files +*.tvsconfig + +# Files with .history extension +*.history + +# Legacy project files +*.dof +*.kof + +#------------------------------------------------------------------------------- +# IDE History, Recovery & Autosave +#------------------------------------------------------------------------------- +**/__history/ +**/__recovery/ +*.autosave + +#------------------------------------------------------------------------------- +# Output Directories (using **/ to match at any depth) +#------------------------------------------------------------------------------- +**/Win32/ +**/Win64/ +**/Debug/ +**/Release/ + +# Cross-platform output folders +**/Linux64/ +**/OSX64/ +**/OSXARM64/ +**/Android/ +**/Android64/ +**/iOSDevice32/ +**/iOSDevice64/ +**/iOSSimulator/ + +#------------------------------------------------------------------------------- +# Common Temporary / Local / Sensitive Files (General) +#------------------------------------------------------------------------------- +# General compressed archives (often backups or downloads) +*.zip + +# Environment variable files (CRITICAL for sensitive data) +*.env + diff --git a/Main.pas b/Main.pas index 3a6ab50..af741e1 100644 --- a/Main.pas +++ b/Main.pas @@ -210,46 +210,103 @@ procedure TfrmUI.FormCreate(Sender: TObject); End; end; +// Place this function in a common unit or in the same unit as your form. +// By Gemini 2.5 Pro +function WidePosEx(const SubStr: WideString; const S: WideString; StartPos: Integer = 1): Integer; +var + I, J: Integer; + Found: Boolean; +begin + Result := 0; + if (Length(SubStr) = 0) then + begin + if (StartPos > Length(S) + 1) or (StartPos < 1) then + Exit; + Result := StartPos; + Exit; + end; + + if (StartPos < 1) or (StartPos > Length(S) - Length(SubStr) + 1) then + Exit; + + for I := StartPos to Length(S) - Length(SubStr) + 1 do + begin + Found := True; + for J := 1 to Length(SubStr) do + begin + if S[I + J - 1] <> SubStr[J] then + begin + Found := False; + Break; + end; + end; + if Found then + begin + Result := I; + Exit; + end; + end; +end; + +// By Gemini 2.5 Pro Procedure TfrmUI.SaveSettings; -var - reg: TTntRegistry; - diskSize: Int64; - i:Integer; - s:WideString; -Begin - reg:=TTntRegistry.Create(KEY_WRITE); - Try - reg.RootKey:=HKEY_LOCAL_MACHINE; - if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\'+serviceName, True) then - begin - diskSize:=StrToInt64(vdSize.Text); - If radioMB.Checked then diskSize:=diskSize Shl 20 Else diskSize:=diskSize shl 30; - i:=0; - while i0)or(WidePosEx('/',s)>0) then memoIgnore.Lines.Delete(i) - else - Begin - memoIgnore.Lines[i]:=s; - Inc(i); - end; - end; - reg.WriteString('DiskSize',IntToStr(diskSize)); - reg.WriteString('DriveLetter',comboLetter.Text); - reg.WriteString('LoadContent',editFolder.Text); - reg.WriteString('ExcludeFolders',memoIgnore.Lines.Text); - reg.WriteBool('UseTempFolder',chkTemp.Checked); - reg.WriteBool('SyncContent',chkSync.Checked); - reg.WriteBool('DeleteOld',chkDelete.Checked); - Reg.CloseKey; - end; - Finally - reg.Free; - End; +var + reg: TTntRegistry; + diskSize: Int64; + i: Integer; + s: WideString; +Begin + reg := TTntRegistry.Create(KEY_WRITE); + Try + reg.RootKey := HKEY_LOCAL_MACHINE; + if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\' + serviceName, True) then + begin + diskSize := StrToInt64(vdSize.Text); + If radioMB.Checked then + diskSize := diskSize shl 20 + Else + diskSize := diskSize shl 30; + + // Corrected loop logic to handle deletions without skipping items + i := 0; + while i < memoIgnore.Lines.Count do + begin + s := Trim(memoIgnore.Lines[i]); + + // This is a check for a drive letter and colon, e.g., "C:" + if (Length(s) >= 2) and (s[2] = ':') then + s := Copy(s, 4, MaxInt); // Correctly gets the rest of the path + + // Check for empty string, backslash, or forward slash + // We use the custom WidePosEx function to find the characters + if (s = '') or (WidePosEx('\', s) > 0) or (WidePosEx('/', s) > 0) then + begin + memoIgnore.Lines.Delete(i); + // Do NOT increment i, because the next item is now at the same index + end + else + begin + memoIgnore.Lines[i] := s; + Inc(i); // ONLY increment if a line was NOT deleted + end; + end; + + // The rest of your code remains the same + reg.WriteString('DiskSize', IntToStr(diskSize)); + reg.WriteString('DriveLetter', comboLetter.Text); + reg.WriteString('LoadContent', editFolder.Text); + reg.WriteString('ExcludeFolders', memoIgnore.Lines.Text); + reg.WriteBool('UseTempFolder', chkTemp.Checked); + reg.WriteBool('SyncContent', chkSync.Checked); + reg.WriteBool('DeleteOld', chkDelete.Checked); + Reg.CloseKey; + end; + Finally + reg.Free; + End; end; + procedure TfrmUI.UpdateLetters; var freeLetters: TAssignedDrives; diff --git a/RamSync.pas b/RamSync.pas index c996d93..744aaed 100644 --- a/RamSync.pas +++ b/RamSync.pas @@ -295,6 +295,9 @@ procedure TreeDelete(const src,dest:WideString;excluded:TTntStringList); WideFindClose(SR); end; +(* + +// old split path Procedure SplitPath(const path:WideString;var list:TStrArray); var oldPos,newPos,k:Integer; @@ -315,6 +318,83 @@ procedure TreeDelete(const src,dest:WideString;excluded:TTntStringList); SetLength(list,k); end; +*) + +// Make sure to place WidePosExChar above SplitPath or in an interface section +// New WidePosExChar by Gemini 2.5 Pro +function WidePosExChar(const SearchChar: WideChar; const S: WideString; StartPos: Integer = 1): Integer; +var + I: Integer; +begin + Result := 0; // Not found by default + for I := StartPos to Length(S) do + begin + if S[I] = SearchChar then + begin + Result := I; + Exit; + end; + end; +end; + +// Assuming TStrArray is defined as array of WideString, e.g.: +// type +// TStrArray = array of WideString; + +// new SplitPath by Gemini 2.5 Pro +Procedure SplitPath(const path:WideString;var list:TStrArray); +var + oldPos, newPos, k: Integer; +Begin + // Initialize list with a reasonable capacity. Length(path) is an overestimate, + // but ensures enough initial space. It will be trimmed later. + SetLength(List, Length(path) div 2 + 1); // More realistic initial size for paths + if Length(path) = 0 then // Handle empty path case + begin + SetLength(list, 0); + Exit; + end; + + k := 0; + oldPos := 1; + Repeat + // Use your WidePosExChar function here + newPos := WidePosExChar('\', path, oldPos); + + if newPos = 0 then // No more delimiters found, this is the last segment + Begin + list[k] := Copy(path, oldPos, MaxInt); // Copy to the end of the string + Inc(k); + Break; // Exit the loop as we've processed the last segment + End + Else // Delimiter found + Begin + // Copy the segment from oldPos up to (but not including) the delimiter + list[k] := Copy(path, oldPos, newPos - oldPos); + Inc(k); + // Move oldPos to the character *after* the delimiter + oldPos := newPos + 1; + End; + + // Check if oldPos has gone beyond the string length, + // which can happen if the path ends with a delimiter. + // If it does, and there's an empty segment at the end, add it. + if (oldPos > Length(path)) and (newPos <> 0) then // newPos=0 indicates last segment was handled + begin + // Add an empty string if the path ended with a backslash and we're at the end + if (path[Length(path)] = '\') then // Only if the original string ended with '\' + begin + list[k] := ''; + Inc(k); + end; + Break; + end; + + Until False; // Use Break to exit the loop once last segment is processed + + SetLength(list, k); // Trim the list to the actual number of segments +end; + Procedure SaveRamDisk(Var existing:TRamDisk); var list:TTntStringList; diff --git a/SrvMain.pas b/SrvMain.pas index 37f7ed9..8b598ff 100644 --- a/SrvMain.pas +++ b/SrvMain.pas @@ -1,155 +1,203 @@ -unit SrvMain; - -interface - -uses - Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs; - -type - TArsenalRamDisk = class(TService) - procedure ServiceAfterInstall(Sender: TService); - procedure ServiceExecute(Sender: TService); - procedure ServiceShutdown(Sender: TService); - procedure ServiceStart(Sender: TService; var Started: Boolean); - procedure ServiceStop(Sender: TService; var Stopped: Boolean); - private - { Private declarations } - public - function GetServiceController: TServiceController; override; - { Public declarations } - end; - -var - ArsenalRamDisk: TArsenalRamDisk; - -implementation - -{$R *.DFM} - -Uses TntRegistry,Definitions,RamCreate,RamRemove; - -Var - config:TRamDisk; - -procedure LoadSettings; -var - reg: TTntRegistry; -Begin - reg:=TTntRegistry.Create(KEY_READ); - Try - DebugLog('Reading settings from registry'); - reg.RootKey:=HKEY_LOCAL_MACHINE; - if Reg.OpenKey('SYSTEM\CurrentControlSet\Services\ArsenalRamDisk', False) then - begin - If Reg.ValueExists('DiskSize') then - Begin - config.size:=StrToInt64(reg.ReadString('DiskSize')); - DebugLog(Format('Reading DiskSize = %u',[config.size])); - end; - if reg.ValueExists('DriveLetter') Then - Begin - config.letter:=Char(reg.ReadString('DriveLetter')[1]); - DebugLog(Format('Reading DriveLetter = %s',[config.letter])); - end; - if reg.ValueExists('LoadContent') Then - Begin - config.persistentFolder:=reg.ReadString('LoadContent'); - DebugLog(WideFormat('Reading LoadContent = %s',[config.persistentFolder])); - end; - if reg.ValueExists('ExcludeFolders') Then - Begin - config.excludedList:=reg.ReadString('ExcludeFolders'); - DebugLog(WideFormat('Reading ExcludeFolders = %s',[config.excludedList])); - end; - if reg.ValueExists('UseTempFolder') Then - Begin - config.useTemp:=reg.ReadBool('UseTempFolder'); - DebugLog(Format('Reading UseTempFolder = %d',[Ord(config.useTemp)])); - end; - if reg.ValueExists('SyncContent') Then - Begin - config.synchronize:=reg.ReadBool('SyncContent'); - DebugLog(Format('Reading SyncContent = %d',[Ord(config.synchronize)])); - end; - if reg.ValueExists('DeleteOld') Then - Begin - config.deleteOld:=reg.ReadBool('DeleteOld'); - DebugLog(Format('Reading DeleteOld = %d',[Ord(config.deleteOld)])); - end; - Reg.CloseKey; - DebugLog('All settings from registry were loaded'); - end; - Finally - reg.Free; - End; -end; - -procedure ServiceController(CtrlCode: DWord); stdcall; -begin - ArsenalRamDisk.Controller(CtrlCode); -end; - -function TArsenalRamDisk.GetServiceController: TServiceController; -begin - Result := ServiceController; -end; - -procedure TArsenalRamDisk.ServiceAfterInstall(Sender: TService); -var - reg:TTntRegistry; -begin - Reg := TTntRegistry.Create(KEY_READ or KEY_WRITE); - try - Reg.RootKey := HKEY_LOCAL_MACHINE; - if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\' + Name, false) then - begin - Reg.WriteString('Description', DisplayName); - Reg.CloseKey; - end; - finally - Reg.Free; - end; - -end; - -procedure TArsenalRamDisk.ServiceExecute(Sender: TService); -begin - while not Terminated do ServiceThread.ProcessRequests(True); -end; - -procedure TArsenalRamDisk.ServiceShutdown(Sender: TService); -begin - DebugLog('RamDisk service initiated shutdown'); - DetachRamDisk(config); -end; - -procedure TArsenalRamDisk.ServiceStart(Sender: TService; var Started: Boolean); -begin - DebugLog('RamDisk service was started'); - LoadSettings; - if (config.size<>0) then - try - if CreateRamDisk(config,False) Then Started:=True; - except - On E:ERamDiskError do DebugLog(decodeException(E.ArsenalCode)); - On E:Exception do DebugLog(E.Message); - End; -end; - -procedure TArsenalRamDisk.ServiceStop(Sender: TService; var Stopped: Boolean); -begin - DebugLog('RamDisk service is being stopped'); - if config.letter <> #0 then - begin - DebugLog('Trying to unmount RamDisk'); - try - If DetachRamDisk(config) then Stopped:=True; - except - On E:ERamDiskError do DebugLog(decodeException(E.ArsenalCode)); - On E:Exception Do DebugLog(E.Message); - end; - End - Else Stopped:=True; -end; - -end. +unit SrvMain; + +interface + +uses + Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs; + +type + TArsenalRamDisk = class(TService) + procedure ServiceAfterInstall(Sender: TService); + procedure ServiceExecute(Sender: TService); + procedure ServiceShutdown(Sender: TService); + procedure ServiceStart(Sender: TService; var Started: Boolean); + procedure ServiceStop(Sender: TService; var Stopped: Boolean); + private + FShutdownThread: TThread; + public + function GetServiceController: TServiceController; override; + { Public declarations } + end; + +var + ArsenalRamDisk: TArsenalRamDisk; + +implementation + +{$R *.DFM} + +Uses TntRegistry,Definitions,RamCreate,RamRemove; + +Type + TShutdownThread = class(TThread) + protected + procedure Execute; override; + end; + +Var + config:TRamDisk; + +procedure TShutdownThread.Execute; +begin + inherited; + DetachRamDisk(config); +end; + +procedure LoadSettings; +var + reg: TTntRegistry; +Begin + reg:=TTntRegistry.Create(KEY_READ); + Try + DebugLog('Reading settings from registry'); + reg.RootKey:=HKEY_LOCAL_MACHINE; + if Reg.OpenKey('SYSTEM\CurrentControlSet\Services\ArsenalRamDisk', False) then + begin + If Reg.ValueExists('DiskSize') then + Begin + config.size:=StrToInt64(reg.ReadString('DiskSize')); + DebugLog(Format('Reading DiskSize = %u',[config.size])); + end; + if reg.ValueExists('DriveLetter') Then + Begin + config.letter:=Char(reg.ReadString('DriveLetter')[1]); + DebugLog(Format('Reading DriveLetter = %s',[config.letter])); + end; + if reg.ValueExists('LoadContent') Then + Begin + config.persistentFolder:=reg.ReadString('LoadContent'); + DebugLog(WideFormat('Reading LoadContent = %s',[config.persistentFolder])); + end; + if reg.ValueExists('ExcludeFolders') Then + Begin + config.excludedList:=reg.ReadString('ExcludeFolders'); + DebugLog(WideFormat('Reading ExcludeFolders = %s',[config.excludedList])); + end; + if reg.ValueExists('UseTempFolder') Then + Begin + config.useTemp:=reg.ReadBool('UseTempFolder'); + DebugLog(Format('Reading UseTempFolder = %d',[Ord(config.useTemp)])); + end; + if reg.ValueExists('SyncContent') Then + Begin + config.synchronize:=reg.ReadBool('SyncContent'); + DebugLog(Format('Reading SyncContent = %d',[Ord(config.synchronize)])); + end; + if reg.ValueExists('DeleteOld') Then + Begin + config.deleteOld:=reg.ReadBool('DeleteOld'); + DebugLog(Format('Reading DeleteOld = %d',[Ord(config.deleteOld)])); + end; + Reg.CloseKey; + DebugLog('All settings from registry were loaded'); + end; + Finally + reg.Free; + End; +end; + +procedure ServiceController(CtrlCode: DWord); stdcall; +begin + ArsenalRamDisk.Controller(CtrlCode); +end; + +function TArsenalRamDisk.GetServiceController: TServiceController; +begin + Result := ServiceController; +end; + +procedure TArsenalRamDisk.ServiceAfterInstall(Sender: TService); +var + reg:TTntRegistry; +begin + Reg := TTntRegistry.Create(KEY_READ or KEY_WRITE); + try + Reg.RootKey := HKEY_LOCAL_MACHINE; + if Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\' + Name, false) then + begin + Reg.WriteString('Description', DisplayName); + Reg.CloseKey; + end; + finally + Reg.Free; + end; + +end; + +procedure TArsenalRamDisk.ServiceExecute(Sender: TService); +begin + while not Terminated do + begin + ServiceThread.ProcessRequests(True); + end; + if FShutdownThread <> nil then + begin + FShutdownThread.WaitFor; + end; +end; + +procedure TArsenalRamDisk.ServiceShutdown(Sender: TService); +begin + DebugLog('RamDisk service initiated shutdown'); + if FShutdownThread = nil then + begin + FShutdownThread := TShutdownThread.Create(False); + FShutdownThread.FreeOnTerminate := True; + end; + inherited; +end; + +procedure TArsenalRamDisk.ServiceStart(Sender: TService; var Started: Boolean); +begin + FShutdownThread := nil; + DebugLog('RamDisk service was started'); + + // Report that we are starting, with a 60 second timeout + WaitHint := 60000; + CheckPoint := 1; + ReportStatus; + + LoadSettings; + + // Report progress + CheckPoint := 2; + ReportStatus; + + if (config.size<>0) then + try + if CreateRamDisk(config,False) Then Started:=True; + except + On E:ERamDiskError do DebugLog(decodeException(E.ArsenalCode)); + On E:Exception do DebugLog(E.Message); + End; + + // Reset WaitHint + WaitHint := 0; +end; + +procedure TArsenalRamDisk.ServiceStop(Sender: TService; var Stopped: Boolean); +begin + DebugLog('RamDisk service is being stopped'); + // Report that we are stopping, with a 60 second timeout + WaitHint := 60000; + CheckPoint := 1; + ReportStatus; + try + if config.letter <> #0 then + begin + DebugLog('Trying to unmount RamDisk'); + if FShutdownThread = nil then + begin + FShutdownThread := TShutdownThread.Create(False); + FShutdownThread.FreeOnTerminate := True; + end; + end + Else Stopped:=True; + finally + inherited; + end; + // Reset WaitHint + WaitHint := 0; +end; + +end.