diff options
Diffstat (limited to 'UCoreWorkers.pas')
| -rw-r--r-- | UCoreWorkers.pas | 2180 |
1 files changed, 2180 insertions, 0 deletions
diff --git a/UCoreWorkers.pas b/UCoreWorkers.pas new file mode 100644 index 0000000..bcdd07f --- /dev/null +++ b/UCoreWorkers.pas @@ -0,0 +1,2180 @@ +(* + Tux Commander - UCoreWorkers - worker threads, operations + Copyright (C) 2009 Tomas Bzatek <tbzatek@users.sourceforge.net> + Check for updates on tuxcmd.sourceforge.net + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*) +unit UCoreWorkers; +interface + +uses glib2, gtk2, SyncObjs, Classes, GTKForms, GTKView, ULibc, UEngines, UCoreUtils, UProgress, UVFSCore, uVFSprototypes, UCore; + +type TVFSCallbackThread = class(TThread) + private + FThreadID: __pthread_t; + FCopyProgressFunc: TEngineProgressFunc; + procedure PrepareExecute; // Call this right after thread has been started + public + AEngine: TPanelEngine; + APlugin: TVFSPlugin; + + VFSCallbackEvent: TSimpleEvent; + VFSAskQuestion_Message: PChar; + VFSAskQuestion_Choices: PPChar; + VFSAskQuestion_Choice: PInteger; + VFSAskQuestion_Display: boolean; + + VFSAskPassword_Message: PChar; + VFSAskPassword_default_user: PChar; + VFSAskPassword_default_domain: PChar; + VFSAskPassword_default_password: PChar; + VFSAskPassword_flags: TVFSAskPasswordFlags; + VFSAskPassword_username: PPChar; + VFSAskPassword_password: PPChar; + VFSAskPassword_anonymous: Pgboolean; + VFSAskPassword_domain: PPChar; + VFSAskPassword_password_save: PVFSPasswordSave; + VFSAskPassword_Display: boolean; + VFSAskPassword_Result: boolean; + + VFSCallbackCancelled: boolean; + + VFSConnectionManagerMode: boolean; + VFSQuickConnectMode: boolean; + VFSDialogsParentWindow: PGtkWidget; + + FCancelRequested: boolean; + + constructor Create(CreateSuspended: boolean); + destructor Destroy; override; + end; + + TWorkerThread = class(TVFSCallbackThread) + private + GUIMutex: TCriticalSection; + protected + procedure Execute; override; + procedure CommitGUIUpdate; + public + FCancelled: boolean; + + // Data to update + FProgress1Pos, FProgress2Pos, FProgress1Max, FProgress2Max: Int64; + FProgress1Text, FProgress2Text, FLabel1Text, FLabel2Text: string; + FGUIProgress1Pos, FGUIProgress2Pos, FGUIProgress1Max, FGUIProgress2Max: Int64; + FGUIProgress1Text, FGUIProgress2Text, FGUILabel1Text, FGUILabel2Text: string; + FGUIChanged: boolean; + + FCancelMessage: string; + FDoneThread, FShowCancelMessage, + FDialogShowDirDelete, FDialogShowOverwrite, FDialogShowNewDir, FDialogShowMsgBox: boolean; + FDialogResultDirDelete, FDialogResultOverwrite, FDialogResultNewDir: integer; + + FDirDeleteButtonsType: integer; + FDirDeleteLabel1Text, FDirDeleteLabel2Text, FDirDeleteLabel3Text, FDirDeleteCaption: string; + FDirDeleteLabel2Visible, FDirDeleteLabel3Visible: boolean; + + FOverwriteButtonsType: integer; + FOverwriteFromLabel, FOverwriteFromInfoLabel, FOverwriteToLabel, FOverwriteToInfoLabel, + FOverwriteRenameStr, FOverwriteSourceFile, FOverwriteDestFile: string; + + FNewDirCaption, FNewDirLabel, FNewDirEdit: string; + FMsgBoxText: string; + FMsgBoxButtons: TMessageButtons; + FMsgBoxStyle: TMessageStyle; + FMsgBoxDefault, FMsgBoxEscape, FDialogResultMsgBox: TMessageButton; + + FCallbackLockEvent: TSimpleEvent; + + // Parameters + ProgressForm: TFProgress; + Engine, SrcEngine, DestEngine: TPanelEngine; + LeftPanel: boolean; + DataList: TList; + ParamBool1, ParamBool2, ParamBool3, ParamBool4, ParamBool5: boolean; + ParamString1, ParamString2, ParamString3: string; + ParamPointer1: Pointer; + ParamInt64: Int64; + ParamInt1, ParamInt2: integer; + ParamLongWord1: LongWord; + ParamCardinal1, ParamCardinal2: Cardinal; + ParamFloat1, ParamFloat2: Extended; + ParamDataItem1: PDataItem; + WorkerProcedure: procedure(SenderThread: TWorkerThread); + SelectedItem: PDataItem; + ExtractFromVFSMode, ExtractFromVFSAll: boolean; + ErrorHappened: boolean; + + constructor Create; + destructor Destroy; override; + procedure CancelIt; + function Cancelled: boolean; + + procedure PrepareJobFilesFromPanel(AList: TList; DoNotRecurse: boolean); + + procedure UpdateProgress1(const Progress: Int64; const ProgressText: string); + procedure UpdateProgress2(const Progress: Int64; const ProgressText: string); + procedure SetProgress1Params(const ProgressMax: Int64); + procedure SetProgress2Params(const ProgressMax: Int64); + procedure UpdateCaption1(const CaptionText: string); + procedure UpdateCaption2(const CaptionText: string); + + function ShowDirDeleteDialog(ButtonsType: integer; const Label1Text: string; const Label2Text: string = ''; + const Label3Text: string = ''; const DirDeleteCaption: string = ''): integer; + function ShowOverwriteDialog(ButtonsType: integer; const FromLabel, FromInfoLabel, ToLabel, ToInfoLabel, RenameStr, + SourceFile, DestFile: string): integer; + function ShowNewDirDialog(Caption, LabelCaption, Edit: string): integer; + function ShowMessageBox(const Text: string; Buttons: TMessageButtons; Style: TMessageStyle; + Default, Escape: TMessageButton): TMessageButton; + end; + + TOpenDirThread = class(TVFSCallbackThread) + private + function ChangeDir(Engine: TPanelEngine; Path: string; var SelItem: string; const AutoFallBack: boolean): integer; + protected + procedure Execute; override; + public + APath: string; + ASelItem: string; + AAutoFallBack: boolean; + ADirList: TList; + ChDirResult, ListingResult, VFSOpenResult: integer; + Finished, CancelIt: boolean; + RunningTime: Int64; + AFullPath, AHighlightItem: string; + constructor Create; + destructor Destroy; override; + end; + + TOpenConnectionThread = class(TVFSCallbackThread) + private + protected + procedure Execute; override; + public + URI: string; + Finished: boolean; + OpenResult: boolean; + constructor Create; + destructor Destroy; override; + end; + + +// Thread aware functions (also half-thread-safe) without any piece of GTK code +procedure DeleteFilesWorker(SenderThread: TWorkerThread); +procedure CopyFilesWorker(SenderThread: TWorkerThread); +procedure MergeFilesWorker(SenderThread: TWorkerThread); +procedure SplitFilesWorker(SenderThread: TWorkerThread); +procedure ChmodFilesWorker(SenderThread: TWorkerThread); +procedure ChownFilesWorker(SenderThread: TWorkerThread); +procedure DummyThreadWorker(SenderThread: TWorkerThread); + +// Worker threads utilities +procedure ProcessProgressThread(SenderThread: TWorkerThread; ProgressForm: TFProgress); + + +implementation + +uses SysUtils, DateUtils, StrUtils, UConfig, UDirDelete, UOverwrite, ULocale, + UFileAssoc, UCoreClasses, URemoteWait, UMain, UGnome, UNewDir, + crc; + + +(********************************************************************************************************************************) +constructor TVFSCallbackThread.Create(CreateSuspended: boolean); +begin + inherited Create(CreateSuspended); + APlugin := nil; + VFSCallbackEvent := TSimpleEvent.Create; + VFSAskQuestion_Display := False; + VFSAskPassword_Display := False; + VFSCallbackCancelled := False; + VFSConnectionManagerMode := False; + VFSQuickConnectMode := False; + VFSDialogsParentWindow := FMain.FWidget; + FCancelRequested := False; +end; + +destructor TVFSCallbackThread.Destroy; +begin + VFSCallbackEvent.Free; + inherited Destroy; +end; + +procedure TVFSCallbackThread.PrepareExecute; +begin + FThreadID := pthread_self; + VFSCallbackCancelled := False; +end; + +(********************************************************************************************************************************) +procedure vfs_ask_question_callback(const AMessage: PChar; const Choices: PPChar; choice: PInteger; cancel_choice: Integer; user_data: Pointer); cdecl; +var Thread: TVFSCallbackThread; +begin + Thread := user_data; + if (Thread = nil) { or (not (Thread is TVFSCallbackThread))} then begin + DebugMsg(['(ERROR): vfs_ask_question_callback: user_data is not TVFSCallbackThread, exiting.']); + Exit; + end; + if Thread.FCancelRequested then begin + DebugMsg(['!! (WARNING): vfs_ask_question_callback: FCancelRequested.']); + if (choice <> nil) then choice^ := -1; + Thread.VFSCallbackCancelled := True; + Exit; + end; + if pthread_self = Application.ThreadID then begin + DebugMsg(['!! (WARNING): vfs_ask_question_callback called from the main thread, expected spawn from a TVFSCallbackThread']); + HandleVFSAskQuestionCallback(Thread.VFSDialogsParentWindow, AMessage, Choices, choice); + if (choice <> nil) then Thread.VFSCallbackCancelled := (choice^ < 0) or (choice^ = cancel_choice); + Exit; + end; + if pthread_self = Thread.FThreadID then begin + DebugMsg(['******* vfs_ask_question_callback spawned, user_data = 0x', IntToHex(QWord(user_data), 16), ', ThreadID = 0x', IntToHex(pthread_self, 16)]); + Thread.VFSAskQuestion_Message := AMessage; + Thread.VFSAskQuestion_Choices := Choices; + Thread.VFSAskQuestion_Choice := choice; + Thread.VFSAskQuestion_Display := True; + Thread.VFSCallbackEvent.ResetEvent; + Thread.VFSCallbackEvent.WaitFor(INFINITE); + DebugMsg(['******* thread: resuming...']); + if (choice <> nil) then Thread.VFSCallbackCancelled := (choice^ < 0) or (choice^ = cancel_choice); + Exit; + end; + DebugMsg(['!! (ERROR): vfs_ask_question_callback spawned neither from the main thread nor from active TVFSCallbackThread, dropping the callback to prevent data corruption.']); + DebugMsg([' ThreadID = 0x', IntToHex(pthread_self, 16), ', TVFSCallbackThread ID = 0x', IntToHex(Thread.FThreadID, 16), ', Application.ThreadID = 0x', IntToHex(Application.ThreadID, 16)]); +end; + +function vfs_ask_password_callback(const AMessage: PChar; const default_user: PChar; const default_domain: PChar; const default_password: PChar; flags: TVFSAskPasswordFlags; + username, password: PPChar; anonymous: Pgboolean; domain: PPChar; password_save: PVFSPasswordSave; + user_data: Pointer): gboolean; cdecl; +var Thread: TVFSCallbackThread; + def_pass: PChar; +begin + Result := False; + Thread := user_data; + if (Thread = nil) { or (not (Thread is TVFSCallbackThread))} then begin + DebugMsg(['(ERROR): vfs_ask_question_callback: user_data is not TVFSCallbackThread, exiting.']); + Exit; + end; + if Thread.FCancelRequested then begin + DebugMsg(['!! (WARNING): vfs_ask_password_callback: FCancelRequested.']); + Result := False; + Thread.VFSCallbackCancelled := True; + Exit; + end; + + def_pass := default_password; + + // Disable password saving if requested + if ConfConnMgrDoNotSynchronizeKeyring then begin + flags := flags and (not VFS_ASK_PASSWORD_SAVING_SUPPORTED); + if password_save <> nil then password_save^ := VFS_PASSWORD_SAVE_NEVER; + end; + if ConfConnMgrDoNotSavePasswords then flags := flags and (not VFS_ASK_PASSWORD_SAVE_INTERNAL) else + if Thread.VFSConnectionManagerMode then flags := flags or VFS_ASK_PASSWORD_SAVE_INTERNAL; + + // Use stored password, if previously set + if (((flags and VFS_ASK_PASSWORD_ARCHIVE_MODE) = VFS_ASK_PASSWORD_ARCHIVE_MODE) or Thread.VFSConnectionManagerMode or Thread.VFSQuickConnectMode) and + (password <> nil) and (Thread.AEngine is TVFSEngine) and (Length((Thread.AEngine as TVFSEngine).Password) > 0) then + begin + if not (Thread.AEngine as TVFSEngine).PasswordUsed then begin + DebugMsg([' (II) vfs_ask_password_callback: reusing manually set password']); + password^ := g_strdup(PChar((Thread.AEngine as TVFSEngine).Password)); + (Thread.AEngine as TVFSEngine).PasswordUsed := True; + if (password_save <> nil) and Thread.VFSConnectionManagerMode then + if ConfConnMgrDoNotSynchronizeKeyring then password_save^ := VFS_PASSWORD_SAVE_NEVER + else password_save^ := VFS_PASSWORD_SAVE_PERMANENTLY; + Thread.VFSCallbackCancelled := False; + Result := True; + Exit; + end else + if (flags and VFS_ASK_PASSWORD_ARCHIVE_MODE) = VFS_ASK_PASSWORD_ARCHIVE_MODE then + def_pass := PChar((Thread.AEngine as TVFSEngine).Password); + end; + + // Ask for password + if pthread_self = Application.ThreadID then begin + DebugMsg(['!! (WARNING): vfs_ask_password_callback called from the main thread, expected spawn from a TVFSCallbackThread']); + Result := HandleVFSAskPasswordCallback(Thread.VFSDialogsParentWindow, AMessage, default_user, default_domain, def_pass, flags, username, password, anonymous, domain, password_save); + Thread.VFSCallbackCancelled := Result = False; + end else + if pthread_self = Thread.FThreadID then begin + DebugMsg(['******* vfs_ask_password_callback spawned, user_data = 0x', IntToHex(QWord(user_data), 16), ', ThreadID = 0x', IntToHex(pthread_self, 16), ', Application.ThreadID = 0x', IntToHex(Application.ThreadID, 16)]); + Thread.VFSAskPassword_Message := AMessage; + Thread.VFSAskPassword_default_user := default_user; + Thread.VFSAskPassword_default_domain := default_domain; + Thread.VFSAskPassword_default_password := def_pass; + Thread.VFSAskPassword_flags := flags; + Thread.VFSAskPassword_username := username; + Thread.VFSAskPassword_password := password; + Thread.VFSAskPassword_anonymous := anonymous; + Thread.VFSAskPassword_domain := domain; + Thread.VFSAskPassword_password_save := password_save; + Thread.VFSAskPassword_Display := True; + Thread.VFSAskPassword_Result := False; + Thread.VFSCallbackEvent.ResetEvent; + Thread.VFSCallbackEvent.WaitFor(INFINITE); + DebugMsg(['******* thread: resuming...']); + Result := Thread.VFSAskPassword_Result; + Thread.VFSCallbackCancelled := Result = False; + end else + begin + DebugMsg(['!! (ERROR): vfs_ask_password_callback spawned neither from the main thread nor from active TVFSCallbackThread, dropping the callback to prevent data corruption.']); + DebugMsg([' ThreadID = 0x', IntToHex(pthread_self, 16), ', TVFSCallbackThread ID = 0x', IntToHex(Thread.FThreadID, 16), ', Application.ThreadID = 0x', IntToHex(Application.ThreadID, 16)]); + end; + + // Save password back to the engine + if Result and (password <> nil) and (strlen(password^) > 0) and (Thread.AEngine is TVFSEngine) and + (((flags and VFS_ASK_PASSWORD_ARCHIVE_MODE) = VFS_ASK_PASSWORD_ARCHIVE_MODE) or + (Thread.VFSConnectionManagerMode and (password_save <> nil) and (password_save^ = VFS_PASSWORD_SAVE_PERMANENTLY))) then + begin + (Thread.AEngine as TVFSEngine).Password := string(password^); + (Thread.AEngine as TVFSEngine).PasswordUsed := True; + end; + + // Strip password saving if requested + if ConfConnMgrDoNotSynchronizeKeyring and (password_save <> nil) then + password_save^ := VFS_PASSWORD_SAVE_NEVER; +end; + +function vfs_progress_callback(position, max: guint64; user_data: Pointer): gboolean; cdecl; +begin +// DebugMsg(['VFSCopyCallBackFunc called (iPos = ', iPos, ', iMax = ', iMax, ')']); + Result := True; + if not Assigned(user_data) then Exit; + if Assigned(TVFSCallbackThread(user_data).FCopyProgressFunc) then + try + Result := TVFSCallbackThread(user_data).FCopyProgressFunc(user_data, position); + except + on E: Exception do DebugMsg(['*** Exception raised in vfs_progress_callback(position=', position, ', max=', max, ', user_data=', user_data, '): (', E.ClassName, '): ', E.Message]); + end; +end; + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +procedure TWorkerThread.Execute; +begin + PrepareExecute; + if Assigned(WorkerProcedure) then WorkerProcedure(Self); +end; + +constructor TWorkerThread.Create; +begin + inherited Create(True); + FreeOnTerminate := False; + GUIMutex := TCriticalSection.Create; + FCallbackLockEvent := TSimpleEvent.Create; + FCancelled := False; + ProgressForm := nil; + Engine := nil; + DataList := nil; + ParamPointer1 := nil; + WorkerProcedure := nil; + SelectedItem := nil; + FDoneThread := False; + FShowCancelMessage := False; + FDialogShowDirDelete := False; + FDialogShowOverwrite := False; + FDialogShowNewDir := False; + FDialogShowMsgBox := False; + ExtractFromVFSMode := False; + ErrorHappened := False; + ParamBool1 := False; + ParamBool2 := False; + ParamBool3 := False; + ParamBool4 := False; + ParamBool5 := False; + FGUIChanged := False; +end; + +destructor TWorkerThread.Destroy; +begin + GUIMutex.Free; + FCallbackLockEvent.Free; + inherited Destroy; +end; + +procedure TWorkerThread.CancelIt; +begin + FCancelled := True; +end; + +function TWorkerThread.Cancelled: boolean; +begin + Result := FCancelled or ProgressForm.Cancelled; +end; + +procedure TWorkerThread.UpdateProgress1(const Progress: Int64; const ProgressText: string); +begin +// DebugMsg([' ** TWorkerThread.UpdateProgress1(Progress = ', Progress, ', ProgressText = ', ProgressText]); + FProgress1Pos := Progress; + FProgress1Text := ProgressText; +end; + +procedure TWorkerThread.UpdateProgress2(const Progress: Int64; const ProgressText: string); +begin +// DebugMsg([' ** TWorkerThread.UpdateProgress2(Progress = ', Progress, ', ProgressText = ', ProgressText]); + FProgress2Pos := Progress; + FProgress2Text := ProgressText; +end; + +procedure TWorkerThread.SetProgress1Params(const ProgressMax: Int64); +begin + FProgress1Max := ProgressMax; +end; + +procedure TWorkerThread.SetProgress2Params(const ProgressMax: Int64); +begin + FProgress2Max := ProgressMax; +end; + +procedure TWorkerThread.UpdateCaption1(const CaptionText: string); +begin + FLabel1Text := CaptionText; +end; + +procedure TWorkerThread.UpdateCaption2(const CaptionText: string); +begin + FLabel2Text := CaptionText; +end; + +procedure TWorkerThread.CommitGUIUpdate; +begin + GUIMutex.Acquire; +// WriteLn('TWorkerThread.CommitGUIUpdate, ted mam lock ja! -- enter'); + FGUIProgress1Pos := FProgress1Pos; + FGUIProgress2Pos := FProgress2Pos; + FGUIProgress1Max := FProgress1Max; + FGUIProgress2Max := FProgress2Max; + FGUIProgress1Text := FProgress1Text; + FGUIProgress2Text := FProgress2Text; + FGUILabel1Text := FLabel1Text; + FGUILabel2Text := FLabel2Text; + FGUIChanged := True; +// Sleep(1000); +// WriteLn('TWorkerThread.CommitGUIUpdate, ted mam lock ja! -- leave'); + GUIMutex.Release; +end; + +function TWorkerThread.ShowDirDeleteDialog(ButtonsType: integer; const Label1Text: string; const Label2Text: string = ''; const Label3Text: string = ''; const DirDeleteCaption: string = ''): integer; +begin + FDialogResultDirDelete := integer(mbCancel); + FDirDeleteLabel1Text := Label1Text; + FDirDeleteLabel2Text := Label2Text; + FDirDeleteLabel3Text := Label3Text; + FDirDeleteLabel2Visible := Label2Text <> ''; + FDirDeleteLabel3Visible := Label3Text <> ''; + FDirDeleteButtonsType := ButtonsType; + if DirDeleteCaption = '' then FDirDeleteCaption := LANGRemoveDirectory + else FDirDeleteCaption := DirDeleteCaption; + FDialogShowDirDelete := True; + FCallbackLockEvent.ResetEvent; + FCallbackLockEvent.WaitFor(INFINITE); + Result := FDialogResultDirDelete; +end; + +function TWorkerThread.ShowOverwriteDialog(ButtonsType: integer; const FromLabel, FromInfoLabel, ToLabel, ToInfoLabel, RenameStr, SourceFile, DestFile: string): integer; +begin + FDialogResultOverwrite := integer(mbCancel); + FOverwriteButtonsType := ButtonsType; + FOverwriteFromLabel := FromLabel; + FOverwriteFromInfoLabel := FromInfoLabel; + FOverwriteToLabel := ToLabel; + FOverwriteToInfoLabel := ToInfoLabel; + FOverwriteRenameStr := RenameStr; + FOverwriteSourceFile := SourceFile; + FOverwriteDestFile := DestFile; + FDialogShowOverwrite := True; + FCallbackLockEvent.ResetEvent; + FCallbackLockEvent.WaitFor(INFINITE); + Result := FDialogResultOverwrite; +end; + +function TWorkerThread.ShowNewDirDialog(Caption, LabelCaption, Edit: string): integer; +begin + FNewDirCaption := Caption; + FNewDirLabel := LabelCaption; + FNewDirEdit := Edit; + FDialogShowNewDir := True; + FCallbackLockEvent.ResetEvent; + FCallbackLockEvent.WaitFor(INFINITE); + Result := FDialogResultNewDir; +end; + +function TWorkerThread.ShowMessageBox(const Text: string; Buttons: TMessageButtons; Style: TMessageStyle; Default, Escape: TMessageButton): TMessageButton; +begin + FMsgBoxText := Text; + FMsgBoxButtons := Buttons; + FMsgBoxStyle := Style; + FMsgBoxDefault := Default; + FMsgBoxEscape := Escape; + FDialogShowMsgBox := True; + FCallbackLockEvent.ResetEvent; + FCallbackLockEvent.WaitFor(INFINITE); + Result := FDialogResultMsgBox; +end; + + +(********************************************************************************************************************************) +procedure TWorkerThread.PrepareJobFilesFromPanel(AList: TList; DoNotRecurse: boolean); +var i: longint; + CurrPath: string; + x: PDataItemSL; +begin + CurrPath := IncludeTrailingPathDelimiter(Engine.Path); + if DataList.Count > 0 then + for i := 0 to DataList.Count - 1 do + with PDataItem(DataList[i])^ do + if (not UpDir) and Selected then + if IsDir and (not IsLnk) and (not DoNotRecurse) + then FillDirFiles(Engine, AList, CurrPath + String(FName)) + else begin + x := GetFileInfoSL(Engine, CurrPath + String(FName)); + if x <> nil then AList.Add(x); + end; + if (AList.Count = 0) and Assigned(SelectedItem) and (not SelectedItem^.UpDir) then + with SelectedItem^ do + if IsDir and (not IsLnk) and (not DoNotRecurse) + then FillDirFiles(Engine, AList, CurrPath + String(FName)) + else begin + x := GetFileInfoSL(Engine, CurrPath + String(FName)); + if x <> nil then AList.Add(x); + end; +end; + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +procedure ProcessProgressThread(SenderThread: TWorkerThread; ProgressForm: TFProgress); +var AFDirDelete: TFDirDelete; + AFOverwrite: TFOverwrite; + AFNewDir: TFNewDir; + b: boolean; +begin + DebugMsg([' ** ProcessProgressThread --begin']); + b := False; + try + while not SenderThread.FDoneThread do begin +// Write('.'); + Sleep(ConstInternalProgressTimer); +// DebugMsg([' ** ProcessProgressThread: updating UI (FProgress1Pos = ', SenderThread.FProgress1Pos, ', FProgress2Pos = ', SenderThread.FProgress2Pos]); + + +// DebugMsg(['ProcessProgressThread - before mutex']); + SenderThread.GUIMutex.Acquire; +// WriteLn('ProcessProgressThread - ted mam lock ja! -- enter'); + + try + if SenderThread.FGUIChanged then begin + if SenderThread.FGUIProgress1Max > 1 + then ProgressForm.ProgressBar.Fraction := SenderThread.FGUIProgress1Pos / SenderThread.FGUIProgress1Max + else ProgressForm.ProgressBar.Fraction := 0; +// ProgressForm.ProgressBar.Value := SenderThread.FGUIProgress1Pos; + ProgressForm.ProgressBar.Text := SenderThread.FGUIProgress1Text; + ProgressForm.Label2.Caption := SenderThread.FGUILabel1Text; + if ProgressForm.FTwoBars then begin + if SenderThread.FGUIProgress2Max > 1 + then ProgressForm.ProgressBar2.Fraction := SenderThread.FGUIProgress2Pos / SenderThread.FGUIProgress2Max + else ProgressForm.ProgressBar2.Fraction := 0; +// ProgressForm.ProgressBar2.Value := SenderThread.FGUIProgress2Pos; + ProgressForm.ProgressBar2.Text := SenderThread.FGUIProgress2Text; + ProgressForm.Label3.Caption := SenderThread.FGUILabel2Text; + end; + ProgressForm.ProgressBar.Max := SenderThread.FGUIProgress1Max; + ProgressForm.ProgressBar2.Max := SenderThread.FGUIProgress2Max; + SenderThread.FGUIChanged := False; + end; + except + on E: Exception do DebugMsg(['*** Exception raised in UCore.ProcessProgressThread::updating progress bars block (', E.ClassName, '): ', E.Message]); + end; + + +// Sleep(1000); +// WriteLn('ProcessProgressThread - ted mam lock ja! -- leave'); + SenderThread.GUIMutex.Release; + + +// DebugMsg(['Before refresh']); + Application.ProcessMessages; +// DebugMsg(['After refresh']); + + + // VFS callbacks + if SenderThread.VFSAskQuestion_Display then begin + SenderThread.VFSAskQuestion_Display := False; + DebugMsg(['ProcessProgressThread - Main thread: displaying question dialog']); + HandleVFSAskQuestionCallback(ProgressForm.FWidget, SenderThread.VFSAskQuestion_Message, SenderThread.VFSAskQuestion_Choices, SenderThread.VFSAskQuestion_Choice); + SenderThread.VFSCallbackEvent.SetEvent; + end; + if SenderThread.VFSAskPassword_Display then begin + SenderThread.VFSAskPassword_Display := False; + DebugMsg(['ProcessProgressThread - Main thread: displaying password prompt']); + SenderThread.VFSAskPassword_Result := HandleVFSAskPasswordCallback(ProgressForm.FWidget, + SenderThread.VFSAskPassword_Message, + SenderThread.VFSAskPassword_default_user, + SenderThread.VFSAskPassword_default_domain, + SenderThread.VFSAskPassword_default_password, + SenderThread.VFSAskPassword_flags, + SenderThread.VFSAskPassword_username, + SenderThread.VFSAskPassword_password, + SenderThread.VFSAskPassword_anonymous, + SenderThread.VFSAskPassword_domain, + SenderThread.VFSAskPassword_password_save); + SenderThread.VFSCallbackEvent.SetEvent; + end; + + + try + if SenderThread.FDialogShowDirDelete then begin + AFDirDelete := nil; + try + AFDirDelete := TFDirDelete.Create(SenderThread.ProgressForm as TComponent); + AFDirDelete.Caption := SenderThread.FDirDeleteCaption; + AFDirDelete.AddButtons(SenderThread.FDirDeleteButtonsType); + AFDirDelete.Label1.Caption := SenderThread.FDirDeleteLabel1Text; + AFDirDelete.Label2.Caption := SenderThread.FDirDeleteLabel2Text; + AFDirDelete.Label3.Caption := SenderThread.FDirDeleteLabel3Text; + AFDirDelete.Label2.Visible := SenderThread.FDirDeleteLabel2Visible; + AFDirDelete.Label3.Visible := SenderThread.FDirDeleteLabel3Visible; + SenderThread.FDialogResultDirDelete := Integer(AFDirDelete.Run); + if (SenderThread.FDirDeleteButtonsType = 3) and (SenderThread.FDialogResultDirDelete = 2) and (not SenderThread.ParamBool3) + then case Application.MessageBox(LANGIgnoreError, [mbYes, mbNo{, mbCancel}], mbWarning, mbYes, mbNo) of + mbNo: SenderThread.FDialogResultDirDelete := 1; + mbCancel: SenderThread.FDialogResultDirDelete := 0; + end; + finally + AFDirDelete.Free; + end; + SenderThread.FDialogShowDirDelete := False; + b := True; + end; + + if SenderThread.FDialogShowOverwrite then begin + AFOverwrite := nil; + try + AFOverwrite := TFOverwrite.Create(SenderThread.ProgressForm as TComponent); + AFOverwrite.AddButtons(SenderThread.FOverwriteButtonsType); + AFOverwrite.FromLabel.Caption := SenderThread.FOverwriteFromLabel; + AFOverwrite.FromInfoLabel.Caption := SenderThread.FOverwriteFromInfoLabel; + AFOverwrite.ToLabel.Caption := SenderThread.FOverwriteToLabel; + AFOverwrite.ToInfoLabel.Caption := SenderThread.FOverwriteToInfoLabel; + AFOverwrite.RenameStr := SenderThread.FOverwriteRenameStr; + AFOverwrite.SourceFile := SenderThread.FOverwriteSourceFile; + AFOverwrite.DestFile := SenderThread.FOverwriteDestFile; + SenderThread.FDialogResultOverwrite := Integer(AFOverwrite.Run); + SenderThread.FOverwriteRenameStr := UTF8ToStr(AFOverwrite.RenameStr); + finally + AFOverwrite.Free; + end; + SenderThread.FDialogShowOverwrite := False; + b := True; + end; + + if SenderThread.FDialogShowNewDir then begin + AFNewDir := nil; + try + AFNewDir := TFNewDir.Create(SenderThread.ProgressForm as TComponent); + AFNewDir.Caption := SenderThread.FNewDirCaption; + AFNewDir.Label1.Caption := SenderThread.FNewDirLabel; + AFNewDir.Entry.Text := SenderThread.FNewDirEdit; + AFNewDir.Entry.SelectAll; + SenderThread.FDialogResultNewDir := Integer(AFNewDir.Run); + SenderThread.FNewDirEdit := AFNewDir.Entry.Text; + finally + AFNewDir.Free; + end; + SenderThread.FDialogShowNewDir := False; + b := True; + end; + + if SenderThread.FDialogShowMsgBox then begin + SenderThread.FDialogResultMsgBox := Application.MessageBox(SenderThread.FMsgBoxText, SenderThread.FMsgBoxButtons, + SenderThread.FMsgBoxStyle, SenderThread.FMsgBoxDefault, + SenderThread.FMsgBoxEscape); + SenderThread.FDialogShowMsgBox := False; + b := True; + end; + finally + // Unlock the waiting worker thread + if b then begin + b := False; + SenderThread.FCallbackLockEvent.SetEvent; + end; + end; + end; + if SenderThread.FShowCancelMessage then + if SenderThread.FCancelMessage = LANGUserCancelled + then Application.MessageBox(SenderThread.FCancelMessage, [mbOK], mbWarning, mbNone, mbOK) + else Application.MessageBox(SenderThread.FCancelMessage, [mbOK], mbError, mbNone, mbOK); + except + on E: Exception do DebugMsg(['*** Exception raised in UCore.ProcessProgressThread (', E.ClassName, '): ', E.Message]); + end; + DebugMsg([' ** ProcessProgressThread --end']); +end; + + + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +procedure DeleteFilesWorker(SenderThread: TWorkerThread); +var SkipAll: boolean; + + function HandleDelete(AFileRec: PDataItemSL): boolean; + var Res, Response: integer; + begin + Result := True; +// DebugMsg(['Debug: IsDir: ', AFileRec^.IsDir, ', Stage1: ', AFileRec^.Stage1, ', IsLnk: ', AFileRec^.IsLnk, '; Result = ', AFileRec^.IsDir and AFileRec^.Stage1 and (not AFileRec^.IsLnk)]); + if AFileRec^.DataItem^.IsDir and AFileRec^.Stage1 and (not AFileRec^.DataItem^.IsLnk) then Exit; + Res := SenderThread.Engine.Remove(String(AFileRec^.DataItem^.FName)); +// DebugMsg(['Result : ', Res]); + if Res <> 0 then + if SkipAll then Result := True else + begin + Response := SenderThread.ShowDirDeleteDialog(1, LANGTheFileDirectory, String(AFileRec^.DataItem^.FDisplayName), + Format(LANGCouldNotBeDeletedS, [GetErrorString(Res)])); + case Response of + 1 : Result := True; + 3 : begin + SkipAll := True; + Result := True; + end; + 2 : Result := HandleDelete(AFileRec); + else Result := False; + end; + end; + end; + +var i: longint; + AList: TList; + CurrPath: string; + Fr: Single; + Response: integer; + DeleteAll, SkipToNext: boolean; +begin + SkipAll := False; + AList := TList.Create; + AList.Clear; + with SenderThread do begin + CurrPath := IncludeTrailingPathDelimiter(Engine.Path); + PrepareJobFilesFromPanel(AList, False); + if Engine.ChangeDir(CurrPath) <> 0 then DebugMsg(['*** WARNING: Cannot change to the origin location, strange behaviour might occur.']); + libc_chdir('/'); + + SetProgress1Params(AList.Count); + CommitGUIUpdate; + DeleteAll := False; + SkipToNext := False; + +// DebugWriteListSL(AList); + + if AList.Count = 1 then Fr := 1 else Fr := 100 / (AList.Count - 1); + if AList.Count > 0 then + for i := 0 to AList.Count - 1 do begin + if Cancelled then begin + FCancelMessage := LANGUserCancelled; + FShowCancelMessage := True; + Break; + end; + if SkipToNext and (PDataItemSL(AList[i])^.Level > 1) then Continue; + if SkipToNext and (PDataItemSL(AList[i])^.Level = 1) and (not PDataItemSL(AList[i])^.Stage1) then begin + SkipToNext := False; + Continue; + end; + // Check for non-empty directory + if (not DeleteAll) and (PDataItemSL(AList[i])^.Level = 1) and PDataItemSL(AList[i])^.Stage1 and PDataItemSL(AList[i])^.DataItem^.IsDir and + (not PDataItemSL(AList[i])^.DataItem^.IsLnk) and (i < AList.Count - 2) and (PDataItemSL(AList[i + 1])^.Level = 2) then + begin + Response := ShowDirDeleteDialog(4, Format(LANGTheDirectorySIsNotEmpty, [string(PDataItemSL(AList[i])^.DataItem^.FDisplayName)]), + LANGDoYouWantToDeleteItWithAllItsFilesAndSubdirectories); + case Response of + 1 : ; // Do nothing in this case - I will not bother with changing the structure; it works :-) + 2 : DeleteAll := True; + 3 : SkipToNext := True; + else Break; + end; + end; + // Process delete + if not HandleDelete(AList[i]) then Break; + UpdateProgress1(i, Format('%d%%', [Round(Fr * i)])); + UpdateCaption1(PDataItemSL(AList[i])^.DataItem^.FDisplayName); + CommitGUIUpdate; + end; + + // Free the objects + if AList.Count > 0 then + for i := AList.Count - 1 downto 0 do FreeDataItem(PDataItemSL(AList[i])); + AList.Clear; + AList.Free; + if Engine.ChangeDir(CurrPath) <> 0 then DebugMsg(['*** WARNING: Cannot change to the origin location, strange behaviour might occur.']); + end; + SenderThread.FDoneThread := True; +end; + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) + + // Return False to break the process + function CopyFilesWorker_ProgressFunc(Sender: Pointer; BytesDone: Int64): boolean; cdecl; + begin + Result := True; +// DebugMsg(['*** CopyFilesWorker: ProgressFunc called (Sender=', QWord(Sender), ', BytesDone=', BytesDone, ')']); + try + if Assigned(Sender) and (TObject(Sender) is TWorkerThread) then + with TWorkerThread(Sender) do begin + if BytesDone = 0 then UpdateProgress1(0, '0%') + else UpdateProgress1(BytesDone, Format('%d%%', [Round(ParamFloat2 * BytesDone)])); + UpdateProgress2(ParamInt64 + BytesDone, Format('%d%%', [Round(ParamFloat1 * (ParamInt64 + BytesDone))])); + Result := not Cancelled; + CommitGUIUpdate; + end else DebugMsg(['*** CopyFilesWorker: Sender is not TWorkerThread']); + except + on E: Exception do DebugMsg(['*** Exception raised in ProgressFunc(Sender=', QWord(Sender), ', BytesDone=', BytesDone, '): (', E.ClassName, '): ', E.Message]); + end; + end; + + // Return True to ignore the error (Skip, Skip All, Ignore, Cancel) + function CopyFilesWorker_ErrorFunc(Sender: Pointer; ErrorType, ErrorNum: integer; FileName: string): boolean; cdecl; + var s, s2, s3: string; + begin + Result := False; + with TWorkerThread(Sender) do begin + if ParamBool2 then begin + Result := True; + Exit; + end; + case ErrorType of + 0 : begin + CancelIt; + Exit; + end; + 1 : s := LANGMemoryAllocationFailed; + 2 : s := LANGCannotOpenSourceFile; + 3 : s := LANGCannotOpenDestinationFile; + 4 : s := LANGCannotCloseDestinationFile; + 5 : s := LANGCannotCloseSourceFile; + 6 : s := LANGCannotReadFromSourceFile; + 7 : s := LANGCannotWriteToDestinationFile; + end; + if ParamBool1 then s2 := LANGCopyError + else s2 := LANGMoveError; + if ErrorType <> 1 then s3 := StrToUTF8(FileName) + else s3 := ''; + + case ShowDirDeleteDialog(3, s, s3, GetErrorString(ErrorNum), s2) of + 0, 252 : begin // Cancel button, Escape + Result := False; + CancelIt; + end; + 2 : Result := True; // Ignore + 3 : begin // Skip All + ParamBool2 := True; { Skip All Err } + Result := False; //** True? + end; + else {1, 124, 255 :} Result := False; // Skip + end; + end; + end; + + +procedure CopyFilesWorker(SenderThread: TWorkerThread); +// ParamFloat1 = Fr - internal +// ParamFloat2 = Fr2 - internal +// ParamInt64 = SizeDone - internal +// ParamBool1 = ModeCopy - internal +// ParamBool2 = SkipAllErr - internal +// ParamBool3 = CopyMode +// ParamBool4 = QuickRename +// ParamBool5 = OneFile +// ParamString1 = NewPath +// ParamString2 = Filepath +// ParamDataItem1 = QuickRenameDataItem +var DefResponse: integer; // Global variables for this function + SkipAll: boolean; + + + + // Returns True if file was successfully copied, if not, the file will be deleted in LocalCopyFile + function ManualCopyFile(SourceFile, DestFile: string; Append: boolean): boolean; + var fsrc, fdst: TEngineFileDes; + Error, BSize: integer; + Buffer: Pointer; + BytesDone, BytesRead, BytesWritten: Int64; + Res: boolean; + begin + DebugMsg(['ManualCopyFile: ', SourceFile, ' ---> ', DestFile]); + with SenderThread do begin + Result := False; + Error := 0; + fsrc := SrcEngine.OpenFile(SourceFile, omRead, Error); + if Error <> 0 then begin + CopyFilesWorker_ErrorFunc(SenderThread, 2, Error, SourceFile); // Cannot open source file + Exit; + end; + if Append then fdst := DestEngine.OpenFile(DestFile, omAppend, Error) + else fdst := DestEngine.OpenFile(DestFile, omWrite, Error); + if Error <> 0 then begin + SrcEngine.CloseFile(fsrc); + CopyFilesWorker_ErrorFunc(SenderThread, 3, Error, SourceFile); // Cannot open target file + Exit; + end; + + BytesDone := 0; + Res := True; + + BSize := DestEngine.GetBlockSize; + Buffer := malloc(BSize); + if Buffer = nil then begin + CopyFilesWorker_ErrorFunc(SenderThread, 1, errno, SourceFile); // Memory allocation failed + libc_free(Buffer); + Exit; + end; + memset(Buffer, 0, BSize); + + BytesWritten := 0; + repeat + BytesRead := SrcEngine.ReadFile(fsrc, Buffer, BSize, Error); + if (BytesRead = 0) and (Error <> 0) then + Res := CopyFilesWorker_ErrorFunc(SenderThread, 6, Error, SourceFile); // Cannot read from source file + if BytesRead > 0 then begin + BytesWritten := DestEngine.WriteFile(fdst, Buffer, BytesRead, Error); + if (BytesWritten < BytesRead) then + Res := CopyFilesWorker_ErrorFunc(SenderThread, 7, Error, DestFile); // Cannot write to source file + end; + Inc(BytesDone, BytesRead); + if not CopyFilesWorker_ProgressFunc(SenderThread, BytesDone) then begin + Res := False; + Break; + end; + until (BytesRead = 0) or (BytesWritten < BytesRead); + libc_free(Buffer); + + if DestEngine.CloseFile(fdst) <> 0 then begin + CopyFilesWorker_ErrorFunc(SenderThread, 4, errno, DestFile); // Cannot close target file + Exit; + end; + if SrcEngine.CloseFile(fsrc) <> 0 then begin + CopyFilesWorker_ErrorFunc(SenderThread, 5, errno, SourceFile); // Cannot close source file + Exit; + end; + Result := Res; + end; + end; + + // Returns True if the file was successfully copied and will be deleted on move + function LocalCopyFile(SourceFile, DestFile: string; Append: boolean): boolean; + var DataSrc, DataDest: PDataItem; + begin + Result := False; + try + with SenderThread do begin + AEngine := nil; + FCopyProgressFunc := CopyFilesWorker_ProgressFunc; + + // local -> local + if (SrcEngine is TLocalTreeEngine) and (DestEngine is TLocalTreeEngine) + then Result := DestEngine.CopyFileIn(SenderThread, SourceFile, DestFile, @CopyFilesWorker_ProgressFunc, @CopyFilesWorker_ErrorFunc, Append) + else + + // from local engine to VFS engine + if (SrcEngine is TLocalTreeEngine) and (DestEngine is TVFSEngine) then + begin + AEngine := DestEngine; + Result := (DestEngine as TVFSEngine).CopyFileInEx(SenderThread, SourceFile, DestFile, @CopyFilesWorker_ErrorFunc, Append, + @vfs_ask_question_callback, @vfs_ask_password_callback, @vfs_progress_callback, SenderThread); + end else + + // from VFS engine to local (most common use) + if (SrcEngine is TVFSEngine) and (DestEngine is TLocalTreeEngine) then + begin + AEngine := SrcEngine; + Result := (SrcEngine as TVFSEngine).CopyFileOutEx(SenderThread, SourceFile, DestFile, @CopyFilesWorker_ErrorFunc, Append, + @vfs_ask_question_callback, @vfs_ask_password_callback, @vfs_progress_callback, SenderThread); + end + + // VFS to VFS (not supported yet) + else + begin + AEngine := SrcEngine; + Result := ManualCopyFile(SourceFile, DestFile, Append); + end; + AEngine := nil; + + // Copy OK? (check size, otherwise delete target file) + if (not Append) and (not Result) then begin + DataSrc := SrcEngine.GetFileInfo(SourceFile, False, True); + if DataSrc = nil then Exit; + DataDest := DestEngine.GetFileInfo(DestFile, False, True); + if (DataDest <> nil) and (DataSrc^.Size <> DataDest^.Size) then + DestEngine.Remove(DestFile); + FreeDataItem(DataSrc); + FreeDataItem(DataDest); + end; + end; + except + on E: Exception do DebugMsg(['*** Exception raised in LocalCopyFile(SourceFile=', SourceFile, ', DestFile=', DestFile, ', Append=', Append, '): (', E.ClassName, '): ', E.Message]); + end; + end; + + function IsOnSameFS(SrcPath, DestPath: string): boolean; + begin + with SenderThread do begin + if (SrcEngine.ClassName <> DestEngine.ClassName) then Result := False else + if (SrcEngine is TVFSEngine) and (DestEngine is TVFSEngine) and + (SrcEngine as TVFSEngine).ArchiveMode and (DestEngine as TVFSEngine).ArchiveMode and + ((SrcEngine as TVFSEngine).ArchivePath <> '') and + ((SrcEngine as TVFSEngine).ArchivePath = (DestEngine as TVFSEngine).ArchivePath) + then Result := True else + Result := DestEngine.IsOnSameFS(SrcPath, DestPath, False); + end; + end; + + function TwoSameFiles(Path1, Path2: string; TestCaseInsensitiveFS: boolean): boolean; + begin + with SenderThread do begin + if (SrcEngine.ClassName <> DestEngine.ClassName) then Result := False else + if (SrcEngine is TVFSEngine) and (DestEngine is TVFSEngine) and + ((SrcEngine as TVFSEngine).ArchiveMode <> (DestEngine as TVFSEngine).ArchiveMode) + then Result := False else + if (SrcEngine is TVFSEngine) and (DestEngine is TVFSEngine) and + (SrcEngine as TVFSEngine).ArchiveMode and (DestEngine as TVFSEngine).ArchiveMode and + ((SrcEngine as TVFSEngine).ArchivePath <> '') and + ((SrcEngine as TVFSEngine).ArchivePath <> (DestEngine as TVFSEngine).ArchivePath) + then Result := False else + if WideCompareStr(Path1, Path2) = 0 then Result := True else + // * FIXME: causes stat errors, no need to check every file. + Result := TestCaseInsensitiveFS and DestEngine.TwoSameFiles(Path1, Path2, False); + end; + end; + + function DoOperation(AFileRec: PDataItemSL; const Dst: string; var ErrorKind: integer; const Append: boolean): integer; + begin + ErrorKind := 0; + Result := 0; + try + with SenderThread do + with AFileRec^ do begin + if DataItem^.IsLnk then begin + // Explicit copy the file + if ParamBool3 or (not IsOnSameFS(String(DataItem^.FName), ExtractFileDir(Dst))) then begin + ErrorKind := DestEngine.MakeSymLink(Dst, String(DataItem^.LnkPointTo)); + if ErrorKind <> 0 then Result := ERRCreateLink; + if not ParamBool3 then begin + ErrorKind := SrcEngine.Remove(String(DataItem^.FName)); + if ErrorKind <> 0 then Result := ERRRemove; + end; + end else begin // Move the file + ErrorKind := DestEngine.RenameFile(String(DataItem^.FName), Dst); + if ErrorKind <> 0 then Result := ERRCopyMove; + end; + end else // is not link + if ParamBool3 then begin // Copy mode + if LocalCopyFile(String(DataItem^.FName), Dst, Append) then begin + if IsOnRO and ConfClearReadOnlyAttr and (DataItem^.Mode and S_IWUSR = 0) then DataItem^.Mode := DataItem^.Mode or S_IWUSR; + DestEngine.Chmod(Dst, DataItem^.Mode); + DestEngine.Chown(Dst, DataItem^.UID, DataItem^.GID); + DestEngine.ChangeTimes(Dst, DataItem^.mtime, DataItem^.atime); + end; + end else // Move mode + if IsOnSameFS(String(DataItem^.FName), ExtractFileDir(Dst)) then begin + if TwoSameFiles(String(DataItem^.FName), Dst, True) and (not TwoSameFiles(String(DataItem^.FName), Dst, False)) then begin + DebugMsg(['*** Activating double-rename due to renaming on case-insensitive FS']); + ErrorKind := DestEngine.RenameFile(String(DataItem^.FName), Dst + '_tcmd'); + if ErrorKind = 0 then ErrorKind := DestEngine.RenameFile(Dst + '_tcmd', Dst); + end else ErrorKind := DestEngine.RenameFile(String(DataItem^.FName), Dst); + if ErrorKind <> 0 then Result := ERRCopyMove; + end else begin + if LocalCopyFile(String(DataItem^.FName), Dst, Append) then begin + if IsOnRO and ConfClearReadOnlyAttr and (DataItem^.Mode and S_IWUSR = 0) then DataItem^.Mode := DataItem^.Mode or S_IWUSR; + DestEngine.Chmod(Dst, DataItem^.Mode); + DestEngine.Chown(Dst, DataItem^.UID, DataItem^.GID); + DestEngine.ChangeTimes(Dst, DataItem^.mtime, DataItem^.atime); + if not Cancelled then begin + ErrorKind := SrcEngine.Remove(String(DataItem^.FName)); + if ErrorKind <> 0 then Result := ERRRemove; + end; + end; + end; + end; +// DebugMsg(['(II) CopyFilesWorker.DoOperation: finished']); + except + on E: Exception do DebugMsg(['*** Exception raised in DoOperation(AFileRec=', QWord(AFileRec), ', Dst=', Dst, ', ErrorKind=', ErrorKind, ', Append=', Append, '): (', E.ClassName, '): ', E.Message]); + end; + end; + + // Return False to break the processing (Cancel) + function HandleCopy(AFileRec: PDataItemSL; NewFilePath: string): boolean; + var Res, Response, ErrorKind, r: integer; + Item: PDataItem; + s, s1, s3, cap: string; + FromInfoLabel, ToInfoLabel, InfoLabelFormat: string; + begin + Result := True; + try + with SenderThread do begin + + // Second stage - change permissions + if (not AFileRec^.Stage1) and (ParamBool3 or ((not ParamBool3) and (not AFileRec^.ForceMove))) then + with AFileRec^ do begin + if IsOnRO and ConfClearReadOnlyAttr and (DataItem^.Mode and S_IWUSR = 0) then DataItem^.Mode := DataItem^.Mode or S_IWUSR; + DestEngine.Chmod(NewFilePath, DataItem^.Mode); + DestEngine.Chown(NewFilePath, DataItem^.UID, DataItem^.GID); + DestEngine.ChangeTimes(NewFilePath, DataItem^.mtime, DataItem^.atime); + if not ParamBool3 then SrcEngine.Remove(String(DataItem^.FName)); // Remove directory + Exit; + end; + + // First stage - copy data + if AFileRec^.DataItem^.IsDir then begin + Res := 0; + if AFileRec^.ForceMove and (not ParamBool3) + then begin + if TwoSameFiles(ExcludeTrailingPathDelimiter(string(AFileRec^.DataItem^.FName)), ExcludeTrailingPathDelimiter(string(AFileRec^.ADestination)), True) and (not + TwoSameFiles(ExcludeTrailingPathDelimiter(string(AFileRec^.DataItem^.FName)), ExcludeTrailingPathDelimiter(string(AFileRec^.ADestination)), False)) then + begin + DebugMsg(['*** Activating double-rename due to renaming on case-insensitive FS']); + ErrorKind := DestEngine.RenameFile(string(AFileRec^.DataItem^.FName), ExcludeTrailingPathDelimiter(string(AFileRec^.ADestination)) + '_tcmd'); + if ErrorKind = 0 then ErrorKind := DestEngine.RenameFile(ExcludeTrailingPathDelimiter(string(AFileRec^.ADestination)) + '_tcmd', ExcludeTrailingPathDelimiter(string(AFileRec^.ADestination))); + end else ErrorKind := DestEngine.RenameFile(string(AFileRec^.DataItem^.FName), string(AFileRec^.ADestination)); + if ErrorKind <> 0 then Res := ERRCopyMove + else Res := 0; + end else + if not DestEngine.DirectoryExists(NewFilePath, False) then begin + ErrorKind := DestEngine.MakeDir(NewFilePath); + if ErrorKind <> 0 then Res := ERRMkDir + else Res := 0; + end; + end else begin // not a directory + if not DestEngine.DirectoryExists(ExtractFileDir(NewFilePath), False) then DestEngine.MakeDir(ExtractFileDir(NewFilePath)); + SetProgress1Params(AFileRec^.DataItem^.Size + Ord(AFileRec^.DataItem^.Size = 0)); + if AFileRec^.DataItem^.Size <= 1 then ParamFloat2 := 1 else ParamFloat2 := 100 / (AFileRec^.DataItem^.Size - 1); + CopyFilesWorker_ProgressFunc(SenderThread, 0); + Res := 0; + if DestEngine.FileExists(NewFilePath, False) and + (not (not ParamBool3 and (not TwoSameFiles(NewFilePath, AFileRec^.DataItem^.FName, False)) and TwoSameFiles(NewFilePath, AFileRec^.DataItem^.FName, True))) + then begin + Response := DefResponse; + Item := DestEngine.GetFileInfo(NewFilePath, False, True); + if Item = nil then begin + DebugMsg(['Something went terribly wrong during copy - Item := DestEngine.GetFileInfoSL(NewFilePath) == NULL!']); + Result := False; + Exit; + end; + if Response = 0 then begin + case ConfSizeFormat of + 5: InfoLabelFormat := '%s, %s'; + else InfoLabelFormat := LANGOvewriteSBytesS; + end; + FromInfoLabel := Format(InfoLabelFormat, [FormatSize(Item^.Size, 0), FormatDate(Item^.mtime, True, True)]); + ToInfoLabel := Format(InfoLabelFormat, [FormatSize(AFileRec^.DataItem^.Size, 0), FormatDate(AFileRec^.DataItem^.mtime, True, True)]); + Response := ShowOverwriteDialog(1 + Ord(ParamBool3), Format(LANGOverwriteS, [StrToUTF8(NewFilePath)]), FromInfoLabel, + Format(LANGWithFileS, [AFileRec^.DataItem^.FDisplayName]), ToInfoLabel, + ExtractFileName(StrToUTF8(NewFilePath)), ExtractFileName(AFileRec^.DataItem^.FDisplayName), ExtractFileName(StrToUTF8(NewFilePath))); + s := FOverwriteRenameStr; + case Response of + // 1: Overwrite + // 3: Skip + 2 {Overwrite All}, 5 {Overwrite All Older}, 6 {Skip All}: DefResponse := Response; + 4 {Cancel}, 124 {Close Window}, 255: begin + Result := False; + Exit; + end; + 7: {Rename} begin + NewFilePath := Copy(NewFilePath, 1, LastDelimiter(PathDelim, NewFilePath)) + s; + Result := HandleCopy(AFileRec, NewFilePath); + Exit; + end; + 8 {Append}: begin + Res := DoOperation(AFileRec, NewFilePath, ErrorKind, True); + end; + end; + end; + + // Remove destination file if exists and should be overwritten + if (Response in [1, 2]) or ((Response = 5) and (Item^.mtime < AFileRec^.DataItem^.mtime)) then begin + r := DestEngine.Remove(NewFilePath); + while r <> 0 do begin + Res := ShowDirDeleteDialog(1, LANGTheFile, StrToUTF8(String(NewFilePath)), + Format(LANGCouldNotBeDeletedS, [GetErrorString(r)]), LANGCopyError); + case Res of + 1: begin + Result := True; + Exit; + end; + 2: r := DestEngine.Remove(NewFilePath); + 0, 124, 255: begin + Result := False; + Exit; + end; + end; + end; + Res := DoOperation(AFileRec, NewFilePath, ErrorKind, False); + end; + end else Res := DoOperation(AFileRec, NewFilePath, ErrorKind, False); + end; + + // Error handling + if (Res <> 0) and (not SkipAll) then begin + if ParamBool3 then cap := LANGCopy + else cap := LANGMove; + case Res of + ERRCreateLink: begin + s1 := LANGTheSymbolicLink; + if ErrorKind = 0 then s3 := LANGCouldNotBeCreated else + s3 := Format(LANGCouldNotBeCreatedS, [GetErrorString(ErrorKind)]); + end; + ERRMkDir: begin + s1 := LANGTheDirectory; + if ErrorKind = 0 then s3 := LANGCouldNotBeCreated else + s3 := Format(LANGCouldNotBeCreatedS, [GetErrorString(ErrorKind)]); + end; + ERRRemove: begin + if AFileRec^.DataItem^.IsDir then s1 := LANGTheDirectory else + if AFileRec^.DataItem^.IsLnk then s1 := LANGTheSymbolicLink else + s1 := LANGTheFile; + if ErrorKind = 0 then s3 := LANGCouldNotBeDeleted else + s3 := Format(LANGCouldNotBeDeletedS, [GetErrorString(ErrorKind)]); + end; + ERRCopyMove: begin + if ParamBool3 then s1 := LANGCannotCopyFile else + s1 := LANGCannotMoveFile; + if ErrorKind = 0 then s3 := '' else + s3 := GetErrorString(ErrorKind); + end; + end; + Response := ShowDirDeleteDialog(1, s1, StrToUTF8(String(NewFilePath)), s3, cap); + case Response of + 1 : Result := True; // Skip + 2 : Result := HandleCopy(AFileRec, NewFilePath); // Retry + 3 : begin // Skip All + SkipAll := True; + Result := True; + end; + 0, 124, 255 : Result := False; // Cancel + end; + end; + end; +// DebugMsg(['(II) CopyFilesWorker.HandleCopy: finished']); + except + on E: Exception do DebugMsg(['*** Exception raised in HandleCopy(AFileRec=', QWord(AFileRec), ', NewFilePath=', NewFilePath, '): (', E.ClassName, '): ', E.Message]); + end; + end; + + procedure HandleProcessPattern(AList: TList; CurrPath, FullPath, ParamFileName: string; ParamDir, Ren: boolean); + var s, s2: string; + b, CaseInsensitiveRename: boolean; + Info: PDataItemSL; + begin + with SenderThread do + if not Ren then begin + if ParamDir then FillDirFiles(SrcEngine, AList, FullPath) + else begin + Info := GetFileInfoSL(SrcEngine, FullPath); + if Info = nil then DebugMsg(['$$$ Copy: Something went wrong while building the filelist...']) + else AList.Add(Info); + end; + end else begin + s := ProcessPattern(DestEngine, ParamString1, CurrPath, ParamFileName, ParamDir); + CaseInsensitiveRename := (WideCompareStr(ParamString1, ParamFileName) <> 0) and (WideCompareText(ParamString1, ParamFileName) = 0) and + ParamDir and DestEngine.TwoSameFiles(IncludeTrailingPathDelimiter(CurrPath) + ParamString1, IncludeTrailingPathDelimiter(CurrPath) + ParamFileName, False); +// DebugMsg(['HandleProcessPattern: s = ', s]); + b := False; + if ParamDir then begin + b := DestEngine.DirectoryExists(ExcludeTrailingPathDelimiter(s), False) and (not CaseInsensitiveRename); + if (not b) and (s <> '/') then begin + s2 := ExcludeTrailingPathDelimiter(s); + s2 := ExcludeTrailingPathDelimiter(Copy(s2, 1, LastDelimiter('/', s2))); + b := DestEngine.DirectoryExists(ExcludeTrailingPathDelimiter(s2), False); + end; + end; + if (not ParamDir) or (ParamDir and b and IsOnSameFS(ExcludeTrailingPathDelimiter(FullPath), s2)) + then begin + Info := GetFileInfoSL(SrcEngine, FullPath); + if Info = nil then begin + DebugMsg(['$$$ Copy: Something went wrong while building the filelist...']); + ErrorHappened := True; + end else begin + Info^.ADestination := strdup(PChar(s)); + Info^.ForceMove := True; + AList.Add(Info); + end; + end else FillDirFiles(SrcEngine, AList, FullPath); + end; + end; + +var i: longint; + List: TList; + CurrPath, SaveDestPath, SaveSrcPath, s: string; + MaxSize: Int64; +begin + List := TList.Create; + List.Clear; + with SenderThread do begin + ErrorHappened := False; + FCancelled := False; + SaveSrcPath := ''; + CurrPath := IncludeTrailingPathDelimiter(SrcEngine.Path); + SaveDestPath := DestEngine.Path; + ParamString1 := ExcludeTrailingPathDelimiter(ParamString1); + if ParamString1 = '' then ParamString1 := PathDelim; + + if ParamBool5 then begin // HandleVFSFromArchive + if not ExtractFromVFSAll then HandleProcessPattern(List, CurrPath, ParamString2, ExtractFileName(ParamString2), False, False) + else begin + SaveSrcPath := IncludeTrailingPathDelimiter(SrcEngine.Path); + SrcEngine.SetPath('/'); + CurrPath := '/'; + HandleProcessPattern(List, '/', '/', '', True, False); + end; + end else + if ParamBool4 then begin // Quick-Rename + with ParamDataItem1^ do + HandleProcessPattern(List, CurrPath, CurrPath + String(FName), String(FName), IsDir and (not IsLnk), True); + end else begin // Not Quick-Rename + if not ExtractFromVFSMode then begin + if DataList.Count > 0 then + for i := 0 to DataList.Count - 1 do + with PDataItem(DataList[i])^ do + if (not UpDir) and Selected + then HandleProcessPattern(List, CurrPath, CurrPath + String(FName), String(FName), IsDir and (not IsLnk), not ParamBool3); + if (List.Count = 0) and Assigned(SelectedItem) and (not SelectedItem^.UpDir) then + with SelectedItem^ do + HandleProcessPattern(List, CurrPath, CurrPath + String(FName), String(FName), IsDir and (not IsLnk), not ParamBool3); + end else begin // Extract from VFS mode + DebugMsg(['CopyFilesWorker: Should not be reached']); + if (not ExtractFromVFSAll) and Assigned(SelectedItem) + then HandleProcessPattern(List, CurrPath, CurrPath + String(SelectedItem^.FName), String(SelectedItem^.FName), SelectedItem^.IsDir and (not SelectedItem^.IsLnk), not ParamBool3) + else begin + SaveSrcPath := IncludeTrailingPathDelimiter(SrcEngine.Path); + SrcEngine.SetPath('/'); + CurrPath := '/'; + HandleProcessPattern(List, '/', '/', '', True, False); + end; + end; + end; + +{ if DestEngine.ChangeDir(CurrPath) <> 0 then DebugMsg(['*** WARNING: Cannot change to the origin location, strange behaviour may occur.']); + if SrcEngine.ChangeDir(CurrPath) <> 0 then DebugMsg(['*** WARNING: Cannot change to the origin location, strange behaviour may occur.']); } +// DebugWriteListSL(List); + + __chdir('/'); + // Compute total size of files to copy + MaxSize := 0; ParamInt64 := 0; + if List.Count > 0 then + for i := 0 to List.Count - 1 do + if PDataItemSL(List[i])^.Stage1 and (PDataItemSL(List[i])^.DataItem^.Size > 0) and (not PDataItemSL(List[i])^.DataItem^.IsDir) and (not PDataItemSL(List[i])^.DataItem^.IsLnk) + then Inc(MaxSize, PDataItemSL(List[i])^.DataItem^.Size); + SrcEngine.BlockSize := ComputeBlockSize(MaxSize); + DestEngine.BlockSize := ComputeBlockSize(MaxSize); + + // Prepare the Progress window + SetProgress2Params(MaxSize + Ord(MaxSize = 0)); + UpdateProgress1(0, '0%'); + UpdateProgress2(0, '0%'); + CommitGUIUpdate; + + DefResponse := 0; + ParamBool1 := ParamBool3; + SkipAll := False; + ParamBool2 := False; + + if MaxSize < 2 then ParamFloat1 := 1 else ParamFloat1 := 100 / (MaxSize - 1); + if List.Count > 0 then + for i := 0 to List.Count - 1 do begin + if Assigned(PDataItemSL(List[i])^.ADestination) + then s := string(PDataItemSL(List[i])^.ADestination) + else + begin + s := ProcessPattern(DestEngine, ParamString1, CurrPath, Copy(PDataItemSL(List[i])^.DataItem^.FName, Length(CurrPath) + 1, Length(PDataItemSL(List[i])^.DataItem^.FName) - Length(CurrPath)), + PDataItemSL(List[i])^.DataItem^.IsDir and (not PDataItemSL(List[i])^.DataItem^.IsLnk)); +// DebugMsg(['s2 = ', Copy(PDataItemSL(List[i])^.AName, Length(CurrPath) + 1, Length(PDataItemSL(List[i])^.AName) - Length(CurrPath)), ', s = ', s]); + end; + + if not (SrcEngine is TVFSEngine) then UpdateCaption1(Format(LANGFromS, [string(PDataItemSL(List[i])^.DataItem^.FDisplayName)])) else + if (SrcEngine as TVFSEngine).ArchiveMode then UpdateCaption1(Format(LANGFromS, [Format(ConstFullPathFormatStr, [(SrcEngine as TVFSEngine).ArchivePath, string(PDataItemSL(List[i])^.DataItem^.FDisplayName)])])) + else UpdateCaption1(Format(LANGFromS, [GetURIPrefix((SrcEngine as TVFSEngine).GetPathURI) + StrToUTF8(string(PDataItemSL(List[i])^.DataItem^.FDisplayName))])); + if not (DestEngine is TVFSEngine) then UpdateCaption2(Format(LANGToS, [StrToUTF8(s)])) else + if (DestEngine as TVFSEngine).ArchiveMode then UpdateCaption2(Format(LANGToS, [Format(ConstFullPathFormatStr, [(DestEngine as TVFSEngine).ArchivePath, StrToUTF8(s)])])) + else UpdateCaption2(Format(LANGToS, [GetURIPrefix((DestEngine as TVFSEngine).GetPathURI) + StrToUTF8(s)])); + CommitGUIUpdate; + if TwoSameFiles(s, string(PDataItemSL(List[i])^.DataItem^.FName), ParamBool3) and (not PDataItemSL(List[i])^.DataItem^.IsDir) then begin + FCancelMessage := LANGCannotCopyFileToItself; + FShowCancelMessage := True; + ErrorHappened := True; + Break; + end; +// * FIXME: why the hell we had something like this here?? +// if s <> string(PDataItemSL(List[i])^.DataItem^.FName) then + if not HandleCopy(List[i], s) then begin + ErrorHappened := True; + Break; + end; + if (not PDataItemSL(List[i])^.DataItem^.IsDir) and (not PDataItemSL(List[i])^.DataItem^.IsLnk) + then Inc(ParamInt64, PDataItemSL(List[i])^.DataItem^.Size); + if Cancelled then begin + FCancelMessage := LANGUserCancelled; + FShowCancelMessage := True; + ErrorHappened := True; + Break; + end; + end; + + // Free the objects + if List.Count > 0 then + for i := List.Count - 1 downto 0 do FreeDataItem(PDataItemSL(List[i])); + List.Clear; + List.Free; + if DestEngine.ChangeDir(SaveDestPath) <> 0 then DebugMsg(['*** WARNING: Cannot change to the origin location, strange behaviour might occur.']); + if SaveSrcPath <> '' then CurrPath := SaveSrcPath; + if SrcEngine.ChangeDir(CurrPath) <> 0 then DebugMsg(['*** WARNING: Cannot change to the origin location, strange behaviour might occur.']); + end; + SenderThread.FDoneThread := True; + DebugMsg(['(II) CopyFilesWorker: finished']); +end; + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +procedure MergeFilesWorker(SenderThread: TWorkerThread); +// ParamBool1 = HasInitialCRC +// ParamString1 = NewPath +// ParamString2 = FileName +// ParamString3 = TargetName +// ParamLongWord1 = TargetCRC +// ParamInt64 = TargetSize + +var FD: TEngineFileDes; + Error, Count, MergeBlockSize: integer; + Buffer: Pointer; + CurrentCRC: LongWord; + PrivateCancel: boolean; + SizeDone: Int64; + TargetName: string; + + + function PasteFile(FName: string): boolean; + var FDR: TEngineFileDes; + wCount: integer; + Stat: PDataItem; + begin + Result := False; + with SenderThread do begin + if ParamBool1 then UpdateCaption2(Format(LANGToS, [StrToUTF8(FName)])) + else UpdateCaption1(Format(LANGFromS, [StrToUTF8(FName)])); + UpdateProgress1(0, '0 %'); + CommitGUIUpdate; + Stat := Engine.GetFileInfo(FName, True, True); + if not Assigned(Stat) then Exit; + SetProgress1Params(Stat^.Size); + FreeDataItem(Stat); + FDR := Engine.OpenFile(FName, omRead, Error); + if Error <> 0 then Exit; + repeat + Count := Engine.ReadFile(FDR, Buffer, MergeBlockSize, Error); + if Error <> 0 then begin + Engine.CloseFile(FD); + Exit; + end; + wCount := Engine.WriteFile(FD, Buffer, Count, Error); + if (Error <> 0) or (Count <> wCount) then begin + FCancelMessage := Format(LANGAnErrorOccuredWhileWritingFileSS, [ExtractFileName(TargetName), GetErrorString(Error)]); + FShowCancelMessage := True; + PrivateCancel := True; + Result := True; // Fake this to don't show next disc dialog + Exit; + end; + CurrentCRC := CRC32(CurrentCRC, Buffer, Count); + UpdateProgress1(FProgress1Pos + Count, Format('%d %%', [Trunc((FProgress1Pos + Count) / FProgress1Max * 100)])); + Inc(SizeDone, Count); + if ParamBool1 then UpdateProgress2(SizeDone, Format('%d %%', [Trunc(SizeDone / FProgress2Max * 100)])); + CommitGUIUpdate; + until (Count < MergeBlockSize) or Cancelled; + Engine.CloseFile(FDR); + end; + Result := True; + end; + + +var CurrFile, SourcePath, TargetFinalName: string; + HasFinalCRC, b: boolean; + Stat: PDataItem; +begin + with SenderThread do begin + HasFinalCRC := ParamBool1; + TargetFinalName := ParamString3; + if (Length(ParamString2) > 4) and (WideUpperCase(RightStr(ParamString2, 4)) = '.CRC') + then CurrFile := ChangeFileExt(ExtractFileName(ParamString2), '.001') + else CurrFile := ExtractFileName(ParamString2); + SourcePath := ExtractFilePath(ParamString2); + if ParamString3 = '' then ParamString3 := ChangeFileExt(ExtractFileName(ParamString2), '.out'); + TargetName := ProcessPattern(Engine, ParamString1, Engine.Path, ParamString3, False); + if Engine.FileExists(TargetName, False) then + if ShowMessageBox(Format(LANGTheTargetFileSAlreadyExistsDoYouWantToOverwriteIt, [StrToUTF8(TargetName)]), [mbYes, mbNo], mbQuestion, mbNone, mbNo) = mbYes then + begin + Error := Engine.Remove(TargetName); + if Error <> 0 then begin + FCancelMessage := Format(LANGTheTargetFileSCannotBeRemovedS, [StrToUTF8(ExtractFileName(TargetName)), GetErrorString(Error)]); + FShowCancelMessage := True; + Exit; + end; + end else Exit; + + Stat := Engine.GetFileInfo(ParamString2, True, True); + if Assigned(Stat) then MergeBlockSize := ComputeBlockSize(Stat^.Size) + else MergeBlockSize := 65536*4; + FreeDataItem(Stat); + try + Buffer := malloc(MergeBlockSize); + memset(Buffer, 0, MergeBlockSize); + except + FCancelMessage := LANGAnErrorOccuredWhileInitializingMemoryBlock; + FShowCancelMessage := True; + Exit; + end; + FD := Engine.OpenFile(TargetName, omWrite, Error); + if Error <> 0 then begin + FCancelMessage := Format(LANGAnErrorOccuredWhileOpeningFileSS, [StrToUTF8(TargetName), GetErrorString(Error)]); + FShowCancelMessage := True; + libc_free(Buffer); + Exit; + end; + + CurrentCRC := 0; + SizeDone := 0; + PrivateCancel := False; + if ParamBool1 then begin + SetProgress2Params(ParamInt64); + UpdateProgress2(0, '0 %'); + UpdateCaption2(Format(LANGFromS, [StrToUTF8(TargetName)])); + CommitGUIUpdate; + end; { else begin + Label2.XAlign := 0; + Label2.XPadding := 20; + end; } + + repeat + b := PasteFile(IncludeTrailingPathDelimiter(SourcePath) + CurrFile); + if not b then begin + PrivateCancel := ShowNewDirDialog(LANGMergeCaption, LANGPleaseInsertNextDiskOrGiveDifferentLocation, StrToUTF8(SourcePath)) <> integer(mbOK); + if not PrivateCancel then begin + SourcePath := UTF8ToStr(FNewDirEdit); + if not HasFinalCRC then + HasFinalCRC := CRCGetInfo(IncludeTrailingPathDelimiter(SourcePath) + CurrFile, Engine, TargetFinalName, ParamLongWord1, ParamInt64); + Continue; + end; + end; + try + CurrFile := Copy(CurrFile, 1, LastDelimiter('.', CurrFile)) + Format('%.3d', [StrToInt( + Copy(CurrFile, LastDelimiter('.', CurrFile) + 1, Length(CurrFile) - LastDelimiter('.', CurrFile))) + 1]); + except + CurrFile := ''; + end; + until (SizeDone = ParamInt64) or Cancelled or PrivateCancel {or ((not b) and (not HasInitialCRC))} or (CurrFile = ''); + if (not ParamBool1) and HasFinalCRC then Engine.RenameFile(TargetName, IncludeTrailingPathDelimiter(ExtractFilePath(TargetName)) + TargetFinalName); + if Cancelled and (not PrivateCancel) then begin + FCancelMessage := LANGUserCancelled; + FShowCancelMessage := True; + end; + if not (Cancelled or PrivateCancel) then + if HasFinalCRC then begin + if CurrentCRC = ParamLongWord1 + then ShowMessageBox(Format(LANGMergeOfSSucceeded, [StrToUTF8(ExtractFileName(TargetFinalName))]), [mbOK], mbInfo, mbNone, mbOK) + else ShowMessageBox(LANGWarningCreatedFileFailsCRCCheck, [mbOK], mbWarning, mbNone, mbOK); + end else ShowMessageBox(Format(LANGMergeOfSSucceeded_NoCRCFileAvailable, [StrToUTF8(ExtractFileName(TargetFinalName))]), [mbOK], mbInfo, mbNone, mbOK); + Engine.CloseFile(FD); + end; + libc_free(Buffer); + SenderThread.FDoneThread := True; +end; + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +procedure SplitFilesWorker(SenderThread: TWorkerThread); +// ParamInt64 = SplitSize +// ParamString1 = FileName +// ParamString2 = NewPath +// ParamBool1 = DeleteTarget + +const SplitBlockSize = 65536*4; +var FD: TEngineFileDes; + Error: integer; + FileCRC: LongWord; + Buffer: Pointer; + PrivateCancel: boolean; + FilePath: string; + SizeDone, TDF, FileSize, CurrSize: Int64; + + + function WriteSplitPart(TargetFile: string; PartSize: Int64; var Written: Int64): boolean; + var FDW: TEngineFileDes; + Count, wCount, bl: integer; + begin + Result := False; + Written := 0; + with SenderThread do begin + FDW := Engine.OpenFile(TargetFile, omWrite, Error); + DebugMsg(['-- Opening file ', ExtractFileName(TargetFile), ', PartSize = ', PartSize]); + if Error <> 0 then Exit; + if ParamInt64 > 0 then begin + UpdateCaption2(Format(LANGToS, [StrToUTF8(TargetFile)])); + SetProgress1Params(PartSize); + UpdateProgress1(0, '0 %'); + end else UpdateCaption1(Format(LANGToS, [StrToUTF8(TargetFile)])); + CommitGUIUpdate; + repeat + DebugMsg(['Seek to ', Engine.FileSeek(FD, SizeDone + Written, Error), ', Written = ', Written]); + if Written + SplitBlockSize > PartSize then bl := PartSize - Written + else bl := SplitBlockSize; + Count := Engine.ReadFile(FD, Buffer, bl, Error); + if (Error <> 0) or (Count <> bl) then begin + Engine.CloseFile(FDW); + DebugMsg(['Read Error: ', GetErrorString(Error), ', Count = ', Count, ', bl = ', bl]); + if (Count <> bl) and (Error = 0) then Error := EIO; + Exit; + end; + wCount := Engine.WriteFile(FDW, Buffer, Count, Error); + Inc(Written, wCount); + FileCRC := CRC32(FileCRC, Buffer, wCount); + if (Error <> 0) or (Count <> wCount) then begin + Engine.CloseFile(FDW); + DebugMsg(['Write Error: ', GetErrorString(Error), ', Count = ', Count, ', wCount = ', wCount]); + if (wCount <> Count) and (Error = 0) then Error := ENOSPC; + Exit; + end; + UpdateProgress1(FProgress1Pos + wCount, Format('%d %%', [Trunc((FProgress1Pos + wCount) / FProgress1Max * 100)])); + if ParamInt64 > 0 then UpdateProgress2(FProgress2Pos + wCount, Format('%d %%', [Trunc((FProgress2Pos + wCount) / FProgress2Max * 100)])); + CommitGUIUpdate; + until (Written = PartSize) or Cancelled or PrivateCancel; + Engine.CloseFile(FDW); + end; + DebugMsg(['-- Closing file ', ExtractFileName(TargetFile), ', PartSize = ', PartSize, ', Written = ', Written]); + Result := True; + end; + + // Returns True if it should break the process + function NewDiskQuestion: boolean; + var x: Int64; + xx: string; + begin + Result := False; + with SenderThread do begin + Engine.GetFileSystemInfo(FilePath, x, TDF, xx); + // Calculate part size + if ParamInt64 = 0 then begin + if FileSize - SizeDone > TDF then CurrSize := TDF + else CurrSize := FileSize - SizeDone; + end else + if SizeDone + ParamInt64 > FileSize then CurrSize := FileSize - SizeDone + else CurrSize := ParamInt64; + if (TDF < 512) {or (CurrSize < 512)} or (TDF < CurrSize) then begin + DebugMsg(['-- New disk question']); + libc_chdir('/'); + PrivateCancel := ShowNewDirDialog(LANGSplitCaption, LANGPleaseInsertNextDiskOrGiveDifferentLocation, + StrToUTF8(FilePath)) <> integer(mbOK); + if not PrivateCancel then FilePath := UTF8ToStr(FNewDirEdit); + Result := PrivateCancel; + end; + end; + end; + + +var i: integer; + OriginalFName, st, FileName: string; + ws: Int64; + Stat: PDataItem; + b: boolean; + List: TList; + x: Int64; + xx: string; +begin + with SenderThread do begin + Stat := Engine.GetFileInfo(ParamString1, True, True); + if not Assigned(Stat) then begin + FCancelMessage := Format(LANGCannotOpenFileS, [StrToUTF8(ParamString1)]); + FShowCancelMessage := True; + Exit; + end; + if (ParamInt64 > 0) and (Stat^.Size > ParamInt64 * 999) then begin + FCancelMessage := LANGCannotSplitTheFileToMoreThan999Parts; + FShowCancelMessage := True; + Exit; + end; + FileSize := Stat^.Size; + FreeDataItem(Stat); + SizeDone := 0; + FileCRC := 0; + List := TList.Create; + + try + Buffer := malloc(SplitBlockSize); + memset(Buffer, 0, SplitBlockSize); + except + FCancelMessage := LANGAnErrorOccuredWhileInitializingMemoryBlock; + FShowCancelMessage := True; + Exit; + end; + FD := Engine.OpenFile(ParamString1, omRead, Error); + if Error <> 0 then begin + FCancelMessage := Format(LANGAnErrorOccuredWhileOpeningFileSS, [StrToUTF8(ParamString1), GetErrorString(Error)]); + libc_free(Buffer); + Exit; + end; + FilePath := IncludeTrailingPathDelimiter(ProcessPattern(Engine, ParamString2, Engine.Path, '', True)); + FileName := ExtractFileName(ParamString1); + OriginalFName := FileName; + if Pos('.', FileName) > 1 then FileName := ChangeFileExt(FileName, '.001') + else FileName := FileName + '.001'; + PrivateCancel := False; + + if ParamInt64 > 0 then begin + SetProgress2Params(FileSize); + UpdateProgress2(0, '0 %'); + end else begin + SetProgress1Params(FileSize); + UpdateProgress1(0, '0 %'); + end; + UpdateCaption1(Format(LANGFromS, [StrToUTF8(IncludeTrailingPathDelimiter(FilePath) + OriginalFName)])); + CommitGUIUpdate; + + repeat + Engine.GetFileSystemInfo(FilePath, x, TDF, xx); + // Delete target files if necessary + if ParamBool1 and ((TDF < 512) or (TDF < FileSize) or (TDF < ParamInt64)) then try + if List.Count > 0 then + for i := List.Count - 1 downto 0 do + FreeDataItem(PDataItem(List[i])); + List.Clear; + Error := Engine.GetListing(List, FilePath, ConfShowDotFiles, False, False); + if (Error = 0) and (List.Count > 0) then begin + st := ''; + if List.Count < 6 then begin + for i := 0 to List.Count - 1 do + st := st + ' ' + string(PDataItem(List[i])^.FDisplayName) + #10; + b := ShowMessageBox(Format(LANGThereAreSomeFilesInTheTargetDirectorySDoYouWantToDeleteThem, [StrToUTF8(st)]), [mbYes, mbNo], mbQuestion, mbNone, mbNo) = mbYes; + end else b := ShowMessageBox(Format(LANGThereAreDFilesInTheTargetDirectoryDoYouWantToDeleteThem, [List.Count]), [mbYes, mbNo], mbQuestion, mbNone, mbNo) = mbYes; + if b then + for i := 0 to List.Count - 1 do begin + Error := Engine.Remove(IncludeTrailingPathDelimiter(FilePath) + string(PDataItem(List[i])^.FName)); + if Error <> 0 then ShowMessageBox(Format(LANGTheTargetFileSCannotBeRemovedS, [StrToUTF8(IncludeTrailingPathDelimiter(FilePath)) + string(PDataItem(List[i])^.FDisplayName), GetErrorString(Error)]), [mbOK], mbError, mbNone, mbOK); + end; + end; + except end; + // Test for target file existence + if Engine.FileExists(IncludeTrailingPathDelimiter(FilePath) + FileName, False) then begin + b := ShowMessageBox(Format(LANGTheTargetFileSAlreadyExistsDoYouWantToOverwriteIt, [StrToUTF8(IncludeTrailingPathDelimiter(FilePath) + FileName)]), [mbYes, mbNo], mbQuestion, mbNone, mbNo) = mbYes; + if b then begin + Error := Engine.Remove(IncludeTrailingPathDelimiter(FilePath) + FileName); + if Error <> 0 then begin + FCancelMessage := Format(LANGTheTargetFileSCannotBeRemovedS, [StrToUTF8(IncludeTrailingPathDelimiter(FilePath) + FileName), GetErrorString(Error)]); + FShowCancelMessage := True; + PrivateCancel := True; + Break; + end; + end else begin + PrivateCancel := True; + Break; + end; + end; + // Free space check + if NewDiskQuestion then Break; + // Writing + ws := 0; + if (CurrSize >= 512) and (TDF >= CurrSize) then begin + b := WriteSplitPart(IncludeTrailingPathDelimiter(FilePath) + FileName, CurrSize, ws); + if (not b) and (ParamInt64 > 0) then begin + FCancelMessage := Format(LANGAnErrorOccuredWhileOperationS, [GetErrorString(Error)]); + FShowCancelMessage := True; + PrivateCancel := True; + Break; + end; + Inc(SizeDone, ws); + if ParamInt64 > 0 then UpdateProgress2(SizeDone, Format('%d %%', [Trunc(SizeDone / FileSize * 100)])) + else UpdateProgress1(SizeDone, Format('%d %%', [Trunc(SizeDone / FileSize * 100)])); + CommitGUIUpdate; + end; + // Free space check - New disk question after operation + if NewDiskQuestion then Break; + // Change filename + if ws > 0 then + try FileName := Copy(FileName, 1, LastDelimiter('.', FileName)) + + Format('%.3d', [StrToInt(Copy(FileName, LastDelimiter('.', FileName) + 1, + Length(FileName) - LastDelimiter('.', FileName))) + 1]); + except + FileName := ''; + end; + until (SizeDone = FileSize) or Cancelled or PrivateCancel or (FileName = ''); + if Cancelled and (not PrivateCancel) then begin + FCancelMessage := LANGUserCancelled; + FShowCancelMessage := True; + end; + if not (Cancelled or PrivateCancel) then begin + repeat + Engine.GetFileSystemInfo(FilePath, x, TDF, xx); + if (TDF < 512) and (not NewDiskQuestion) then Break; + until (TDF >= 512) or PrivateCancel or Cancelled; + if WriteCRCFile(Engine, IncludeTrailingPathDelimiter(FilePath) + FileName, OriginalFName, SizeDone, FileCRC) + then ShowMessageBox(Format(LANGSplitOfSSucceeded, [StrToUTF8(OriginalFName)]), [mbOK], mbInfo, mbNone, mbOK) + else begin + FCancelMessage := Format(LANGSplitOfSFailed, [StrToUTF8(OriginalFName)]); + FShowCancelMessage := True; + end; + end; + Engine.CloseFile(FD); + end; + if List.Count > 0 then + for i := List.Count - 1 downto 0 do + FreeDataItem(PDataItem(List[i])); + List.Free; + SenderThread.FDoneThread := True; +end; + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +procedure ChmodFilesWorker(SenderThread: TWorkerThread); +// ParamBool1 = Recursive +// ParamInt1 = All/Dir/Files +// ParamCardinal1 = Mode + +var SkipAll: boolean; + + function HandleChmod(AFileRec: PDataItemSL): boolean; + var Res, Response: integer; + begin + Result := True; + with SenderThread do begin +// DebugMsg(['Chmod Debug: IsDir: ', AFileRec^.IsDir, ', Stage1: ', AFileRec^.Stage1, ', IsLnk: ', AFileRec^.IsLnk, '; Result = ', AFileRec^.IsDir and AFileRec^.Stage1 and (not AFileRec^.IsLnk)]); + if AFileRec^.DataItem^.IsDir and ParamBool1 and AFileRec^.Stage1 and (not AFileRec^.DataItem^.IsLnk) then Exit; + if (not AFileRec^.DataItem^.IsDir) and ParamBool1 and (ParamInt1 = 1) then Exit; // Directories only + if AFileRec^.DataItem^.IsDir and ParamBool1 and (ParamInt1 = 2) then Exit; // Files only + Res := Engine.Chmod(String(AFileRec^.DataItem^.FName), ParamCardinal1); +// DebugMsg(['Result : ', Res]); + if Res <> 0 then + if SkipAll then Result := True else + begin + Response := ShowDirDeleteDialog(1, LANGTheFileDirectory, String(AFileRec^.DataItem^.FDisplayName), Format(LANGCouldNotBeChmoddedS, + [GetErrorString(Res)]), LANGDialogChangePermissions); + case Response of + 1 : Result := True; + 3 : begin + SkipAll := True; + Result := True; + end; + 2 : Result := HandleChmod(AFileRec); + else Result := False; + end; + end; + end; + end; + +var i: longint; + AList: TList; + Fr: Single; +begin + SkipAll := False; + with SenderThread do begin + AList := TList.Create; + PrepareJobFilesFromPanel(AList, not ParamBool1); + libc_chdir('/'); + SetProgress1Params(AList.Count); + UpdateProgress1(0, '0 %'); + CommitGUIUpdate; + +// DebugWriteListSL(AList); + + if AList.Count = 1 then Fr := 1 else Fr := 100 / (AList.Count - 1); + if AList.Count > 0 then + for i := 0 to AList.Count - 1 do begin + if Cancelled then begin + FCancelMessage := LANGUserCancelled; + FShowCancelMessage := True; + Break; + end; + // Process chmod + if not HandleChmod(AList[i]) then Break; + UpdateProgress1(i, Format('%d%%', [Round(Fr * i)])); + UpdateCaption1(PDataItemSL(AList[i])^.DataItem^.FDisplayName); + CommitGUIUpdate; + end; + + // Free the objects + if AList.Count > 0 then + for i := AList.Count - 1 downto 0 do FreeDataItem(PDataItemSL(AList[i])); + AList.Clear; + AList.Free; + end; + SenderThread.FDoneThread := True; +end; + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +procedure ChownFilesWorker(SenderThread: TWorkerThread); +// ParamBool1 = Recursive +// ParamCardinal1 = UID +// ParamCardinal2 = GID +var SkipAll: boolean; + + function HandleChown(AFileRec: PDataItemSL): boolean; + var Res, Response: integer; + begin + Result := True; + with SenderThread do begin +// DebugMsg(['Chown Debug: IsDir: ', AFileRec^.IsDir, ', Stage1: ', AFileRec^.Stage1, ', IsLnk: ', AFileRec^.IsLnk, '; Result = ', AFileRec^.IsDir and AFileRec^.Stage1 and (not AFileRec^.IsLnk)]); + if (AFileRec^.DataItem^.IsDir and ParamBool1 and AFileRec^.Stage1 and (not AFileRec^.DataItem^.IsLnk)) or + ((not AFileRec^.DataItem^.IsDir) and ParamBool1) then Exit; + Res := Engine.Chown(String(AFileRec^.DataItem^.FName), ParamCardinal1, ParamCardinal2); +// DebugMsg(['Result : ', Res]); + if Res <> 0 then + if SkipAll then Result := True else + begin + Response := ShowDirDeleteDialog(1, LANGTheFileDirectory, String(AFileRec^.DataItem^.FDisplayName), Format(LANGCouldNotBeChownedS, + [GetErrorString(Res)]), LANGDialogChangeOwner); + case Response of + 1 : Result := True; + 3 : begin + SkipAll := True; + Result := True; + end; + 2 : Result := HandleChown(AFileRec); + else Result := False; + end; + end; + end; + end; + +var i: longint; + AList: TList; + Fr: Single; +begin + SkipAll := False; + with SenderThread do begin + AList := TList.Create; + PrepareJobFilesFromPanel(AList, not ParamBool1); + libc_chdir('/'); + SetProgress1Params(AList.Count); + UpdateProgress1(0, '0 %'); + CommitGUIUpdate; + +// DebugWriteListSL(AList); + + if AList.Count = 1 then Fr := 1 else Fr := 100 / (AList.Count - 1); + if AList.Count > 0 then + for i := 0 to AList.Count - 1 do begin + if Cancelled then begin + FCancelMessage := LANGUserCancelled; + FShowCancelMessage := True; + Break; + end; + // Process chmod + if not HandleChown(AList[i]) then Break; + UpdateProgress1(i, Format('%d%%', [Round(Fr * i)])); + UpdateCaption1(PDataItemSL(AList[i])^.DataItem^.FDisplayName); + CommitGUIUpdate; + end; + + // Free the objects + if AList.Count > 0 then + for i := AList.Count - 1 downto 0 do FreeDataItem(PDataItemSL(AList[i])); + AList.Clear; + AList.Free; + end; + SenderThread.FDoneThread := True; +end; + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +procedure DummyThreadWorker(SenderThread: TWorkerThread); +var i: integer; +begin + DebugMsg(['(II) DummyThreadWorker: begin']); + with SenderThread do begin + SetProgress1Params(100); + SetProgress2Params(100); + UpdateProgress1(0, '0 %'); + UpdateProgress2(100, '100 %'); + CommitGUIUpdate; + for i := 1 to 100 do begin + Sleep(100); + DebugMsg([' (II) DummyThreadWorker: done ', i, ' / 100']); + UpdateProgress1(i, Format('%d%%', [i])); + UpdateCaption1(Format('Test %d test', [i])); + UpdateProgress2(101-i, Format('%d%%', [101-i])); + UpdateCaption2(Format('Test %d test', [101-i])); + CommitGUIUpdate; + if Cancelled then Break; + end; + end; + DebugMsg(['(II) DummyThreadWorker: finish']); + SenderThread.FDoneThread := True; +end; + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +constructor TOpenDirThread.Create; +begin + inherited Create(True); + FreeOnTerminate := False; + Finished := False; + CancelIt := False; + ChDirResult := 0; + ListingResult := 0; + VFSOpenResult := 0; + RunningTime := 0; +end; + +destructor TOpenDirThread.Destroy; +begin + inherited Destroy; +end; + +(********************************************************************************************************************************) +function TOpenDirThread.ChangeDir(Engine: TPanelEngine; Path: string; var SelItem: string; const AutoFallBack: boolean): integer; + + procedure GoUp(var NewPath: string); + var x: integer; + begin + if NewPath = PathDelim then Exit; + NewPath := ExcludeTrailingPathDelimiter(NewPath); + if Length(Trim(NewPath)) < 2 then Exit; + x := PosEnd(PathDelim, NewPath); + SelItem := Copy(NewPath, x + 1, Length(NewPath) - x); + NewPath := Copy(NewPath, 1, x); + NewPath := IncludeTrailingPathDelimiter(NewPath); + end; + +var APath: string; + Error : integer; +begin + try + APath := Engine.Path; + if Path = '..' then GoUp(APath) + else begin + APath := IncludeTrailingPathDelimiter(APath); + Path := IncludeTrailingPathDelimiter(Path); + if (Length(Path) > 0) and (Path[1] <> '/') + then APath := APath + Path + else APath := Path; + APath := IncludeTrailingPathDelimiter(APath); + end; + + // AutoFallback loop + if Engine is TVFSEngine + then Error := (Engine as TVFSEngine).ChangeDirEx(APath, @vfs_ask_question_callback, @vfs_ask_password_callback, nil, Self) + else Error := Engine.ChangeDir(APath); + + while AutoFallback and (Error <> 0) and (APath <> '/') do begin + GoUp(APath); + if Engine is TVFSEngine + then Error := (Engine as TVFSEngine).ChangeDirEx(APath, @vfs_ask_question_callback, @vfs_ask_password_callback, nil, Self) + else Error := Engine.ChangeDir(APath); + end; + // Going on... + if Error <> 0 then begin + Result := Error; + DebugMsg(['*** UCore.ChangeDir: error during Engine.ChangeDir: ', GetErrorString(Error)]); + Exit; + end; + Engine.Path := APath; + Result := 0; + except + on E: Exception do begin + DebugMsg(['*** Exception raised in UCore.ChangeDir (', E.ClassName, '): ', E.Message]); + Result := 1; + end; + end; +end; + +procedure TOpenDirThread.Execute; +var tt: TDateTime; + xEngine: TVFSEngine; +begin + PrepareExecute; + try + tt := Now; + try + if APlugin <> nil then begin + xEngine := TVFSEngine.Create(APlugin); + xEngine.ParentEngine := AEngine; + AEngine.LastHighlightItem := AHighlightItem; + xEngine.SavePath := AEngine.Path; + // AEngine must be set here since VFSOpenEx callbacks will reference it + AEngine := xEngine; + VFSOpenResult := (AEngine as TVFSEngine).VFSOpenEx(AFullPath, @vfs_ask_question_callback, @vfs_ask_password_callback, nil, Self); + end else VFSOpenResult := 0; + + if (VFSOpenResult = 0) and (not CancelIt) then begin + ChDirResult := ChangeDir(AEngine, APath, ASelItem, AAutoFallBack); + if (ChDirResult = 0) and (not CancelIt) then + ListingResult := AEngine.GetListing(ADirList, AEngine.GetPath, ConfShowDotFiles, True, False); + end; + except + on E: Exception do DebugMsg(['*** Exception raised in TOpenDirThread.Execute (', E.ClassName, '): ', E.Message]); + end; + RunningTime := MilliSecondsBetween(tt, Now); + finally + Finished := True; + end; +end; + + +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +(********************************************************************************************************************************) +constructor TOpenConnectionThread.Create; +begin + inherited Create(True); + FreeOnTerminate := False; + Finished := False; + OpenResult := False; +end; + +destructor TOpenConnectionThread.Destroy; +begin + inherited Destroy; +end; + +procedure TOpenConnectionThread.Execute; +begin + PrepareExecute; + try + OpenResult := (AEngine as TVFSEngine).VFSOpenURI(URI, @vfs_ask_question_callback, @vfs_ask_password_callback, nil, Self); + finally + Finished := True; + end; +end; + + +end. |
