(* Tux Commander - UEngines - Basic engines (abstract, local) Copyright (C) 2007 Tomas Bzatek 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 UEngines; interface uses glib2, gdk2, Classes, ULibc; const omRead = 0; omWrite = 1; omAppend = 2; ConfDefaultDirCreationMask = 755; type PDataItem = ^TDataItem; TDataItem = record FName: PChar; // ANSI FDisplayName: PChar; // always-valid UTF-8 LnkPointTo: PChar; // ANSI ColumnData: array[0..9] of PChar; Size: cuLongLong; PackedSize: Int64; inode_no: cuLong; UpDir: boolean; Mode, UID, GID: cuLong; IsDir, IsLnk, IsBlk, IsChr, IsFIFO, IsSock: boolean; Selected, IsDotFile, IsExecutable: boolean; atime, mtime, ctime: time_t; Icon: Pointer; ItemColor: PGdkColor; end; PDataItemSL = ^TDataItemSL; TDataItemSL = record DataItem: PDataItem; Stage1: boolean; Level: word; ADestination: PChar; ForceMove, IsOnRO: boolean; end; // Progress callback, return False to break the copy process // If an Error is set, returning True means to ignore error (don't delete broken file if possible) // If an Error is set, BytesDone may contain random value // Do not free Error, it belongs to the copy operation TEngineProgressFunc = function (Sender: Pointer; BytesDone: Int64; Error: PGError): boolean; cdecl; // * TODO: file handle TEngineFileDes = pointer; TPanelEngine = class private BreakProcessingType: integer; public ParentEngine: TPanelEngine; LastHighlightItem, SavePath: string; constructor Create; destructor Destroy; override; function GetListing(List: TList; const APath: string; AddDotFiles, FollowSymlinks, AddFullPath: boolean; Error: PPGError): boolean; virtual; abstract; function GetFileInfo(const APath: string; FollowSymlinks, AddFullPath: boolean; Error: PPGError): PDataItem; virtual; abstract; function ChangeDir(const NewPath: string; Error: PPGError): boolean; virtual; abstract; function GetPath: string; virtual; abstract; procedure SetPath(Value: string); virtual; abstract; function GetDirSize(const APath: string): Int64; virtual; abstract; // Returns size or 0 if fails procedure BreakProcessing(ProcessingKind: integer); virtual; abstract; // 1 = GetDirSize, 2 = GetListing function FileExists(const FileName: string; FollowSymlinks: boolean): boolean; virtual; abstract; function DirectoryExists(const FileName: string; FollowSymlinks: boolean): boolean; virtual; abstract; procedure GetFileSystemInfo(const APath: string; var FSSize, FSFree: Int64; var FSName: string); virtual; abstract; function IsOnROMedium(const FileName: string): boolean; virtual; abstract; function FileCanRun(const FileName: string): boolean; virtual; abstract; // Operations function MakeDir(const NewDir: string; Error: PPGError): boolean; virtual; abstract; function Remove(const APath: string; Error: PPGError): boolean; virtual; abstract; function MakeSymLink(const NewFileName, PointTo: string; Error: PPGError): boolean; virtual; abstract; function Chmod(const FileName: string; Mode: cuLong; Error: PPGError): boolean; virtual; abstract; function Chown(const FileName: string; UID, GID: cuLong; Error: PPGError): boolean; virtual; abstract; function RenameFile(const SourceFile, DestFile: string; Error: PPGError): boolean; virtual; abstract; function ChangeTimes(const APath: string; mtime, atime: time_t; Error: PPGError): boolean; virtual; abstract; // Copy-related routines function GetBlockSize: guint32; virtual; abstract; procedure SetBlockSize(Value: guint32); virtual; abstract; function CopyFileIn(const SourceFile, DestFile: string; Append: boolean; ProgressFunc: TEngineProgressFunc; Sender: Pointer): boolean; virtual; abstract; // returns True if file is successfully copied function CopyFileOut(const SourceFile, DestFile: string; Append: boolean; ProgressFunc: TEngineProgressFunc; Sender: Pointer): boolean; virtual; abstract; // returns True if file is successfully copied function IsOnSameFS(const Path1, Path2: string; FollowSymlinks: boolean): boolean; virtual; abstract; function TwoSameFiles(const Path1, Path2: string; FollowSymlinks: boolean): boolean; virtual; abstract; // Separate file read/write routines, not supported on most backends function OpenFile(const APath: string; Mode: integer; Error: PPGError): TEngineFileDes; virtual; abstract; // Returns filedescriptor function ReadFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; ABlockSize: integer; Error: PPGError): integer; virtual; abstract; // Returns number of bytes read function WriteFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; BytesCount: integer; Error: PPGError): integer; virtual; abstract; // Returns number of bytes written function CloseFile(const FileDescriptor: TEngineFileDes; Error: PPGError): boolean; virtual; abstract; function FileSeek(const FileDescriptor: TEngineFileDes; const AbsoluteOffset: Int64; Error: PPGError): Int64; virtual; abstract; published property Path: string read GetPath write SetPath; property BlockSize: guint32 read GetBlockSize write SetBlockSize; end; TLocalTreeEngine = class(TPanelEngine) private FPath: string; FBlockSize: guint32; function CreateFakeFileInfo(const APath: string; AddFullPath: boolean): PDataItem; public constructor Create; destructor Destroy; override; function GetListing(List: TList; const APath: string; AddDotFiles, FollowSymlinks, AddFullPath: boolean; Error: PPGError): boolean; override; function GetFileInfo(const APath: string; FollowSymlinks, AddFullPath: boolean; Error: PPGError): PDataItem; override; function ChangeDir(const NewPath: string; Error: PPGError): boolean; override; function GetPath: string; override; procedure SetPath(Value: string); override; function GetDirSize(const APath: string): Int64; override; procedure BreakProcessing(ProcessingKind: integer); override; function FileExists(const FileName: string; FollowSymlinks: boolean): boolean; override; function DirectoryExists(const FileName: string; FollowSymlinks: boolean): boolean; override; procedure GetFileSystemInfo(const APath: string; var FSSize, FSFree: Int64; var FSName: string); override; function IsOnROMedium(const FileName: string): boolean; override; function FileCanRun(const FileName: string): boolean; override; function MakeDir(const NewDir: string; Error: PPGError): boolean; override; function Remove(const APath: string; Error: PPGError): boolean; override; function MakeSymLink(const NewFileName, PointTo: string; Error: PPGError): boolean; override; function Chmod(const FileName: string; Mode: cuLong; Error: PPGError): boolean; override; function Chown(const FileName: string; UID, GID: cuLong; Error: PPGError): boolean; override; function RenameFile(const SourceFile, DestFile: string; Error: PPGError): boolean; override; function ChangeTimes(const APath: string; mtime, atime: time_t; Error: PPGError): boolean; override; function GetBlockSize: guint32; override; procedure SetBlockSize(Value: guint32); override; function CopyFileIn(const SourceFile, DestFile: string; Append: boolean; ProgressFunc: TEngineProgressFunc; Sender: Pointer): boolean; override; function CopyFileOut(const SourceFile, DestFile: string; Append: boolean; ProgressFunc: TEngineProgressFunc; Sender: Pointer): boolean; override; function IsOnSameFS(const Path1, Path2: string; FollowSymlinks: boolean): boolean; override; function TwoSameFiles(const Path1, Path2: string; FollowSymlinks: boolean): boolean; override; function OpenFile(const APath: string; Mode: integer; Error: PPGError): TEngineFileDes; override; function ReadFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; ABlockSize: integer; Error: PPGError): integer; override; function WriteFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; BytesCount: integer; Error: PPGError): integer; override; function CloseFile(const FileDescriptor: TEngineFileDes; Error: PPGError): boolean; override; function FileSeek(const FileDescriptor: TEngineFileDes; const AbsoluteOffset: Int64; Error: PPGError): Int64; override; published property Path; property BlockSize; end; procedure FreeDataItem(DataItem: PDataItemSL); overload; procedure FreeDataItem(DataItem: PDataItem); overload; function DuplicateDataItem(DataItem: PDataItem): PDataItem; overload; function DuplicateDataItem(DataItem: PDataItemSL): PDataItemSL; overload; implementation uses SysUtils, UCoreUtils, UGnome, UError; (********************************************************************************************************************************) constructor TPanelEngine.Create; begin inherited Create; BreakProcessingType := 0; ParentEngine := nil; // By default it is a top-level engine (local) LastHighlightItem := ''; end; destructor TPanelEngine.Destroy; begin inherited Destroy; end; (********************************************************************************************************************************) constructor TLocalTreeEngine.Create; begin inherited Create; FPath := '/'; FBlockSize := 65536; end; destructor TLocalTreeEngine.Destroy; begin inherited Destroy; end; function TLocalTreeEngine.GetPath: string; begin Result := FPath; end; procedure TLocalTreeEngine.SetPath(Value: string); begin if Value <> FPath then begin FPath := Value; end; end; function TLocalTreeEngine.GetBlockSize: guint32; begin Result := FBlockSize; end; procedure TLocalTreeEngine.SetBlockSize(Value: guint32); begin if Value <> FBlockSize then begin FBlockSize := Value; end; end; function TLocalTreeEngine.GetListing(List: TList; const APath: string; AddDotFiles, FollowSymlinks, AddFullPath: boolean; Error: PPGError): boolean; var Item: PDataItem; Handle: PDIR; DirEnt: PDirent64; Buf: PChar; saved_errno: integer; FError: PGError; begin Result := False; try if libc_chdir(PChar(APath)) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error changing directory to ''''%s'''': %s', StrToUTF8(PChar(APath)), g_strerror(saved_errno)); DebugMsg(['*** TLocalTreeEngine.GetListing(APath=', APath, '): chdir error: ', strerror(saved_errno)]); Exit; end; Handle := opendir(PChar(APath)); if Handle = nil then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error opening directory ''''%s'''': %s', StrToUTF8(PChar(APath)), g_strerror(saved_errno)); DebugMsg(['*** TLocalTreeEngine.GetListing(APath=', APath, '): opendir() handle == NULL: ', strerror(errno)]); Exit; end; FError := nil; repeat DirEnt := readdir64(Handle); if (DirEnt <> nil) and (DirEnt^.d_name[0] <> #0) then begin Buf := PChar(@DirEnt^.d_name[0]); if (Buf <> '.') and (Buf <> '..') and (strlen(Buf) > 0) and (AddDotFiles or (Buf[0] <> '.')) then begin Item := GetFileInfo(IncludeTrailingPathDelimiter(APath) + string(Buf), FollowSymlinks, AddFullPath, nil); // Ignore failed stats, let's display just a filename and bogus info if Item = nil then Item := CreateFakeFileInfo(IncludeTrailingPathDelimiter(APath) + string(Buf), AddFullPath); List.Add(Item); end; end; until (DirEnt = nil) or (FError <> nil); closedir(Handle); if FError <> nil then g_propagate_error(Error, FError) else Result := True; except on E: Exception do begin Result := False; DebugMsg(['*** TLocalTreeEngine.GetListing(APath=', APath, ') -Exception: ', E.Message]); Exit; end; end; end; function TLocalTreeEngine.GetFileInfo(const APath: string; FollowSymlinks, AddFullPath: boolean; Error: PPGError): PDataItem; var Item: PDataItem; StatBuf: Pstat64; LnkBuf: array[0..65535] of char; i: integer; saved_errno: integer; begin StatBuf := malloc(sizeof(Tstat64)); memset(StatBuf, 0, sizeof(Tstat64)); if lstat64(PChar(APath), StatBuf) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error getting file info for ''''%s'''': %s', StrToUTF8(PChar(APath)), g_strerror(saved_errno)); DebugMsg(['*** TLocalTreeEngine.GetFileInfo(APath=', APath, '): Error reading file via lstat64: ', strerror(saved_errno)]); libc_free(StatBuf); Result := nil; Exit; end; Item := malloc(sizeof(TDataItem)); memset(Item, 0, sizeof(TDataItem)); Item^.UpDir := False; Item^.LnkPointTo := nil; Item^.Selected := False; if AddFullPath then Item^.FName := strdup(PChar(APath)) else Item^.FName := strdup(PChar(ExtractFileName(APath))); Item^.FDisplayName := StrToUTF8(Item^.FName); Item^.Mode := StatBuf^.st_mode; Item^.IsDotFile := (Length(ExtractFileName(APath)) > 0) and (ExtractFileName(APath)[1] = '.'); Item^.IsExecutable := (StatBuf^.st_mode and S_IXUSR) = S_IXUSR; Item^.IsDir := __S_ISTYPE(StatBuf^.st_mode, __S_IFDIR); Item^.IsLnk := __S_ISTYPE(StatBuf^.st_mode, __S_IFLNK); Item^.IsBlk := __S_ISTYPE(StatBuf^.st_mode, __S_IFBLK); Item^.IsChr := __S_ISTYPE(StatBuf^.st_mode, __S_IFCHR); Item^.IsFIFO := __S_ISTYPE(StatBuf^.st_mode, __S_IFIFO); Item^.IsSock := __S_ISTYPE(StatBuf^.st_mode, __S_IFSOCK); Item^.mtime := StatBuf^.st_mtime; Item^.atime := StatBuf^.st_atime; Item^.ctime := StatBuf^.st_ctime; Item^.UID := StatBuf^.st_uid; Item^.GID := StatBuf^.st_gid; Item^.Size := StatBuf^.st_size; Item^.PackedSize := -1; Item^.inode_no := StatBuf^.st_ino; libc_free(StatBuf); if Item^.IsLnk then begin memset(@LnkBuf[0], 0, Length(LnkBuf)); i := readlink(PChar(APath), LnkBuf, sizeof(LnkBuf)); if i >= 0 then Item^.LnkPointTo := g_strdup(@LnkBuf[0]); if FollowSymlinks then begin StatBuf := malloc(sizeof(Tstat64)); memset(StatBuf, 0, sizeof(Tstat64)); if stat64(PChar(APath), StatBuf) = 0 then begin Item^.IsDir := __S_ISTYPE(StatBuf^.st_mode, __S_IFDIR); Item^.Mode := StatBuf^.st_mode; Item^.Size := StatBuf^.st_size; end; libc_free(StatBuf); end; end; Result := Item; end; function TLocalTreeEngine.CreateFakeFileInfo(const APath: string; AddFullPath: boolean): PDataItem; var Item: PDataItem; begin Item := malloc(sizeof(TDataItem)); memset(Item, 0, sizeof(TDataItem)); Item^.UpDir := False; Item^.LnkPointTo := nil; Item^.Selected := False; if AddFullPath then Item^.FName := strdup(PChar(APath)) else Item^.FName := strdup(PChar(ExtractFileName(APath))); Item^.FDisplayName := StrToUTF8(Item^.FName); Item^.Mode := 0; Item^.IsDotFile := (Length(ExtractFileName(APath)) > 0) and (ExtractFileName(APath)[1] = '.'); Item^.IsExecutable := False; Item^.IsDir := False; Item^.IsLnk := False; Item^.IsBlk := False; Item^.IsChr := False; Item^.IsFIFO := False; Item^.IsSock := False; Item^.mtime := 0; Item^.atime := 0; Item^.ctime := 0; Item^.UID := geteuid; Item^.GID := getegid; Item^.Size := 0; Item^.PackedSize := -1; Item^.inode_no := 0; Result := Item; end; function TLocalTreeEngine.ChangeDir(const NewPath: string; Error: PPGError): boolean; var APath: string; Handle : PDIR; saved_errno: integer; begin Result := False; try APath := IncludeTrailingPathDelimiter(NewPath); if libc_chdir(PChar(APath)) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error changing directory to ''''%s'''': %s', StrToUTF8(PChar(NewPath)), g_strerror(saved_errno)); Exit; end; Handle := opendir(PChar(APath)); if Handle = nil then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error changing directory to ''''%s'''': %s', StrToUTF8(PChar(NewPath)), g_strerror(saved_errno)); Exit; end; { if not Assigned(readdir(Handle)) then begin Result := ERRNoAccess; Exit; end; } if closedir(Handle) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error changing directory to ''''%s'''': %s', StrToUTF8(PChar(NewPath)), g_strerror(saved_errno)); Exit; end; Result := True; except on E: Exception do begin Result := False; DebugMsg(['*** TLocalTreeEngine.ChangeDir(APath=', APath, ') -Exception: ', E.Message]); Exit; end; end; end; (********************************************************************************************************************************) function TLocalTreeEngine.GetDirSize(const APath: string): Int64; function InternalGetDirSize(APath: string): Int64; var Handle: PDIR; DirEnt: PDirent64; StatBuf: Pstat64; Buf: PChar; begin Result := 0; try if BreakProcessingType = 1 then Exit; APath := IncludeTrailingPathDelimiter(APath); if libc_chdir(PChar(APath)) <> 0 then Exit; Handle := opendir(PChar(APath)); if not Assigned(Handle) then Exit; repeat DirEnt := readdir64(Handle); if DirEnt <> nil then begin Buf := PChar(@DirEnt^.d_name[0]); if (strlen(Buf) > 0) and (Buf <> '.') and (Buf <> '..') then begin StatBuf := malloc(sizeof(Tstat64)); memset(StatBuf, 0, sizeof(Tstat64)); if lstat64(Buf, StatBuf) = 0 then begin if __S_ISTYPE(StatBuf^.st_mode, __S_IFDIR) then begin Result := Result + InternalGetDirSize(APath + string(Buf)); libc_chdir(PChar(APath)); end else Result := Result + StatBuf^.st_size; end; libc_free(StatBuf); end; end; until DirEnt = nil; closedir(Handle); except on E: Exception do begin Result := 0; DebugMsg(['*** TLocalTreeEngine.GetDirSize(APath=', APath, ') -Exception: ', E.Message]); end; end; end; begin try BreakProcessingType := 0; Result := InternalGetDirSize(APath); finally BreakProcessingType := 0; end; end; (********************************************************************************************************************************) function TLocalTreeEngine.MakeDir(const NewDir: string; Error: PPGError): boolean; var saved_errno: integer; begin Result := False; if __mkdir(PChar(NewDir), OctalToAttr(ConfDefaultDirCreationMask)) <> 0 then begin if Self.DirectoryExists(NewDir, False) or (@g_mkdir_with_parents = nil) or (g_mkdir_with_parents(PChar(NewDir), OctalToAttr(ConfDefaultDirCreationMask)) <> 0) then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error creating directory ''''%s'''': %s', StrToUTF8(PChar(NewDir)), g_strerror(saved_errno)); end else Result := True; end else Result := True; end; (********************************************************************************************************************************) function TLocalTreeEngine.Remove(const APath: string; Error: PPGError): boolean; var saved_errno: integer; begin Result := False; if libc_remove(PChar(ExcludeTrailingPathDelimiter(APath))) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error deleting ''''%s'''': %s', StrToUTF8(PChar(APath)), g_strerror(saved_errno)); end else Result := True; end; (********************************************************************************************************************************) function TLocalTreeEngine.MakeSymLink(const NewFileName, PointTo: string; Error: PPGError): boolean; var saved_errno: integer; begin Result := False; if symlink(PChar(PointTo), PChar(NewFileName)) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error creating symlink ''''%s'''': %s', StrToUTF8(PChar(NewFileName)), g_strerror(saved_errno)); end else Result := True; end; (********************************************************************************************************************************) function TLocalTreeEngine.Chmod(const FileName: string; Mode: cuLong; Error: PPGError): boolean; var saved_errno: integer; begin Result := False; if libc_chmod(PChar(FileName), Mode) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error during chmod of ''''%s'''': %s', StrToUTF8(PChar(FileName)), g_strerror(saved_errno)); end else Result := True; end; (********************************************************************************************************************************) function TLocalTreeEngine.Chown(const FileName: string; UID, GID: cuLong; Error: PPGError): boolean; var saved_errno: integer; begin Result := False; if libc_chown(PChar(FileName), UID, GID) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error during chown of ''''%s'''': %s', StrToUTF8(PChar(FileName)), g_strerror(saved_errno)); end else Result := True; end; (********************************************************************************************************************************) function TLocalTreeEngine.RenameFile(const SourceFile, DestFile: string; Error: PPGError): boolean; var saved_errno: integer; begin Result := False; if libc_rename(PChar(SourceFile), PChar(DestFile)) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error renaming file ''''%s'''': %s', StrToUTF8(PChar(SourceFile)), g_strerror(saved_errno)); end else Result := True; end; (********************************************************************************************************************************) function TLocalTreeEngine.ChangeTimes(const APath: string; mtime, atime: time_t; Error: PPGError): boolean; var timebuf: Putimbuf; saved_errno: integer; begin Result := False; try timebuf := malloc(sizeof(Tutimbuf)); memset(timebuf, 0, sizeof(Tutimbuf)); timebuf^.actime := atime; timebuf^.modtime := mtime; if utime(PChar(APath), timebuf) <> 0 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error while changing timestamps of ''''%s'''': %s', StrToUTF8(PChar(APath)), g_strerror(saved_errno)); end else Result := True; libc_free(timebuf); except on E: Exception do DebugMsg(['*** Exception raised in TLocalTreeEngine.ChangeTimes(APath=', APath, '): (', E.ClassName, '): ', E.Message]); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.CopyFileIn(const SourceFile, DestFile: string; Append: boolean; ProgressFunc: TEngineProgressFunc; Sender: Pointer): boolean; begin Result := CopyFileOut(SourceFile, DestFile, Append, ProgressFunc, Sender); end; function TLocalTreeEngine.CopyFileOut(const SourceFile, DestFile: string; Append: boolean; ProgressFunc: TEngineProgressFunc; Sender: Pointer): boolean; var fsrc, fdest: PFILE; Buffer: Pointer; BytesDone, BytesRead, BytesWritten: Int64; Ignore: boolean; saved_errno: integer; Error: PGError; begin Result := False; Error := nil; try // Open source file for reading fsrc := fopen64(PChar(SourceFile), 'r'); if fsrc = nil then begin if @ProgressFunc <> nil then begin saved_errno := errno; g_set_error(@Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error opening source file ''''%s'''': %s', StrToUTF8(PChar(SourceFile)), g_strerror(saved_errno)); ProgressFunc(Sender, 0, Error); g_error_free(Error); end; Exit; end; // Open target file for writing/appending if Append then fdest := fopen64(PChar(DestFile), 'a') else fdest := fopen64(PChar(DestFile), 'w'); if fsrc = nil then begin if @ProgressFunc <> nil then begin saved_errno := errno; g_set_error(@Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error opening target file ''''%s'''': %s', StrToUTF8(PChar(DestFile)), g_strerror(saved_errno)); ProgressFunc(Sender, 0, Error); g_error_free(Error); end; fclose(fsrc); Exit; end; BytesDone := 0; Buffer := malloc(FBlockSize); while feof(fsrc) = 0 do begin Error := nil; // Read block BytesRead := fread(Buffer, 1, FBlockSize, fsrc); if (BytesRead < FBlockSize) and (feof(fsrc) = 0) then begin Ignore := False; if @ProgressFunc <> nil then begin saved_errno := errno; g_set_error(@Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error reading from source file ''''%s'''': %s', StrToUTF8(PChar(SourceFile)), g_strerror(saved_errno)); Ignore := ProgressFunc(Sender, BytesDone + BytesRead, Error); g_error_free(Error); end; if Ignore then Continue else Break; end; // Write block BytesWritten := fwrite(Buffer, 1, BytesRead, fdest); if BytesWritten < BytesRead then begin if @ProgressFunc <> nil then begin saved_errno := ferror(fdest); g_set_error(@Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error writing to target file ''''%s'''': %s', StrToUTF8(PChar(DestFile)), g_strerror(saved_errno)); ProgressFunc(Sender, BytesDone + BytesWritten, Error); g_error_free(Error); end; Break; // We cannot ignore write errors end; BytesDone := BytesDone + BytesRead; if (@ProgressFunc <> nil) and (not ProgressFunc(Sender, BytesDone, nil)) then Break; end; Result := feof(fsrc) <> 0; libc_free(Buffer); if fclose(fdest) <> 0 then begin fclose(fsrc); Result := False; if @ProgressFunc <> nil then begin Error := nil; saved_errno := errno; g_set_error(@Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error closing target file ''''%s'''': %s', StrToUTF8(PChar(DestFile)), g_strerror(saved_errno)); Result := ProgressFunc(Sender, BytesDone, Error); g_error_free(Error); end; Exit; end; if fclose(fsrc) <> 0 then begin Result := False; if @ProgressFunc <> nil then begin Error := nil; saved_errno := errno; g_set_error(@Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error closing source file ''''%s'''': %s', StrToUTF8(PChar(SourceFile)), g_strerror(saved_errno)); Result := ProgressFunc(Sender, BytesDone, Error); g_error_free(Error); end; Exit; end; except on E: Exception do DebugMsg(['*** Exception raised in TLocalTreeEngine.CopyFile(Sender=', Sender, ', SourceFile=', SourceFile, ', DestFile=', DestFile, '): (', E.ClassName, '): ', E.Message]); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.FileExists(const FileName: string; FollowSymlinks: boolean): boolean; var st: Pstat64; begin st := malloc(sizeof(Tstat64)); memset(st, 0, sizeof(Tstat64)); if not FollowSymlinks then Result := lstat64(PChar(FileName), st) = 0 else Result := stat64(PChar(FileName), st) = 0; libc_free(st); end; (********************************************************************************************************************************) function TLocalTreeEngine.DirectoryExists(const FileName: string; FollowSymlinks: boolean): boolean; var st: Pstat64; begin st := malloc(sizeof(Tstat64)); memset(st, 0, sizeof(Tstat64)); if not FollowSymlinks then Result := lstat64(PChar(FileName), st) = 0 else Result := stat64(PChar(FileName), st) = 0; Result := Result and S_ISDIR(st^.st_mode); libc_free(st); end; (********************************************************************************************************************************) procedure TLocalTreeEngine.BreakProcessing(ProcessingKind: integer); begin BreakProcessingType := ProcessingKind; end; (********************************************************************************************************************************) function TLocalTreeEngine.IsOnSameFS(const Path1, Path2: string; FollowSymlinks: boolean): boolean; var FStat1, FStat2: Pstat64; Res1, Res2: integer; begin // DebugMsg(['** TLocalTreeEngine.IsOnSameFS("', Path1, '", "', Path2, '")']); Result := False; // Default fallback result (forces copy + delete) FStat1 := malloc(sizeof(Tstat64)); FStat2 := malloc(sizeof(Tstat64)); memset(FStat1, 0, sizeof(Tstat64)); memset(FStat2, 0, sizeof(Tstat64)); if FollowSymlinks then Res1 := stat64(PChar(Path1), FStat1) else Res1 := lstat64(PChar(Path1), FStat1); if Res1 <> 0 then DebugMsg(['** TLocalTreeEngine.IsOnSameFS: stat(', Path1, ') error: ', strerror(errno)]); if FollowSymlinks then Res2 := stat64(PChar(Path2), FStat2) else Res2 := lstat64(PChar(Path2), FStat2); if Res2 <> 0 then DebugMsg(['** TLocalTreeEngine.IsOnSameFS: stat(', Path2, ') error: ', strerror(errno)]); if (Res1 = 0) and (Res2 = 0) then Result := FStat1^.st_dev = FStat2^.st_dev; libc_free(FStat1); libc_free(FStat2); // DebugMsg(['** TLocalTreeEngine.IsOnSameFS("', Path1, '", "', Path2, '") Result = ', Result]); end; (********************************************************************************************************************************) function TLocalTreeEngine.TwoSameFiles(const Path1, Path2: string; FollowSymlinks: boolean): boolean; var FStat1, FStat2: Pstat64; Res1, Res2: integer; begin Result := False; FStat1 := malloc(sizeof(Tstat64)); FStat2 := malloc(sizeof(Tstat64)); memset(FStat1, 0, sizeof(Tstat64)); memset(FStat2, 0, sizeof(Tstat64)); if FollowSymlinks then Res1 := stat64(PChar(Path1), FStat1) else Res1 := lstat64(PChar(Path1), FStat1); if Res1 <> 0 then DebugMsg(['** TLocalTreeEngine.TwoSameFiles: stat(', Path1, ') error: ', strerror(errno)]); if FollowSymlinks then Res2 := stat64(PChar(Path2), FStat2) else Res2 := lstat64(PChar(Path2), FStat2); if Res2 <> 0 then DebugMsg(['** TLocalTreeEngine.TwoSameFiles: stat(', Path2, ') error: ', strerror(errno)]); if (Res1 = 0) and (Res2 = 0) then Result := FStat1^.st_ino = FStat2^.st_ino; libc_free(FStat1); libc_free(FStat2); end; (********************************************************************************************************************************) procedure TLocalTreeEngine.GetFileSystemInfo(const APath: string; var FSSize, FSFree: Int64; var FSName: string); var Stat: Pstatfs64; fd: PFILE; mntent: Pmntent; mntdev: PChar; FoundLength: integer; Buffer: array[0..31] of char; begin FSSize := -1; FSFree := -1; FSName := ''; try Stat := malloc(sizeof(Tstatfs64)); memset(Stat, 0, sizeof(Tstatfs64)); if statfs64(PChar(APath), Stat) <> 0 then Exit; FSSize := Stat^.f_bsize * Stat^.f_blocks; FSFree := Stat^.f_bsize * Stat^.f_bfree; fd := setmntent(_PATH_MOUNTED, 'r'); if fd = nil then Exit; // Get mount name FoundLength := 0; mntdev := nil; mntent := getmntent(fd); while mntent <> nil do begin if (Pos(mntent^.mnt_dir, APath) = 1) and (Length(String(mntent^.mnt_dir)) > FoundLength) then begin FoundLength := Length(String(mntent^.mnt_dir)); FSName := String(mntent^.mnt_dir); mntdev := mntent^.mnt_fsname; end; mntent := getmntent(fd); end; endmntent(fd); // if it is CD-ROM, read ISO9660 label if Stat^.f_type = $9660 then begin { ISOFS_SUPER_MAGIC } if Assigned(mntdev) and (mntdev <> '') then begin fd := fopen(mntdev, 'r'); if fd <> nil then begin if fseek(fd, 32808, SEEK_SET) = 0 then if fread(@Buffer[0], 1, 32, fd) <> 0 then FSName := Trim(String(Buffer)); fclose(fd); end; end; end; libc_free(Stat); except on E: Exception do DebugMsg(['*** Exception raised in TLocalTreeEngine.GetFileSystemInfo(APath=', APath, '): (', E.ClassName, '): ', E.Message]); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.OpenFile(const APath: string; Mode: integer; Error: PPGError): TEngineFileDes; var m: PChar; saved_errno: integer; begin case Mode of omRead: m := 'r'; omWrite: m := 'w'; omAppend: m := 'a'; else m := 'r'; end; Result := fopen64(PChar(APath), m); if Result = nil then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error opening file ''''%s'''': %s', StrToUTF8(PChar(APath)), g_strerror(saved_errno)); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.ReadFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; ABlockSize: integer; Error: PPGError): integer; var saved_errno: integer; begin Result := fread(Buffer, 1, ABlockSize, FileDescriptor); if (Result = 0) and (feof(FileDescriptor) = 0) then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error reading from file: %s', g_strerror(saved_errno)); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.WriteFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; BytesCount: integer; Error: PPGError): integer; var saved_errno: integer; begin Result := fwrite(Buffer, 1, BytesCount, FileDescriptor); if Result < BytesCount then begin saved_errno := ferror(FileDescriptor); g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error writing to file: %s', g_strerror(saved_errno)); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.CloseFile(const FileDescriptor: TEngineFileDes; Error: PPGError): boolean; var saved_errno: integer; begin Result := fclose(FileDescriptor) = 0; if not Result then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error closing file: %s', g_strerror(saved_errno)); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.FileSeek(const FileDescriptor: TEngineFileDes; const AbsoluteOffset: Int64; Error: PPGError): Int64; var saved_errno: integer; begin Result := fseeko64(FileDescriptor, AbsoluteOffset, SEEK_SET); if Result = -1 then begin saved_errno := errno; g_set_error(Error, G_IO_ERROR, gint(g_io_error_from_errno(saved_errno)), 'Error seeking in file: %s', g_strerror(saved_errno)); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.IsOnROMedium(const FileName: string): boolean; var Stat: Pstatfs64; begin Result := False; try Stat := malloc(sizeof(Tstatfs64)); memset(Stat, 0, sizeof(Tstatfs64)); if statfs64(PChar(FileName), Stat) = 0 then Result := (Stat^.f_type = $9660); { ISOFS_SUPER_MAGIC } libc_free(Stat); except on E: Exception do DebugMsg(['*** TLocalTreeEngine.IsOnROMedium(FileName=', FileName, ') -Exception: ', E.Message]); end; end; (********************************************************************************************************************************) function TLocalTreeEngine.FileCanRun(const FileName: string): boolean; begin Result := access(PChar(FileName), R_OK or X_OK) = 0; end; (********************************************************************************************************************************) (********************************************************************************************************************************) procedure FreeDataItem(DataItem: PDataItem); var i : integer; begin try if DataItem <> nil then begin with DataItem^ do begin if FName <> nil then libc_free(FName); if FDisplayName <> nil then libc_free(FDisplayName); if LnkPointTo <> nil then libc_free(LnkPointTo); for i := 0 to Length(ColumnData) - 1 do if ColumnData[i] <> nil then libc_free(ColumnData[i]); end; libc_free(DataItem); end; except on E: Exception do DebugMsg(['*** FreeDataItem: Exception: ', E.Message]); end; end; procedure FreeDataItem(DataItem: PDataItemSL); begin try if DataItem <> nil then begin with DataItem^ do begin if ADestination <> nil then libc_free(ADestination); FreeDataItem(DataItem); end; libc_free(DataItem); end; except on E: Exception do DebugMsg(['*** FreeDataItem: Exception: ', E.Message]); end; end; function DuplicateDataItem(DataItem: PDataItem): PDataItem; var NewDataItem: PDataItem; i: integer; begin NewDataItem := malloc(sizeof(TDataItem)); memcpy(NewDataItem, DataItem, sizeof(TDataItem)); NewDataItem^.FName := g_strdup(DataItem^.FName); NewDataItem^.FDisplayName := g_strdup(DataItem^.FDisplayName); NewDataItem^.LnkPointTo := g_strdup(DataItem^.LnkPointTo); for i := 0 to Length(DataItem^.ColumnData) - 1 do NewDataItem^.ColumnData[i] := g_strdup(DataItem^.ColumnData[i]); Result := NewDataItem; end; function DuplicateDataItem(DataItem: PDataItemSL): PDataItemSL; var NewDataItem: PDataItemSL; begin NewDataItem := malloc(sizeof(TDataItemSL)); memcpy(NewDataItem, DataItem, sizeof(TDataItemSL)); NewDataItem^.ADestination := g_strdup(DataItem^.ADestination); NewDataItem^.DataItem := DuplicateDataItem(DataItem^.DataItem); Result := NewDataItem; end; end.