unit ex2explore;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, Menus, ExtCtrls, Buttons, WinIOCTL, LinuxFS,
  ex2, Options, INode, Directory, Saving, OpenDir, Users, ImgList,
  DropFileListView;

const
   Explore2fsVersion = '1.00 pre 1';

{
   To Do:
      Fix temporary file name problems
      
}

type

  // Each node in the TreeView is represented by one of these TNodeData objects
  TNodeData = class
  public
    INode         : ULONG;
    ParentINode   : ULONG;
    FileName      : String;
    Loaded        : Boolean;
    Partition     : TLinuxPartition;

    mode          : ULONG;
	 uid           : USHORT;	// Owner Uid */
	 gid           : USHORT;	// Owner Uid */
	 size          : ULONG;		// Size in bytes */
	 mtime         : ULONG;		// Modification time */
	 flags         : ULONG;		// File flags */

    Children      : TList;    // This keeps a list of (TNodeData objects for) all the children in the directory

    ModeString    : String;

    constructor Create;
    destructor Destroy; override;

  end;

  TMainForm = class(TForm)
    StatusBar: TStatusBar;
    ToolbarPanel: TPanel;
    SmallImageList: TImageList;
    LargeImageList: TImageList;
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Exit1: TMenuItem;
    LargIconSpeedButton: TSpeedButton;
    SmallIconSpeedButton: TSpeedButton;
    ListSpeedButton: TSpeedButton;
    DetailsSpeedButton: TSpeedButton;
    ComboBox1: TComboBox;
    SpeedButton5: TSpeedButton;
    SpeedButton6: TSpeedButton;
    FilePopupMenu: TPopupMenu;
    Properties1: TMenuItem;
    Edit1: TMenuItem;
    View1: TMenuItem;
    Toolbar1: TMenuItem;
    Statusbar2: TMenuItem;
    N1: TMenuItem;
    LargeIcons1: TMenuItem;
    SmallIcons1: TMenuItem;
    List1: TMenuItem;
    Details1: TMenuItem;
    N2: TMenuItem;
    ArrangeIcons1: TMenuItem;
    by1: TMenuItem;
    byType1: TMenuItem;
    byDate1: TMenuItem;
    bySize1: TMenuItem;
    LineupIcons1: TMenuItem;
    N3: TMenuItem;
    Refresh1: TMenuItem;
    Options1: TMenuItem;
    N4: TMenuItem;
    AutoArrange1: TMenuItem;
    Undo1: TMenuItem;
    N5: TMenuItem;
    Cut1: TMenuItem;
    Copy1: TMenuItem;
    Paste1: TMenuItem;
    N6: TMenuItem;
    SelectAll1: TMenuItem;
    InvertSelection1: TMenuItem;
    Delete1: TMenuItem;
    Rename1: TMenuItem;
    Properties2: TMenuItem;
    N7: TMenuItem;
    NewFolder1: TMenuItem;
    N8: TMenuItem;
    OpenFile1: TMenuItem;
    Ex2OpenDialog: TOpenDialog;
    Save1: TMenuItem;
    SaveDialog1: TSaveDialog;
    Help1: TMenuItem;
    About1: TMenuItem;
    Save2: TMenuItem;
    Bevel1: TBevel;
    Debug1: TMenuItem;
    RescanPartitions1: TMenuItem;
    DirPopupMenu: TPopupMenu;
    MenuItem1: TMenuItem;
    MenuItem2: TMenuItem;
    Close1: TMenuItem;
    Test1: TMenuItem;
    Delete2: TMenuItem;
    Addfile1: TMenuItem;
    ImportFileDialog: TOpenDialog;
    ExportatText1: TMenuItem;
    Rename2: TMenuItem;
    NewFolder2: TMenuItem;
    Delete3: TMenuItem;
    ImportDirectory1: TMenuItem;
    N10: TMenuItem;
    N11: TMenuItem;
    N9: TMenuItem;
    Format1: TMenuItem;
    View2: TMenuItem;
    Create1: TMenuItem;
    CharacterDevice1: TMenuItem;
    BlockDevice1: TMenuItem;
    SymbolicLink1: TMenuItem;
    ImportArchive2: TMenuItem;
    ImportArchiveDialog: TOpenDialog;
    Panel1: TPanel;
    Splitter1: TSplitter;
    TreeView1: TTreeView;
    Panel2: TPanel;
    Panel3: TPanel;
    Panel4: TPanel;
    DebugMemo: TMemo;
    Splitter2: TSplitter;
    OpenDirectory1: TOpenDirectory;
    ListView2: TDropFileListView;
    SyncTimer: TTimer;
    procedure Button2Click(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
    procedure LargIconSpeedButtonClick(Sender: TObject);
    procedure SmallIconSpeedButtonClick(Sender: TObject);
    procedure ListSpeedButtonClick(Sender: TObject);
    procedure DetailsSpeedButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Toolbar1Click(Sender: TObject);
    procedure Statusbar2Click(Sender: TObject);
    procedure by1Click(Sender: TObject);
    procedure SelectAll1Click(Sender: TObject);
    procedure InvertSelection1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure TreeView1Expanding(Sender: TObject; Node: TTreeNode;
      var AllowExpansion: Boolean);
    procedure OpenFile1Click(Sender: TObject);
    procedure Properties2Click(Sender: TObject);
    procedure TreeView1Change(Sender: TObject; Node: TTreeNode);
    procedure Save1Click(Sender: TObject);
    procedure About1Click(Sender: TObject);
    procedure Debug1Click(Sender: TObject);
    procedure Options1Click(Sender: TObject);
    procedure RescanPartitions1Click(Sender: TObject);
    procedure Close1Click(Sender: TObject);
    procedure Delete2Click(Sender: TObject);
    procedure Addfile1Click(Sender: TObject);
    procedure ExportasText1Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure NewFolder1Click(Sender: TObject);
    procedure Rename1Click(Sender: TObject);
    procedure TreeView1Editing(Sender: TObject; Node: TTreeNode;
      var AllowEdit: Boolean);
    procedure ListView2Editing(Sender: TObject; Item: TListItem;
      var AllowEdit: Boolean);
    procedure ListView2Edited(Sender: TObject; Item: TListItem;
      var S: string);
    procedure TreeView1Edited(Sender: TObject; Node: TTreeNode;
      var S: string);
    procedure Delete3Click(Sender: TObject);
    procedure ImportDirectory1Click(Sender: TObject);
    procedure DirPopupMenuPopup(Sender: TObject);
    procedure FilePopupMenuPopup(Sender: TObject);
    procedure SpeedButton5Click(Sender: TObject);
    procedure View2Click(Sender: TObject);
    procedure BlockDevice1Click(Sender: TObject);
    procedure SymbolicLink1Click(Sender: TObject);
    procedure ImportArchive1Click(Sender: TObject);
    procedure TreeView1Click(Sender: TObject);
    procedure TreeView1KeyPress(Sender: TObject; var Key: Char);
    procedure ListView2Compare(Sender: TObject; Item1, Item2: TListItem;
      Data: Integer; var Compare: Integer);
    procedure ListView2ColumnClick(Sender: TObject; Column: TListColumn);
    procedure byType1Click(Sender: TObject);
    procedure byDate1Click(Sender: TObject);
    procedure bySize1Click(Sender: TObject);
    procedure ListView2MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure Refresh1Click(Sender: TObject);
    procedure TreeView1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure TreeView1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ListView2DropFiles(Sender: TObject; Files: TStringList;
      Point: TPoint);
    procedure SyncTimerTimer(Sender: TObject);
  private
    { Private declarations }
    Partitions    : TList;
    Options       : TOptions;
    UserLib       : TUserList;
    ListedNode    : TTreeNode;

    FilesToDrop   : TStringList;
    DropFilesDlg  : TSavingDlg;
    TempDir       : String;


    procedure Debug(S : String; Level : Integer);
    function  ScanForLinux(Partitions : TList): integer;
    procedure AddPartitionToTree(Partition : TLinuxPartition);
    procedure LoadNode(Node : TTreeNode);
    procedure ReListNodeFiles;
    procedure ListNodeFiles(Node : TTreeNode);
    procedure PruneNode(Node : TTreeNode);
    procedure LoadUserLib;
    function  GetTempDir : String;
    procedure BuildSaveDirectory(Node : TTreeNode; DirName : String); // Used for d'n'd

  public
    { Public declarations }
    OSis95 : Boolean;

    procedure SaveFile(Data : TNodeData; FileName : String; Dlg : TSavingDlg; XLate : Boolean);
    procedure SaveDirectory(Node : TTreeNode; Directory : String);

    procedure FixDisplay;
    function CreateFileGroupDescriptor : HGLOBAL;
    function CreateFileContents(Index : Integer) : HGLOBAL;
//    function CreateFileContents2(Index : Integer) : String;

  end;

const
{
   Icon codes:
      0  Disk
      1  Directory
      2  File (MS)
      3  Symbolic link
      4  Socket
      5  FIFO
      6  File (Linux)
      7  CharDev
      8  BlockDev
}
   ICDisk      = 0;
   icDirectory = 1;
   icFile      = 6;
   icLink      = 3;
   icSocket    = 4;
   icFIFO      = 5;
   icChar      = 7;
   icBlock     = 8;


   DebugOff    = 0; // always displayed
   DebugLow    = 1; // only if Low or higher
   DebugMedium = 2;
   DebugHigh   = 3;

var
  MainForm     : TMainForm;

  CreateFlags  : Integer = 0;
  OSType       : DWORD;

  procedure Debug(S : String; Level : Integer);

implementation

uses Properties, About, Partition, Time, binFile, ShellAPI, CreateLink,
     CreateDevice, TarFile, FileDropSource, ActiveX, ShlObj;


{$R *.DFM}


procedure TMainForm.FormCreate(Sender: TObject);
var
   Version        : TOSVersionInfo;
   VersionString  : String;
   i              : Integer;
begin
   Partitions  := TList.Create;
   Options     := TOptions.Create;

   Debug('Explore2fs version ' + Explore2fsVersion, DebugOff);
   Debug('Written by John Newbigin', DebugOff);
   Debug('Send debug logs to jn@it.swin.edu.au', DebugHigh);

   Debug('New file permissions is 0x' + IntToHex(Options.GetUMask, 3), DebugHigh);

   // Set column header widths
   for i := 0 to Options.ColumnWidths.Count - 1 do
   begin
      if i < ListView2.Columns.Count then
      begin
         ListView2.Columns[i].Width := Integer(Options.ColumnWidths[i]);
      end;
   end;

   CreateFlags := Options.GetUMask;
   ListedNode  := nil;

   UserLib := TUserList.Create;
   LoadUSerLib;

   FixDisplay;
   Show;
   Application.ProcessMessages;

   // find out OS version
   Version.dwOSVersionInfoSize := Sizeof(Version);
   if GetVersionEx(Version) then
   begin
      case Version.dwPlatformId of
         VER_PLATFORM_WIN32s        : VersionString := 'WIN32s';
         VER_PLATFORM_WIN32_WINDOWS : VersionString := 'Windows 95';
         VER_PLATFORM_WIN32_NT      : VersionString := 'Windows NT';
      else
         VersionString := 'Unknown OS';
      end;
      VersionString := VersionString + ' ' + IntToStr(Version.dwMajorVersion) +
                                       '.' + IntToStr(Version.dwMinorVersion) +
                                       ' build number ' + IntToStr(Version.dwBuildNumber);
      Debug(VersionString, DebugOff);
      if Version.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS then
      begin
         OSis95 := True;
      end
      else
      begin
         OSis95 := False;
      end;
      OSType := Version.dwPlatformId;
   end
   else
   begin
      Debug('Could not get Version info!', DebugOff);
   end;

   RescanPartitions1Click(RescanPartitions1);
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
var
   i : Integer;
begin
   try
      ListView2.Items.BeginUpdate;
      i := 0;
      while i < TreeView1.Items.Count do
      begin
         PruneNode(TreeView1.Items.Item[i]);
         i := i + 1;
      end;
      TreeView1.Items.Clear;
      for i := 0 to Partitions.Count - 1 do
      begin
         TLinuxPartition(Partitions[i]).Free;
      end;
      Partitions.Free;

      UserLib.Free;

      if Options.AutoSave then
      begin
         DebugMemo.Lines.SaveToFile(ExtractFilePath(Application.ExeName) + 'explore2fs debug log.txt');
      end;

      // save column widths
      Options.ColumnWidths.Clear;
      for i := 0 to ListView2.Columns.Count - 1 do
      begin
         Options.ColumnWidths.Add(Pointer(ListView2.Columns[i].Width));
      end;
      Options.Free;
   except
      on E : Exception do
      begin
         MessageDlg(E.Message + #10 + 'An exception has occured while quitting application.', mtError, [mbOK], 0);
      end;
   end;
end;

procedure Debug(S : String; Level : Integer);
begin
   MainForm.Debug(S, Level);
end;

procedure TMainForm.Button2Click(Sender: TObject);
begin
   ListView2.ViewStyle := vsIcon;
end;

procedure TMainForm.Exit1Click(Sender: TObject);
begin
   Close;
end;

procedure TMainForm.LargIconSpeedButtonClick(Sender: TObject);
begin
   Options.ViewStyle := vsIcon;
   FixDisplay;
end;

procedure TMainForm.SmallIconSpeedButtonClick(Sender: TObject);
begin
   Options.ViewStyle := vsSmallIcon;
   FixDisplay;
end;

procedure TMainForm.ListSpeedButtonClick(Sender: TObject);
begin
   Options.ViewStyle := vsList;
   FixDisplay;
end;

procedure TMainForm.DetailsSpeedButtonClick(Sender: TObject);
begin
   Options.ViewStyle := vsReport;
   FixDisplay;
end;

procedure TMainForm.Toolbar1Click(Sender: TObject);
begin
   Options.ShowToolbar := not Toolbar1.Checked;
   FixDisplay;
end;

procedure TMainForm.FixDisplay;
begin
   ToolBarPanel.Visible := Options.ShowToolbar;
   Toolbar1.Checked     := Options.ShowToolbar;

   StatusBar2.Checked := Options.ShowStatus;
   StatusBar.Visible  := Options.ShowStatus;

   Debug1.Checked := Options.ShowDebug;
   Splitter2.Visible := Options.ShowDebug;
   DebugMemo.Visible := Options.ShowDebug;

   case Options.ViewStyle of
      vsReport :
      begin
         Details1.Checked        := True;
         DetailsSpeedButton.Down := True;
         ListView2.Font.Name     := 'Courier';
      end;
      vsSmallIcon :
      begin
        SmallIcons1.Checked       := True;
        SmallIconSpeedButton.Down := True;
        ListView2.Font.Name       := 'MS Sans Serif';
      end;
      vsIcon :
      begin
         LargeIcons1.Checked      := True;
         LargIconSpeedButton.Down := True;
         ListView2.Font.Name      := 'MS Sans Serif';
      end;
      vsList :
      begin
        List1.Checked         := True;
        ListSpeedButton.Down  := True;
        ListView2.Font.Name   := 'MS Sans Serif';
      end;
   end;

   ListView2.ViewStyle := Options.ViewStyle;

end;


procedure TMainForm.Statusbar2Click(Sender: TObject);
begin
   Options.ShowStatus := not StatusBar2.Checked;
   FixDisplay;
end;

procedure TMainForm.by1Click(Sender: TObject);
begin
   Options.SortColumn := 0;
   ReListNodeFiles
end;

procedure TMainForm.bySize1Click(Sender: TObject);
begin
   Options.SortColumn := 1;
   ReListNodeFiles
end;

procedure TMainForm.byType1Click(Sender: TObject);
begin
   Options.SortColumn := 2;
   ReListNodeFiles
end;

procedure TMainForm.byDate1Click(Sender: TObject);
begin
   Options.SortColumn := 3;
   ReListNodeFiles
end;

procedure TMainForm.SelectAll1Click(Sender: TObject);
var
   i : Integer;
begin
   ListView2.SetFocus;
   for i := 0 to ListView2.Items.Count - 1 do
   begin
      ListView2.Items[i].Selected := True;
   end;
end;

procedure TMainForm.InvertSelection1Click(Sender: TObject);
var
   i : Integer;
begin
   ListView2.SetFocus;
   for i := 0 to ListView2.Items.Count - 1 do
   begin
      ListView2.Items[i].Selected := not ListView2.Items[i].Selected;
   end;
end;

procedure TMainForm.Debug(S : String; Level : Integer);
begin
   if Options.DebugLevel >= Level then
   begin
      DebugMemo.Lines.Add(S);
   end;
end;


// This looks for Linux partitions
// see Partirion.pas for the partition scanning code
function TMainForm.ScanForLinux(Partitions : TList): integer;
var
   List     : TPartitionList;
   Linux    : TLinuxPartition;
   i        : Integer;
begin
   Debug('Looking for Linux Partitions', DebugLow);
   List := TPartitionList.Create;
   List.ScanFloppy := Options.FloppyScan;
   try

      // look for real PARTITION_LINUX partitions
      if OSis95 then
      begin
         List.Win95_Scan(PARTITION_LINUX);
      end
      else
      begin
         List.LL_Scan(PARTITION_LINUX);
      end;

      // do we have a non-standard partition type to look for?
      if (Options.NonStandard > 0) and (Options.NonStandard <> PARTITION_LINUX)then
      begin
         if List.Count > 0 then // this is bad logic, but the NonStandard is a hack too
         begin
            List.ScanFloppy := False;
         end;
         Debug('Scanning for non-standard partition type 0x' + IntToHex(Options.NonStandard, 2), DebugOff);
         if OSis95 then
         begin
            List.Win95_Scan(Options.NonStandard);
         end
         else
         begin
            List.LL_Scan(Options.NonStandard);
         end;
      end;

      Debug('Found ' + IntToStr(List.Count) + ' Linux partitions', DebugLow);
      Result := List.Count;
      // Create a TLinuxPartition object for each linux partition found
      for i := 0 to List.Count - 1 do
      begin
         Linux := TLinuxPartition.Create(
            List.GetPartition(i).FileName,
            List.GetPartition(i).DriveNo,
            List.GetPartition(i).PartNo,
            List.GetPartition(i).StartingOffset,
            List.GetPartition(i).PartitionLength,
            List.GetPartition(i).BytesPerSector,
            Options.Prefix);
         Partitions.add(Linux);

      end;

   finally
      List.Free;
   end;
end;

// Re-list the appropriate directory in the right hand pane.
procedure TMainForm.ReListNodeFiles;
var
   Node : TTreeNode;
begin
   Node := ListedNode;
   ListedNode := nil;
   ListNodeFiles(Node);
end;

// Lists the files in the specified node in the right hand node
// if they are already listed, then nothing is changed.  To force an
// update, use ReListNodeFiles
procedure TMainForm.ListNodeFiles(Node : TTreeNode);
var
   i           : Integer;
   Data        : TNodeData;
   NewItem     : TListItem;
   Child       : TNodeData;
   TotalSize   : ULONG;
   ObjectCount : Integer;
   LongTime    : Boolean;
   Increment   : Integer;
begin
   // add the files in the ListView
   if ListedNode <> Node then
   begin
      LongTime    := False; // assume we don't need a progress bar
      Increment   := 1;
      Panel4.Caption := 'Contents of ' + Node.Text;
      ListView2.Items.BeginUpdate;
      ListView2.Items.Clear;
      ListedNode  := Node;
      Data        := TNodeData(Node.Data);

      // Enable file drop messages...  (John's custom TListView component)
      if Data.Partition.ReadWrite <> ListView2.AcceptFiles then
      begin
         ListView2.AcceptFiles := Data.Partition.ReadWrite;
      end;

      TotalSize   := 0;
      ObjectCount := 0;
      if Assigned(Data.Children) then // are there any files?
      begin
         if Data.Children.Count > 64 then // we want to show the progress bar
         begin
            LongTime := True;
            TreeView1.Cursor := crHourglass;
            ListView2.Cursor := crHourglass;
            StatusBar.Panels[1].Text := '0%';
            StatusBar.Panels[0].Text := IntToStr(Data.Children.Count) + ' objects';
            Increment := Data.Children.Count div 100;
            if Increment <= 0 then
            begin
               Increment := 1;
            end;
            Application.ProcessMessages; // to update the screen
         end
         else
         begin
            LongTime := False; // don't use progress bar
         end;

         for i := 0 to Data.Children.Count - 1 do
         begin
            if LongTime then // increment progress bar
            begin
               if (i > 0) and (i mod Increment = 0) then
               begin
                  StatusBar.Panels[1].Text := IntToStr((i * 100) div Data.Children.Count) + '%';
                  StatusBar.Update;
               end;
            end;

            Child := TNodeData(Data.Children[i]);

            // Should we hide any files?
            if ((Child.Filename[1] = '.') and (Options.ShowDotFiles = false)) or
               ((Child.Filename[1] = '~') and (Options.ShowBackups  = false)) then
            begin
               // do nothing?
            end
            else
            begin
               // add the file to the list
               NewItem         := ListView2.Items.Add();
               NewItem.Data    := Child;

               // Caption
               NewItem.Caption := Child.FileName;

               // File Size
               NewItem.SubItems.Add(IntToStr(Child.size));
               TotalSize       := TotalSize + Child.size;
               ObjectCount     := ObjectCount + 1;

               // set icon and
               // File type description
               case (Child.mode and S_IFMT) of
                  S_IFSOCK :  begin
                                 NewItem.ImageIndex := icSocket;
                                 NewItem.SubItems.Add('socket');
                              end;
                  S_IFLNK :  begin
                                 NewItem.ImageIndex := icLink;
                                 NewItem.SubItems.Add('sym link');
                              end;
                  S_IFREG :  begin
                                 NewItem.ImageIndex := icFile;
                                 NewItem.SubItems.Add('file');
                              end;
                  S_IFBLK :  begin
                                 NewItem.ImageIndex := icBlock;
                                 NewItem.SubItems.Add('block device');
                              end;
                  S_IFDIR :  begin
                                 NewItem.ImageIndex := icDirectory;
                                 NewItem.SubItems.Add('directory');
                              end;
                  S_IFCHR :  begin
                                 NewItem.ImageIndex := icChar;
                                 NewItem.SubItems.Add('character device');
                              end;
                  S_IFIFO :  begin
                                 NewItem.ImageIndex := icFifo;
                                 NewItem.SubItems.Add('fifo');
                              end;
               else
                  begin
                     NewItem.ImageIndex := icFile;
                     NewItem.SubItems.Add('unknown');
                  end;
               end;

               // modification time
               NewItem.SubItems.Add(CTime(Child.mtime));

               // UID/user name
               NewItem.SubItems.Add(UserLib.GetName(Child.uid));

               // GID/group name
               NewItem.SubItems.Add(UserLib.GetGroup(Child.gid));

               // mode string
               NewItem.SubItems.Add(Child.ModeString);
            end;
         end;
         StatusBar.Panels[0].Text := IntToStr(ObjectCount) + ' objects';
      end
      else
      begin
         StatusBar.Panels[0].Text := '0 objects';
      end;
      StatusBar.Panels[2].Text := IntToStr(TotalSize) + ' bytes';
      if LongTime then
      begin
         TreeView1.Cursor := crDefault;
         ListView2.Cursor := crDefault;
         StatusBar.Panels[1].Text := '';
      end;

      ListView2.Items.EndUpdate;
   end;
end;

procedure TMainForm.LoadNode(Node : TTreeNode);
var
   Partition   : TLinuxPartition;
   INode       : TINode;
   Dir         : TDirectory;
   NewNode     : TTreeNode;
   Data        : TNodeData;
   NewINode    : TINode;
   List        : TStringList;
   i           : Integer;
   Links       : Integer;
begin
   if Assigned(node) then
   begin
      if not TNodeData(Node.Data).Loaded then
      begin
         TreeView1.Items.BeginUpdate;
         try
            // read the inode
            Partition := TNodeData(Node.Data).Partition;

            try
               Partition.Open; // Make sure - this will raise an exception if it fails

               INode := TINode.Create(Partition, TNodeData(Node.Data).INode);
               try
                  Partition.DumpINode(INode.Info);
                  // now extract the directory info (assume it is is a dir if we are expanding it!)
                  Dir := TDirectory.Create(Partition, INode);

                  try
                     List := TStringList.Create;
                     Dir.EnumDir(List);
                     for i := 0 to List.Count - 1 do
                     begin
                        Links := 0;
                        if not((CompareStr(List[i], '.') = 0) or (CompareStr(List[i], '..') = 0)) then
                        begin
                           Data := TNodeData.Create;
                           Data.INode              := LongInt(List.Objects[i]);
                           Data.FileName           := List[i];
                           Data.ParentINode        := TNodeData(Node.Data).INode;
                           Data.Partition          := Partition;

                           // we have to lock the inode and read it's data....?
                           if Data.INode > 0 then
                           begin
                              NewINode := TINode.Create(Partition, Data.INode);
                              try
                                 Data.mode   := NewINode.Info.i_mode;
                                 Data.uid    := NewINode.Info.i_uid;
                                 Data.gid    := NewINode.Info.i_gid;
                                 Data.size   := NewINode.Info.i_size;
                                 Data.mtime  := NewINode.Info.i_mtime;
                                 Data.flags  := NewINode.Info.i_flags;
                                 Data.ModeString := NewINode.ModeString;

                                 Links := NewINode.Info.i_links_count;
                              finally
                                 NewINode.Free;
                              end;
                           end;

                           if (Data.mode and S_IFMT) = S_IFDIR then
                           begin
                              NewNode := TreeView1.Items.AddChild(Node, List[i]);
                              Debug('Dir has ' + IntToStr(Links) + ' links', DebugLow);
                              if Links > 2 then
                              begin
                                 NewNode.HasChildren := True;
                              end
                              else
                              begin
                                 NewNode.HasChildren := False;
                              end;
                              NewNode.ImageIndex      := 1; // dir
                              NewNode.SelectedIndex   := 1;
                              NewNode.Data            := Data;
                           end
                           else // some kind of file
                           begin
                              if not Assigned(TNodeData(Node.Data).Children) then
                              begin
                                 TNodeData(Node.Data).Children := TList.Create;
                              end;
                              TNodeData(Node.Data).Children.Add(Data);
                           end;
                        end;
                     end;
                  finally
                     Dir.Free;
                  end;

               finally
                  INode.Free;
               end;

               Node.AlphaSort;
               TNodeData(Node.Data).Loaded := True;
               ListNodeFiles(Node);
               if Node.Count = 0 then
               begin
                  Node.HasChildren := False;
               end;
            except
               on E : Exception do
               begin
                  MessageDlg('Error: ' + E.Message, mtError, [mbOK], 0);
                  TNodeData(Node.Data).Loaded := False;
               end;
            end;

         finally
            TreeView1.Items.EndUpdate;
         end;
      end;
   end;
end;

procedure TMainForm.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
  var AllowExpansion: Boolean);
begin
   try
      TreeView1.Selected := Node;
      LoadNode(Node);
      AllowExpansion := True;
   except
      on E : Exception do
      begin
         MessageDlg(E.Message, mtError, [mbOK], 0);
         AllowExpansion := False;
         TreeView1.Items.EndUpdate;
      end;
   end;
end;

///////////////////
//
//  TNodeData
//
///////////////////
constructor TNodeData.Create;
begin
   Loaded := False;
   Children := nil;
end;

destructor TNodeData.Destroy;
var
   i : Integer;
begin
   if Assigned(Children) then
   begin
      for i := 0 to Children.Count - 1 do
      begin
         TNodeData(Children[i]).Free;
      end;
      Children.Free;
   end;
end;

procedure TMainForm.OpenFile1Click(Sender: TObject);
var
   Linux : TLinuxPartition;
begin
   if Ex2OpenDialog.Execute then
   begin
      // create a TLinuxPartition...
      Linux := TLinuxPartition.Create(Ex2OpenDialog.FileName, -1, 0, Large0, Large0, 512, '');
      Partitions.Add(Linux);
      if Options.WriteSupport then
      begin
         if MessageDlg('Do you wish to enable experimental write support for this file ?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
         begin
            Linux.EnableWrite;
         end;
      end;

      AddPartitionToTree(Linux);
   end;
end;


// Adds the root parition objects to the TreeView
procedure TMainForm.AddPartitionToTree(Partition : TLinuxPartition);
var
   Node  : TTreeNode;
   Data  : TNodeData;
begin
   Node := TreeView1.Items.AddChild(nil, Partition.Description);
   Node.ImageIndex      := 0; // disk
   Node.SelectedIndex   := 0;
   Node.HasChildren     := True;

   Data                 := TNodeData.Create;
   Data.INode           := EXT2_ROOT_INO;
   Data.ParentINode     := EXT2_ROOT_INO; // just in case
   Data.Partition       := Partition;
   Node.Data            := Data;
end;

procedure TMainForm.Properties2Click(Sender: TObject);
var
   Node        : TTreeNode;
   Item        : TListItem;
   Data        : TNodeData;
   INode       : TInode;
   Properties  : TPropertiesDlg;
begin
   // is it the tree window?
   if TreeView1.Focused then
   begin
      // which node in the tree?
      Node := TreeView1.Selected;
      if Assigned(Node) and Assigned(Node.Data) then
      begin
         Data := TNodeData(Node.Data);
         if Data.INode = EXT2_ROOT_INO then
         begin
            LoadNode(Node); // make sure
         end;
         Properties := TPropertiesDlg.Create(self);
         INode := TINode.Create(Data.Partition, Data.INode);
         try
            if Data.INode = EXT2_ROOT_INO then
            begin
               Properties.Execute(Data.Partition.GetSuper, Data.Partition, INode, Data.FileName, UserLib);
            end
            else
            begin
               Properties.Execute(nil, nil, INode, Data.FileName, UserLib);
            end;
         finally
            INode.Free;
         end;
      end;
   end
   else if ListView2.Focused then
   begin
      // which item?
      if ListView2.SelCount > 1 then
      begin
         MessageDlg('You can only view the properties of one file at a time', mtInformation, [mbOK], 0);
      end
      else if ListView2.SelCount = 0 then
      begin
         MessageDlg('Please select a file', mtInformation, [mbOK], 0);
      end
      else
      begin
         Item := ListView2.Selected;
         if Assigned(Item) then
         begin
            Data := TNodeData(Item.Data);
            Properties := TPropertiesDlg.Create(self);
            INode := TINode.Create(Data.Partition, Data.INode);
            try
               if Properties.Execute(nil, nil, INode, Data.FileName, UserLib) = mrOK then
               begin
                  if Data.Partition.ReadWrite then
                  begin
                     Data.mode   := INode.Info.i_mode;
                     Data.uid    := INode.Info.i_uid;
                     Data.gid    := INode.Info.i_gid;
                     Data.size   := INode.Info.i_size;
                     Data.mtime  := INode.Info.i_mtime;
                     Data.flags  := INode.Info.i_flags;
                     Data.ModeString := INode.ModeString;
                     ReListNodeFiles;
                  end;
               end;
            finally
               INode.Free;
            end;
         end;
      end;
   end;
end;

procedure TMainForm.TreeView1Change(Sender: TObject; Node: TTreeNode);
var
   Data : TNodeData;
begin
   try
      if Assigned(Node) then
      begin
         Data := TNodeData(Node.Data);
         if Assigned(Data) then
         begin
            ComboBox1.Text := Node.Text;
            if Data.Loaded then
            begin
               ListNodeFiles(Node);
            end;
         end;
      end;
   except
      on E : Exception do
      begin
         MessageDlg(E.Message, mtError, [mbOK], 0);
         TreeView1.Items.EndUpdate;
      end;
   end;
end;

procedure TMainForm.Save1Click(Sender: TObject);
var
   Data     : TNodeData;
   i        : Integer;
   Count    : Integer;
   Dlg      : TSavingDlg;
begin
   // find the file that is selected
   if TreeView1.Focused then
   begin
      // save a dir
      if OpenDirectory1.Execute then
      begin
         try
            TreeView1.Items.BeginUpdate;
            SaveDirectory(TreeView1.Selected, OpenDirectory1.Directory);
         finally
            TreeView1.Items.EndUpdate;
         end;
      end;
   end
   else if ListView2.Focused then
   begin
      if ListView2.SelCount = 1 then
      begin
         Data := TNodeData(ListView2.Selected.Data);
         if Assigned(Data) then
         begin
            if ((Data.Mode and S_IFMT) = S_IFREG) or ((Data.Mode and S_IFMT) = S_IFLNK) and (Data.Size > ((EXT2_N_BLOCKS * sizeof(ULONG)))) then
            begin
               SaveDialog1.FileName := Data.FileName;
               if SaveDialog1.Execute then
               begin
                  SaveFile(Data, SaveDialog1.FileName, nil, false);
               end;
            end
            else
            begin
               MessageDlg('Can''t handle anything but regular files at the moment', mtInformation, [mbOK], 0);
            end;
         end;
      end
      else
      begin
         // we have a multi select.....
         //we need to select a dir (somehow!)
         if OpenDirectory1.Execute then
         begin
            Dlg   := TSavingDlg.Create(self);
            try
               Count := 0;
               // count the number of files to copy...
               for i := 0 to ListView2.Items.Count - 1 do
               begin
                  if ListView2.Items[i].Selected then
                  begin
                     Count := Count + 1;
                  end
               end;
               Dlg.Display(Count);

               Count := 0;
               for i := 0 to ListView2.Items.Count - 1 do
               begin
                  if ListView2.Items[i].Selected then
                  begin
                     Data := TNodeData(ListView2.Items[i].Data);
                     if (Data.Mode and S_IFMT) = S_IFREG then
                     begin
                        SaveFile(Data, OpenDirectory1.Directory + '\' + Data.FileName, Dlg, false);
                        Count := Count + 1;
                     end;
                  end;
               end;
               MessageDlg(IntToStr(Count) + ' files saved', mtInformation, [mbOK], 0);
            finally
               Dlg.Free;
            end;
         end;
      end;
   end;
end;

procedure TMainForm.SaveFile(Data : TNodeData; FileName : String; Dlg : TSavingDlg; XLate : Boolean);
var
   INode : TINode;
begin
   // lock the inode
   INode := TINode.Create(Data.Partition, Data.INode);
   try
      if not Assigned(Dlg) then
      begin
         Dlg := TSavingDlg.Create(self);
         try
            Dlg.Display(1);
            INode.SaveToFile(FileName, Dlg, XLate);
         finally
            Dlg.Free;
         end;
      end
      else
      begin
         INode.SaveToFile(FileName, Dlg, XLate);
      end;
   finally
      INode.Free;
   end;
end;

procedure TMainForm.About1Click(Sender: TObject);
begin
   TAboutBox.Create(self).ShowModal;
end;

procedure TMainForm.SaveDirectory(Node : TTreeNode; Directory : String);
var
   Dir   : String;
   i     : Integer;
   Data  : TNodeData;
   Child : TNodeData;
   Dlg   : TSavingDlg;
begin
   //
   LoadNode(Node);
   Data := Node.Data;
   Dir := Directory + '\' + Data.FileName;
   try
      Mkdir(Dir);
   except
      on EInOutError do
      begin
      end;
   end;

   // save the children
   if Assigned(Data.Children) then
   begin
      Dlg := TSavingDlg.Create(self);
      try
         Dlg.Display(Data.Children.Count);
         for i := 0 to Data.Children.Count - 1 do
         begin
            Child := TNodeData(Data.Children[i]);
            if Assigned(Child) then
            begin
               if (Child.Mode and S_IFMT) = S_IFREG then
               begin
                  SaveFile(Child, Dir + '\' + Child.FileName, Dlg, false);
               end;
            end;
         end;
      finally
         Dlg.Free;
      end;
   end;
   // what about the directories?
   for i := 0 to Node.Count - 1 do
   begin
      SaveDirectory(Node.Item[i], Dir);
   end;
end;

procedure TMainForm.Debug1Click(Sender: TObject);
begin
   Options.ShowDebug := not Debug1.Checked;
   FixDisplay;
end;

procedure TMainForm.Options1Click(Sender: TObject);
var
   Node : TTreeNode;
begin
   if Options.Execute then
   begin
      FixDisplay;
      Node := TreeView1.Selected;

      // reload user lib files
      if Assigned(UserLib) then
      begin
         UserLib.Free;
      end;
      UserLib := TUserList.Create;
      LoadUserLib;

      if Assigned(Node) and Assigned(Node.Data) then
      begin
         ReListNodeFiles;
      end;
   end;
end;

procedure TMainForm.RescanPartitions1Click(Sender: TObject);
var
   i     : Integer;
begin
   try
      ListView2.Items.Clear;
      Treeview1.Items.BeginUpdate;

      i := 0;
      while i < TreeView1.Items.Count do
      begin
         PruneNode(TreeView1.Items.Item[i]);
         i := i + 1;
      end;
      TreeView1.Items.Clear;
      for i := 0 to Partitions.Count - 1 do
      begin
         TLinuxPartition(Partitions[i]).Free;
      end;
      Partitions.Clear;

      ScanForLinux(Partitions);

      for i := 0 to Partitions.Count - 1 do
      begin
         if Options.WriteSupport then
         begin
            if MessageDlg('Do you wish to enable write support for the partition ' + TLinuxPartition(Partitions[i]).Description + '.' + #10 + #10 +
                          '(Please read the readme before answering yes!)', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
            begin
               TLinuxPartition(Partitions[i]).EnableWrite;
            end;
         end;
         AddPartitionToTree(TLinuxPartition(Partitions[i]));
      end;
   finally
      TreeView1.AlphaSort;
      Treeview1.Items.EndUpdate;
   end;
end;

procedure TMainForm.Close1Click(Sender: TObject);
begin
   MessageDlg('NYI', mtInformation, [mbOK], 0);
end;

procedure TMainForm.Delete2Click(Sender: TObject);
var
   Item     : TListItem;
   Data     : TNodeData;
   Parent   : TNodeData;
   Count    : Integer;
   i        : Integer;
begin
   if ListView2.Focused then
   begin
      Parent := TNodeData(ListedNode.Data);
      Count := 0;
      for i := 0 to ListView2.Items.Count - 1 do
      begin
         if ListView2.Items[i].Selected then
         begin
            Count := Count + 1;
         end;
      end;
      if Count > 0 then
      begin
         if MessageDlg('Are you sure you want to delete these ' + IntToStr(Count) + ' files?', mtConfirmation, [mbYes, mbCancel], 0) = mrYes then
         begin
            for i := 0 to ListView2.Items.Count - 1 do
            begin
               if ListView2.Items[i].Selected then
               begin
                  Item := ListView2.Items[i];
                  Data := TNodeData(Item.Data);
                  Parent.Partition.DeleteFile(Parent.INode, Data.INode);
                  Parent.Children.Remove(Data);
                  Data.Free;
               end;
            end;
         end;
      end;
      Debug('Check the redraw code here!', DebugOff);
      PruneNode(ListedNode); // ???????????

      ReListNodeFiles;
   end;
end;

procedure TMainForm.Addfile1Click(Sender: TObject);
var
   Node     : TTreeNode;
   Data     : TNodeData;
   Expand   : Boolean;
   NewINode : ULong;
   INodeO   : TINode;
   Dlg      : TSavingDlg;
begin
   Node := TreeView1.Selected;
   if Assigned(Node) and Assigned(Node.Data) then
   begin
      if ImportFileDialog.Execute then
      begin
         Data := TNodeData(Node.Data);
         NewINode := Data.Partition.CreateFile(Data.Inode, ExtractFileName(ImportFileDialog.FileName));
         INodeO := TINode.Create(Data.Partition, NewINode);
         try
            Dlg := TSavingDlg.Create(self);
            try
               Dlg.Display(1);
               INodeO.ReadFromFile(ImportFileDialog.FileName, Dlg, false);
            finally
               Dlg.Free;
            end;
         finally
            INodeO.Free;
         end;

         // remember to update the display....!
         // what do we need to free?
         Expand := Node.Expanded;
         PruneNode(Node);
         LoadNode(Node);
         if Expand then
         begin
            Node.Expand(False);
         end;
      end;
   end;
end;

procedure TMainForm.ExportasText1Click(Sender: TObject);
var
   Data     : TNodeData;
   i        : Integer;
   Count    : Integer;
   Dlg      : TSavingDlg;
begin
   if ListView2.Focused then
   begin
      if ListView2.SelCount = 1 then
      begin
         Data := TNodeData(ListView2.Selected.Data);
         if Assigned(Data) then
         begin
            if (Data.Mode and S_IFMT) = S_IFREG then
            begin
               SaveDialog1.FileName := Data.FileName;
               if SaveDialog1.Execute then
               begin
                  SaveFile(Data, SaveDialog1.FileName, nil, True);
               end;
            end
            else
            begin
               MessageDlg('Can''t handle anything but regular files at the moment', mtInformation, [mbOK], 0);
            end;
         end;
      end
      else
      begin
         // we have a multi select.....
         //we need to select a dir (somehow!)
         if OpenDirectory1.Execute then
         begin
            Dlg   := TSavingDlg.Create(self);
            try
               Count := 0;
               // count the number of files to copy...
               for i := 0 to ListView2.Items.Count - 1 do
               begin
                  if ListView2.Items[i].Selected then
                  begin
                     Count := Count + 1;
                  end
               end;
               Dlg.Display(Count);

               Count := 0;
               for i := 0 to ListView2.Items.Count - 1 do
               begin
                  if ListView2.Items[i].Selected then
                  begin
                     Data := TNodeData(ListView2.Items[i].Data);
                     if (Data.Mode and S_IFMT) = S_IFREG then
                     begin
                        SaveFile(Data, OpenDirectory1.Directory + '\' + Data.FileName, Dlg, true);
                        Count := Count + 1;
                     end;
                  end;
               end;
               MessageDlg(IntToStr(Count) + ' files saved', mtInformation, [mbOK], 0);
            finally
               Dlg.Free;
            end;
         end;
      end;
   end;
end;

procedure TMainForm.Button1Click(Sender: TObject);
var
   F : TBinaryFile;
   i : Integer;
   Size : Ulong;
begin
   F := TBinaryFile.Create;
   F.Assign('testfile');

   // make a file that requires a tripple indirect block
   // assume 256 blocks per indirect block (1k fs)
   Size := EXT2_NDIR_BLOCKS + (256) + (256 * 256) + 1;
   Size := Size * 1024;
   Debug('Creating file length ' + IntToStr(Size), DebugOff);
   // i know this is slow, but it is just for testing...
   for i := 1 to Size do
   begin
      F.WriteChar('j');
   end;
   F.Free;
end;

procedure TMainForm.PruneNode(Node : TTreeNode);
var
   i     : Integer;
   Data  : TNodeData;
begin
   if Assigned(Node) then
   begin
      if ListedNode = Node then
      begin
         ListedNode := nil
      end;
      // recurse and remove children.....
      for i := 0 to Node.Count - 1 do
      begin
         PruneNode(Node.Item[i]);
      end;
      Node.DeleteChildren;
      Data := TNodeData(Node.Data);
      if Assigned(Data) then
      begin
         if Assigned(Data.Children) then
         begin
            for i := 0 to Data.Children.Count - 1 do
            begin
               TNodeData(Data.Children[i]).Free;
            end;
            Data.Children.Free;
            Data.Children := nil;
         end;
      end;
      Data.Loaded := False;
      Node.HasChildren := True;
   end;
end;

procedure TMainForm.NewFolder1Click(Sender: TObject);
var
   Node     : TTreeNode;
   Data     : TNodeData;
   Expand   : Boolean;
begin
   Node := TreeView1.Selected;
   if Assigned(Node) and Assigned(Node.Data) then
   begin
      Data := TNodeData(Node.Data);
      Data.Partition.MakeDirectory(Data.Inode, 'new folder');

      // remember to update the display....!
      // what do we need to free?
      Expand := Node.Expanded;
      PruneNode(Node);
      LoadNode(Node);
      if Expand then
      begin
         Node.Expand(False);
      end;
   end;
end;

procedure TMainForm.Rename1Click(Sender: TObject);
var
   Item     : TListItem;
   Node     : TTreeNode;
begin
   if ListView2.Focused then
   begin
      Item := ListView2.Selected;
      if Assigned(Item) then
      begin
         Item.EditCaption;
      end;
   end
   else if TreeView1.Focused then
   begin
      Node := TreeView1.Selected;
      if Assigned(Node) then
      begin
         Node.EditText;
      end;
   end;
end;

procedure TMainForm.TreeView1Editing(Sender: TObject; Node: TTreeNode;
  var AllowEdit: Boolean);
begin
   if TNodeData(Node.Data).Partition.ReadWrite then
   begin
      AllowEdit := True;
   end
   else
   begin
      AllowEdit := False;
   end;
end;

procedure TMainForm.ListView2Editing(Sender: TObject; Item: TListItem;
  var AllowEdit: Boolean);
begin
   if TNodeData(Item.Data).Partition.ReadWrite then
   begin
      AllowEdit := True;
   end
   else
   begin
      AllowEdit := False;
   end;
end;

procedure TMainForm.ListView2Edited(Sender: TObject; Item: TListItem;
  var S: string);
var
   Data     : TNodeData;
begin
   // which item?
   if Assigned(Item) then
   begin
      Data := TNodeData(Item.Data);
      if Assigned(Data) then
      begin
         Data.Partition.Rename(Data.ParentINode, Data.INode, S);
         Item.Caption := S;
      end;
   end;
end;

procedure TMainForm.TreeView1Edited(Sender: TObject; Node: TTreeNode;
  var S: string);
var
   Data : TNodeData;
begin
   if Assigned(Node.Data) then
   begin
      Data := TNodeData(Node.Data);
      Data.Partition.Rename(Data.ParentINode, Data.INode, S);
      Node.Text := S;
   end;
end;

procedure TMainForm.Delete3Click(Sender: TObject);
   // delete folder....
var
   Node : TTreeNode;
   Data : TNodeData;
begin
   Node := TreeView1.Selected;
   if Assigned(Node.Data) then
   begin
      if MessageDlg('Are you sure you want to delete the directory ''' + Node.Text + ''' and all it''s children?', mtConfirmation, [mbYes, mbCancel], 0) = mrYes then
      begin
         Data := TNodeData(Node.Data);
         Data.Partition.RmDirectory(Data.ParentINode, Data.INode);

         Node := Node.Parent;
         PruneNode(Node);
         LoadNode(Node);
         Node.Expand(False);
      end;
   end;
end;

procedure TMainForm.ImportDirectory1Click(Sender: TObject);
var
   Node     : TTreeNode;
   Data     : TNodeData;
   Expand   : Boolean;
   DirName  : String;
   P        : Integer;
   NewINode : ULong;
   INodeO   : TINode;
   Dlg      : TSavingDlg;
begin
   Node := TreeView1.Selected;
   if Assigned(Node) and Assigned(Node.Data) then
   begin
      Data := TNodeData(Node.Data);
      if OpenDirectory1.Execute then
      begin
         // get the name of the directory
         DirName := OpenDirectory1.Directory;
         while True do
         begin
            P := pos('\', DirName);
            if P = 0 then break;
            Delete(DirName, 1, P);
         end;
         if MessageDlg('Are you sure you want to import this directory ''' + OpenDirectory1.Directory + ''' into the directory ''' + DirName + ''' ?', mtConfirmation, [mbYes, mbCancel], 0) = mrYes then
         begin

            NewINode := Data.Partition.MakeDirectory(Data.Inode, DirName);
            // add lots of files.....
            INodeO := TINode.Create(Data.Partition, NewINode);
            try
               Dlg := TSavingDlg.Create(self);
               try
                  Dlg.Display(1);
                  INodeO.ImportDirectory(OpenDirectory1.Directory, Dlg, false);
               finally
                  Dlg.Free;
               end;
            finally
               INodeO.Free;
            end;


            // remember to update the display....!
            // what do we need to free?
            Expand := Node.Expanded;
            PruneNode(Node);
            LoadNode(Node);
            if Expand then
            begin
               Node.Expand(False);
            end;
         end;
      end;
   end;
end;

procedure TMainForm.DirPopupMenuPopup(Sender: TObject);
var
   Node : TTreeNode;
   Data : TNodeData;
   EnableWrite : Boolean;
   i : Integer;
begin
   EnableWrite := False; // default
   // find out which element we will use.....
   Node := TreeView1.Selected;
   if Assigned(Node) then
   begin
      if Assigned(Node.Data) then
      begin
         Data := TNodeData(Node.Data);
         if Data.Partition.ReadWrite then
         begin
            EnableWrite := True;
         end;
      end;
   end;
   with Sender as TPopupMenu do
   begin
      for i := 0 to Items.Count - 1 do
      begin
         if Items[i].Tag = 1 then
         begin
            Items[i].Enabled := EnableWrite;
         end;
      end;
   end;
end;

procedure TMainForm.FilePopupMenuPopup(Sender: TObject);
var
   Data : TNodeData;
   EnableWrite : Boolean;
   i : Integer;
begin
   EnableWrite := False; // default
   if Assigned(ListedNode) then
   begin
      if Assigned(ListedNode.Data) then
      begin
         Data := TNodeData(ListedNode.Data);
         if Data.Partition.ReadWrite then
         begin
            EnableWrite := True;
         end;
      end;
   end;
   with Sender as TPopupMenu do
   begin
      for i := 0 to Items.Count - 1 do
      begin
         if Items[i].Tag = 1 then
         begin
            Items[i].Enabled := EnableWrite;
         end;
      end;
   end;
end;

procedure TMainForm.SpeedButton5Click(Sender: TObject);
begin
   // go to the selected node's parent
   if Assigned(TreeView1.Selected.Parent) then
   begin
      TreeView1.Selected := TreeView1.Selected.Parent;
   end;
end;

procedure TMainForm.LoadUserLib;
begin
   try
      if length(Options.PasswdFile) > 0 then
      begin
         UserLib.ParsePasswd(Options.PasswdFile);
      end;
      if length(Options.GroupFile) > 0 then
      begin
         UserLib.ParseGroup(Options.GroupFile);
      end;
   except
      on E : Exception do
      begin
         MessageDlg('Loading user libs' + #10 + E.Message, mtError, [mbOK], 0);
      end;
   end;
end;


procedure TMainForm.View2Click(Sender: TObject);
var
   Data     : TNodeData;
   FileName : String;
   r        : DWORD;
   StartupInfo : TStartupInfo;
   ProcessInfo : TProcessInformation;

begin
   // export the file to temp and call shellexecute on it....?
   // if it not a known type, use notepad

   // find the file that is selected
   if ListView2.Focused then
   begin
      if ListView2.SelCount = 1 then
      begin
         Data := TNodeData(ListView2.Selected.Data);
         if Assigned(Data) then
         begin
            if ((Data.Mode and S_IFMT) = S_IFREG) or ((Data.Mode and S_IFMT) = S_IFLNK) and (Data.Size > ((EXT2_N_BLOCKS * sizeof(ULONG)))) then
            begin
               FileName := GetTempDir + Data.FileName;
               if Options.ViewText then
               begin
                  SaveFile(Data, FileName, nil, true);
               end
               else
               begin
                  SaveFile(Data, FileName, nil, false);
               end;
               r := ShellExecute(Handle, nil, PChar(FileName), nil, nil, SW_SHOWDEFAULT);
               if R <= 32 then
               begin
                  // there was an error
                  if r = SE_ERR_NOASSOC then
                  begin
                     // run notepad?
                     ZeroMemory(@StartupInfo, sizeof(StartupInfo));
                     FileName := Options.ViewerProg + ' ' + FileName;
                     if not CreateProcess(nil, PChar(FileName), nil, nil, False, CREATE_NEW_PROCESS_GROUP, nil, nil, StartupInfo, ProcessInfo) then
                     begin
                        MessageDlg('Error ' + IntToStr(GetLastError) + ' viewing file', mtError, [mbOK], 0);
                     end;
                  end
                  else
                  begin
                     MessageDlg('Error ' + IntToStr(R) + ' viewing file', mtError, [mbOK], 0);
                  end;
               end;
            end
            else
            begin
               MessageDlg('Can''t handle anything but regular files at the moment', mtInformation, [mbOK], 0);
            end;
         end;
      end
   end;
end;

procedure TMainForm.BlockDevice1Click(Sender: TObject);
var
   Data     : TNodeData;
   Expand   : Boolean;
   Dlg      : TCreateDeviceDlg;
   Flags    : ULong;
begin
   if ListView2.Focused then
   begin
      Data := TNodeData(ListedNode.Data);
      if Assigned(Data) then
      begin
         Dlg := TCreateDeviceDlg.Create(self);
         try
            if Sender = BlockDevice1 then
            begin
               Dlg.DeviceType.ItemIndex := 0;
            end
            else
            begin
               Dlg.DeviceType.ItemIndex := 1;
            end;
            if Dlg.ShowModal = mrOK then
            begin
               if Dlg.DeviceType.ItemIndex = 0 then
               begin
                  Flags := S_IFBLK;
               end
               else
               begin
                  Flags := S_IFCHR;
               end;

               Data.Partition.CreateNode(Data.INode, Dlg.NameEdit.Text, Flags, StrToInt(Dlg.MajorEdit.Text), StrToInt(Dlg.MinorEdit.Text));

               // update screen...
               Expand := ListedNode.Expanded;
               PruneNode(ListedNode);
               LoadNode(ListedNode);
               if Expand then
               begin
                  ListedNode.Expand(False);
               end;
            end;
         finally
            Dlg.Free;
         end;
      end;
   end;
end;

procedure TMainForm.SymbolicLink1Click(Sender: TObject);
var
   Data     : TNodeData;
   Expand   : Boolean;
   Dlg      : TCreateLinkDlg;
begin
   if ListView2.Focused then
   begin
      Data := TNodeData(ListedNode.Data);
      if Assigned(Data) then
      begin
         Dlg := TCreateLinkDlg.Create(self);
         try
            if Dlg.ShowModal = mrOK then
            begin
               Data.Partition.CreateSymLink(Data.INode, Dlg.NameEdit.Text, Dlg.DestinationEdit.Text);
            end;
         finally
            Dlg.Free;
         end;
      end;
      Expand := ListedNode.Expanded;
      PruneNode(ListedNode);
      LoadNode(ListedNode);
      if Expand then
      begin
         ListedNode.Expand(False);
      end;
   end;
end;

procedure TMainForm.ImportArchive1Click(Sender: TObject);
{var
   Node     : TTreeNode;
   Data     : TNodeData;
   Expand   : Boolean;
   NewINode : ULong;
   INodeO   : TINode;
   Dlg      : TSavingDlg;
   TarFile  : TTarFile;}
begin
{   Node := TreeView1.Selected;
   if Assigned(Node) and Assigned(Node.Data) then
   begin
      if ImportArchiveDialog.Execute then
      begin
         Data := TNodeData(Node.Data);

         TarFile := TTarFile.Create;
         try
            TarFile.OpenTar(ImportArchiveDialog.FileName);
            TarFile.Extract(Data.Partition, Data.INode);
            TarFile.DumpFile;
         finally
            TarFile.Free;
         end;

         // remember to update the display....!
         // what do we need to free?
         Expand := Node.Expanded;
         PruneNode(Node);
         LoadNode(Node);
         if Expand then
         begin
            Node.Expand(False);
         end;
      end;
   end;}
end;

procedure TMainForm.TreeView1Click(Sender: TObject);
var
   Data : TNodeData;
   Node : TTreeNode;
begin
   try
      Node := TreeView1.Selected;
      if Assigned(Node) then
      begin
         Data := TNodeData(Node.Data);
         if Assigned(Data) then
         begin
            ComboBox1.Text := Node.Text;
            if not Data.Loaded then
            begin
               LoadNode(Node);
            end;
            ListNodeFiles(Node);
            if Data.Loaded then
            begin
               ListNodeFiles(Node);
            end;
         end;
      end;
   except
      on E : Exception do
      begin
         MessageDlg(E.Message, mtError, [mbOK], 0);
         TreeView1.Items.EndUpdate;
      end;
   end;
end;

procedure TMainForm.TreeView1KeyPress(Sender: TObject; var Key: Char);
begin
   if Key = #13 then
   begin
      TreeView1Click(Sender);
      Key := #0;
   end;
end;

procedure TMainForm.ListView2Compare(Sender: TObject; Item1,
  Item2: TListItem; Data: Integer; var Compare: Integer);
var
   I1 : TNodeData;
   I2 : TNodeData;
begin
   I1 := Item1.Data;
   I2 := Item2.Data;
   case Options.SortColumn of
      0: // name
         Compare := CompareStr(I1.FileName, I2.FileName);
      1: // size
         Compare := I1.Size - I2.Size;
      2: // type
         Compare := (I1.mode and S_IFMT) - (I2.mode and S_IFMT);
      3: // time
         Compare := I1.mtime - I2.mtime;
      4: // UID
         Compare := I1.UID - I2.UID;
      5: // GID
         Compare := I1.GID - I2.GID;
      6: // Attributes
         Compare := CompareStr(I1.ModeString, I2.ModeString);
   end;
   if Options.SortDir then
   begin
      Compare := - Compare;
   end
end;

procedure TMainForm.ListView2ColumnClick(Sender: TObject;
  Column: TListColumn);
begin
   if Column.Index = Options.SortColumn then
   begin
      Options.SortDir := not Options.SortDir;
   end
   else
   begin
      Options.SortColumn := Column.Index;
   end;

   ReListNodeFiles;
end;


procedure TMainForm.ListView2MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
   DragDrop : TDropSource;
   i        : Integer;
   Data     : TNodeData;
begin
   if ssLeft in Shift then
   begin
      if ListView2.SelCount > 0 then
      begin
         // we should build a list of files....
         FilesToDrop := TStringList.Create;

         for i := 0 to ListView2.Items.Count - 1 do
         begin
            if ListView2.Items[i].Selected then
            begin
               Data := ListView2.Items[i].Data;
               FilesToDrop.AddObject(Data.FileName, Data);
            end
         end;

         DragDrop := TDropSource.Create(self);
         try
            DropFilesDlg := TSavingDlg.Create(self);
            try
               DragDrop.fOnCreateFileGroup    := CreateFileGroupDescriptor;
               DragDrop.fOnCreateFileContents := CreateFileContents;
               DragDrop.Execute;
               FilesToDrop.Free;
            finally
               DropFilesDlg.Free;
            end;
         finally
            DragDrop.Free;
         end;
      end;
   end;
end;

function TMainForm.CreateFileGroupDescriptor : HGLOBAL;
var
   nItems   : Integer;
   hg       : HGLOBAL;
   pfgd     : PFILEGROUPDESCRIPTOR;
   i        : integer;
   FileName : String;
   Data     : TNodeData;
begin
   nItems := FilesToDrop.Count;

   hg := GlobalAlloc(GMEM_MOVEABLE or GMEM_SHARE, sizeof(FILEGROUPDESCRIPTOR) + (nItems - 1) * sizeof(FILEDESCRIPTOR));
   if hg <> 0 then
   begin
      pfgd := PFILEGROUPDESCRIPTOR(GlobalLock(hg));
      if Assigned(pfgd) then
      begin
         pfgd.cItems := nItems;
         for i := 0 to nItems - 1 do
         begin
            FileName := FilesToDrop[i];
            Data := TNodeData(FilesToDrop.Objects[i]);

            if Assigned(Data) then
            begin
   	         pfgd.fgd[i].dwFlags := FD_ATTRIBUTES;
   	         pfgd.fgd[i].dwFileAttributes := FILE_ATTRIBUTE_NORMAL;

               StrLCopy(pfgd.fgd[i].cFileName, PChar(FileName), sizeof(pfgd.fgd[i].cFileName));
            end
            else
            begin
               // a directory...FILE_ATTRIBUTE_DIRECTORY
   	         pfgd.fgd[i].dwFlags := FD_ATTRIBUTES;
   	         pfgd.fgd[i].dwFileAttributes := FILE_ATTRIBUTE_DIRECTORY;

//               FileName := Data.FileName;
               StrLCopy(pfgd.fgd[i].cFileName, PChar(FileName), sizeof(pfgd.fgd[i].cFileName));
            end;
         end;

         GlobalUnlock(hg);
      end;
   end;
   Result := hg;
end;

{function TMainForm.CreateFileContents2(Index : Integer) : String;
var
   Data      : TNodeData;
   FileName  : String;
begin
   // Index is into FilesToDrop

   // Extract file to /temp
   // read file and put it into memory

   // I am sure that is should be possible to say 'The data is in a file in the temp directory'
   // but for now I want to get something working.  I'll worry about that later.

   // If anybody knows how to do this, I would welcome some code...

   Data := TNodeData(FilesToDrop[Index]);

   FileName := GetTempDir + 'extract.dat';

   SaveFile(Data, FileName , nil, false);

   Result := FileName;
end;}

function TMainForm.CreateFileContents(Index : Integer) : HGLOBAL;
var
   hg    : HGLOBAL;
   psz   : Pointer;
   FileSize : DWORD;

   Data      : TNodeData;
   FileName  : String;
   h         : THandle;
   BytesRead : DWORD;
   Error     : DWORD;
begin
   // Index is into FilesToDrop

   // Extract file to /temp
   // read file and put it into memory

   // I am sure that is should be possible to say 'The data is in a file in the temp directory'
   // but for now I want to get something working.  I'll worry about that later.

   // If anybody knows how to do this, I would welcome some code...

   if Index = 0 then
   begin
      DropFilesDlg.Display(FilesToDrop.Count);
   end;

   Data := TNodeData(FilesToDrop.Objects[Index]);

   FileName := GetTempDir + 'extract.dat';

   SaveFile(Data, FileName , DropFilesDlg, false);

   FileSize := Data.Size;

   hg := GlobalAlloc(GMEM_MOVEABLE or GMEM_SHARE, FileSize);

   if hg > 0 then
   begin
      psz := GlobalLock(hg);
      if Assigned(psz) then
      try
         // open FileName and read the data out........
         h := CreateFile(Pchar(FileName), GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);
         try
            if h <> INVALID_HANDLE_VALUE then
            begin
               if not ReadFile2(h, psz, FileSize, BytesRead, nil) then
               begin
                  Error := GetLastError;
                  MessageDlg('Error ' + IntToStr(Error) + ' reading file!', mtError, [mbOK], 0);
               end;
            end;
         finally
            CloseHandle(h);
         end;
      finally
         GlobalUnlock(hg);
      end;
   end
   else
   begin
      MessageDlg('Error allocating memory!', mtError, [mbOK], 0);
   end;
   Result := hg;
end;

function TMainForm.GetTempDir : String;
   function AdjustString(S : String) : String;
   var
      i : Integer;
   begin
      for i := 1 to Length(S) do
      begin
         if S[i] = #0 then
         begin
            Result := Copy(S, 1, i - 1);
            break;
         end;
      end;
   end;
begin
   if Length(TempDir) = 0 then
   begin
      SetLength(TempDir, 256);
      GetTempPath(256, PChar(TempDir));
      TempDir := AdjustString(TempDir);
   end;
   Result := TempDir;
end;

procedure TMainForm.Refresh1Click(Sender: TObject);
begin
   ReListNodeFiles;
end;

procedure TMainForm.TreeView1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
   DragDrop : TDropSource;
begin
   if ssRight in Shift then // drag with right mouse button for now (safer)
   begin
      begin
         // we should build a list of files....
         FilesToDrop := TStringList.Create;

         BuildSaveDirectory(TreeView1.Selected, '\'); // This will load the sub-directories and add all the necessary entries to the FilesToDrop List

         DragDrop := TDropSource.Create(self);
         try
            DropFilesDlg := TSavingDlg.Create(self);
            try
               DragDrop.fOnCreateFileGroup    := CreateFileGroupDescriptor;
               DragDrop.fOnCreateFileContents := CreateFileContents;
               DragDrop.Execute;
               FilesToDrop.Free;
            finally
               DropFilesDlg.Free;
            end;
         finally
            DragDrop.Free;
         end;
      end;
   end;
end;

procedure TMainForm.BuildSaveDirectory(Node : TTreeNode; DirName : String);
var
   i     : Integer;
   Data  : TNodeData;
   Child : TNodeData;
begin
   // a file gets 'relative dir\filename', data
   // a dir gets 'relative dir\', nil

   LoadNode(Node); // Make sure
   Data := Node.Data;

   // add the dir
   FilesToDrop.AddObject(DirName + Data.FileName, nil);

   DirName := DirName + Data.FileName + '\';

   // add the children
   if Assigned(Data.Children) then
   begin
      for i := 0 to Data.Children.Count - 1 do
      begin
         Child := TNodeData(Data.Children[i]);
         if Assigned(Child) then
         begin
            if (Child.Mode and S_IFMT) = S_IFREG then
            begin
               FilesToDrop.AddObject(DirName + Child.FileName, Child);
            end;
         end;
      end;
   end;
   // what about the directories?
   for i := 0 to Node.Count - 1 do
   begin
      BuildSaveDirectory(Node.Item[i], DirName);
   end;
end;


procedure TMainForm.TreeView1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
   Node : TTreeNode;
begin
   Node := TreeView1.GetNodeAt(X, Y);
   if Assigned(Node) then
   begin
      TreeView1.Selected := Node;
   end;
end;

procedure TMainForm.ListView2DropFiles(Sender: TObject; Files: TStringList;
  Point: TPoint);
var
   Node     : TTreeNode;
   Data     : TNodeData;
   Expand   : Boolean;
   NewINode : ULong;
   INodeO   : TINode;
   Dlg      : TSavingDlg;
   i        : Integer;
   Attr     : Integer;
   DirName  : String;
   p        : Integer;
begin
   // Copied from Import File....

   Node := TreeView1.Selected;
   if Assigned(Node) and Assigned(Node.Data) then
   begin
      begin
         Data := TNodeData(Node.Data);
         Dlg := TSavingDlg.Create(self);
         try
            Dlg.Display(Files.Count);

            for i := 0 to Files.Count - 1 do
            begin
               // See if this is a file...
               Attr := FileGetAttr(Files[i]);
               if (Attr) and faDirectory = faDirectory then
               begin
                  // get the name of the directory
                  DirName := Files[i];
                  while True do
                  begin
                     P := pos('\', DirName);
                     if P = 0 then break;
                     Delete(DirName, 1, P);
                  end;

                  NewINode := Data.Partition.MakeDirectory(Data.Inode, DirName);
                  // add lots of files.....
                  INodeO := TINode.Create(Data.Partition, NewINode);
                  try
                     Dlg.Display(1);
                     INodeO.ImportDirectory(Files[i], Dlg, false);
                  finally
                     INodeO.Free;
                  end;
               end
               else
               begin
                  NewINode := Data.Partition.CreateFile(Data.Inode, ExtractFileName(Files[i]));
                  INodeO := TINode.Create(Data.Partition, NewINode);
                  try
                     INodeO.ReadFromFile(Files[i], Dlg, false);
                  finally
                     INodeO.Free;
                  end;
               end;
               Dlg.Next;
            end;
         finally
            Dlg.Free;
         end;

         // remember to update the display....!
         // what do we need to free?
         Expand := Node.Expanded;
         PruneNode(Node);
         LoadNode(Node);
         if Expand then
         begin
            Node.Expand(False);
         end;
      end;
   end;
end;

procedure TMainForm.SyncTimerTimer(Sender: TObject);
var
   i : Integer;
begin
   // for each partition
   for i := 0 to Partitions.Count - 1 do
   begin
      if TLinuxPartition(Partitions[i]).ReadWrite then
      begin
         Debug('Syncing ' + TLinuxPartition(Partitions[i]).Description, DebugHigh);
         TLinuxPartition(Partitions[i]).Sync;
      end;
   end;
end;

end.
