Version 0.1.5.
[guestfs-browser.git] / filetree_ops.ml
index c273f30..dcea59d 100644 (file)
 open Printf
 
 open Utils
+open Slave_types
 
 open Filetree_type
+open Filetree_markup
+
+(* Temporary directory for shared use by any function in this file.
+ * It is cleaned up when the program exits.
+ *)
+let tmpdir = tmpdir ()
 
 (* Get the basename of a file, using path conventions which are valid
  * for libguestfs.  So [Filename.basename] won't necessarily work
@@ -35,6 +42,19 @@ let basename pathname =
   with
     Not_found -> pathname
 
+(* Get the extension of a file using libguestfs path conventions,
+ * including the leading point (eg. ".txt").  Might return an empty
+ * string if there is no extension.
+ *)
+let extension pathname =
+  let len = String.length pathname in
+  try
+    let i = String.rindex pathname '.' in
+    let r = String.sub pathname i (len-i) in
+    r
+  with
+    Not_found -> ""
+
 (* Download a single file. *)
 let rec download_file ({ model = model } as t) path () =
   let row = model#get_iter path in
@@ -58,13 +78,12 @@ let rec download_file ({ model = model } as t) path () =
           dlg#destroy ();
 
           (* Download the file. *)
-          update_status t
-            (sprintf "Downloading %s to %s ..." pathname localfile);
           Slave.download_file src pathname localfile
-            (when_downloaded_file t pathname localfile)
+            (when_downloaded_file t path)
 
-and when_downloaded_file t _ localfile () =
-  update_status t (sprintf "Finished downloading %s" localfile)
+and when_downloaded_file ({ model = model } as t) path () =
+  let row = model#get_iter path in
+  set_visited t row
 
 (* Download a directory as a tarball. *)
 let rec download_dir_tarball ({ model = model } as t) format path () =
@@ -79,9 +98,9 @@ let rec download_dir_tarball ({ model = model } as t) format path () =
   dlg#add_select_button_stock `SAVE `SAVE;
 
   let extension = match format with
-    | Slave.Tar -> ".tar"
-    | Slave.TGZ -> ".tar.gz"
-    | Slave.TXZ -> ".tar.xz"
+    | Tar -> ".tar"
+    | TGZ -> ".tar.gz"
+    | TXZ -> ".tar.xz"
   in
   dlg#set_current_name (basename pathname ^ extension);
 
@@ -95,13 +114,12 @@ let rec download_dir_tarball ({ model = model } as t) format path () =
           dlg#destroy ();
 
           (* Download the directory. *)
-          update_status t
-            (sprintf "Downloading %s to %s ..." pathname localfile);
           Slave.download_dir_tarball src pathname format localfile
-            (when_downloaded_dir_tarball t pathname localfile)
+            (when_downloaded_dir_tarball t path)
 
-and when_downloaded_dir_tarball t _ localfile () =
-  update_status t (sprintf "Finished downloading %s" localfile)
+and when_downloaded_dir_tarball ({ model = model } as t) path () =
+  let row = model#get_iter path in
+  set_visited t row
 
 let rec download_dir_find0 ({ model = model } as t) path () =
   let row = model#get_iter path in
@@ -136,10 +154,257 @@ let rec download_dir_find0 ({ model = model } as t) path () =
           dlg#destroy ();
 
           (* Download the directory. *)
-          update_status t
-            (sprintf "Downloading filenames in %s to %s ..." pathname localfile);
           Slave.download_dir_find0 src pathname localfile
-            (when_downloaded_dir_find0 t pathname localfile)
+            (when_downloaded_dir_find0 t path)
+
+and when_downloaded_dir_find0 ({ model = model } as t) path () =
+  let row = model#get_iter path in
+  set_visited t row
+
+let has_child_node_equals t row content =
+  try ignore (find_child_node_by_content t row content); true
+  with Not_found -> false
+
+(* Calculate disk space used by a directory. *)
+let rec disk_usage ({ model = model } as t) path () =
+  t.view#expand_row path;
+
+  let row = model#get_iter path in
+  let src, pathname = get_pathname t row in
+  debug "disk_usage %s" pathname;
+
+  (* See if this node already has an Info "disk_usage" child node.  If
+   * so they don't recreate it.
+   *)
+  let content = Info "disk_usage" in
+  if not (has_child_node_equals t row content) then (
+    (* Create the child node first. *)
+    let row = model#insert ~parent:row 0 in
+    let hdata = { state=IsLeaf; content=content; visited=false; hiveh=None } in
+    store_hdata t row hdata;
+    model#set ~row ~column:t.name_col "<i>Calculating disk usage ...</i>";
+
+    Slave.disk_usage src pathname (when_disk_usage t path pathname)
+  )
+
+and when_disk_usage ({ model = model } as t) path pathname kbytes =
+  let row = model#get_iter path in
+
+  (* Find the Info "disk_usage" child node added above, and replace the
+   * text in it with the final size.
+   *)
+  try
+    let content = Info "disk_usage" in
+    let row = find_child_node_by_content t row content in
+    let msg =
+      sprintf "<b>%s</b>\n<small>Disk usage of %s (%Ld KB)</small>"
+        (human_size_1k kbytes) pathname kbytes in
+    model#set ~row ~column:t.name_col msg
+  with
+    Not_found -> ()
+
+(* Display operating system inspection information. *)
+let display_inspection_data ({ model = model } as t) path () =
+  t.view#expand_row path;
+
+  let row = model#get_iter path in
+  let src, _ = get_pathname t row in
+  debug "display_inspection_data";
+
+  (* Should be an OS source, if not ignore. *)
+  match src with
+  | Volume _ -> ()
+  | OS os ->
+      (* See if this node already has an Info "inspection_data" child
+       * node.  If so they don't recreate it.
+       *)
+      let content = Info "inspection_data" in
+      if not (has_child_node_equals t row content) then (
+        let row = model#insert ~parent:row 0 in
+        let hdata =
+          { state=IsLeaf; content=content; visited=false; hiveh=None } in
+        store_hdata t row hdata;
+
+        (* XXX UGHLEE *)
+        let data =
+          sprintf "Type: <b>%s</b>\nDistro: <b>%s</b>\nVersion: <b>%d.%d</b>\nArch.: <b>%s</b>\nPackaging: <b>%s</b>/<b>%s</b>\n%sMountpoints:\n%s"
+            os.insp_type os.insp_distro
+            os.insp_major_version os.insp_minor_version
+            os.insp_arch
+            os.insp_package_management os.insp_package_format
+            (match os.insp_windows_systemroot with
+             | None -> ""
+             | Some path ->
+                 sprintf "Systemroot: <b>%s</b>\n" (markup_escape path))
+            (String.concat "\n"
+               (List.map (
+                  fun (mp, dev) ->
+                    sprintf "<b>%s</b> on <b>%s</b>"
+                      (markup_escape dev) (markup_escape mp))
+                  os.insp_mountpoints)
+            ) in
+
+        model#set ~row ~column:t.name_col data
+      )
+
+(* Copy registry key value to clipboard. *)
+let copy_regvalue ({ model = model } as t) path () =
+  let row = model#get_iter path in
+  let hdata = get_hdata t row in
+  match hdata with
+  | { content=RegValue value; hiveh = Some h } ->
+      let t, v = Hivex.value_value h value in
+      let v = printable_hivex_value t v in
+      let cb = GData.clipboard Gdk.Atom.clipboard in
+      cb#set_text v
+
+  | _ -> () (* not a registry value row, ignore *)
+
+(* View a single file. *)
+let rec view_file ({ model = model } as t) path opener () =
+  let row = model#get_iter path in
+  let src, pathname = get_pathname t row in
+  debug "view_file %s" pathname;
+
+  (* Download the file into a temporary directory. *)
+  let ext = extension pathname in
+  let localfile = tmpdir // string_of_int (unique ()) ^ ext in
+  Slave.download_file src pathname localfile
+    (when_downloaded_file_for_view t path opener localfile)
+
+and when_downloaded_file_for_view ({ model = model } as t) path
+    opener localfile () =
+  let row = model#get_iter path in
+  set_visited t row;
+
+  let cmd =
+    sprintf "%s %s" (Filename.quote opener) (Filename.quote localfile) in
+  Slave.run_command cmd Slave.no_callback
+
+(* Compute the checksum of a file. *)
+let rec checksum_file ({ model = model } as t) path csumtype () =
+  let row = model#get_iter path in
+  let src, pathname = get_pathname t row in
+  debug "checksum_file %s" pathname;
+
+  (* See if this node already has an Info "checksum" child
+   * node.  If so they don't recreate it.
+   *)
+  let content = Info ("checksum:" ^ csumtype) in
+  if not (has_child_node_equals t row content) then (
+    let row = model#insert ~parent:row 0 in
+    let hdata =
+      { state=IsLeaf; content=content; visited=false; hiveh=None } in
+    store_hdata t row hdata;
+    model#set ~row ~column:t.name_col
+      (sprintf "<i>Calculating %s ...</i>" csumtype);
+
+    t.view#expand_row path;
+
+    Slave.checksum_file src pathname csumtype
+      (when_checksum_file t path pathname csumtype)
+  )
+
+and when_checksum_file ({ model = model } as t) path pathname csumtype checksum=
+  let row = model#get_iter path in
+  set_visited t row;
+
+  (* Find the child node added above, and replace the text. *)
+  try
+    let content = Info ("checksum:" ^ csumtype) in
+    let row = find_child_node_by_content t row content in
+    let msg = sprintf "%s: %s" csumtype checksum in
+    model#set ~row ~column:t.name_col msg
+  with
+    Not_found -> ()
+
+(* Compute the file information of a file. *)
+let rec file_information ({ model = model } as t) path () =
+  let row = model#get_iter path in
+  let src, pathname = get_pathname t row in
+  debug "file_information %s" pathname;
+
+  (* See if this node already has an Info "file_information" child
+   * node.  If so they don't recreate it.
+   *)
+  let content = Info "file_information" in
+  if not (has_child_node_equals t row content) then (
+    let row = model#insert ~parent:row 0 in
+    let hdata =
+      { state=IsLeaf; content=content; visited=false; hiveh=None } in
+    store_hdata t row hdata;
+    model#set ~row ~column:t.name_col "<i>Calculating file information ...</i>";
+
+    t.view#expand_row path;
+
+    Slave.file_information src pathname (when_file_information t path pathname)
+  )
+
+and when_file_information ({ model = model } as t) path pathname info =
+  let row = model#get_iter path in
+  set_visited t row;
+
+  (* Find the child node added above, and replace the text. *)
+  try
+    let content = Info "file_information" in
+    let row = find_child_node_by_content t row content in
+    model#set ~row ~column:t.name_col (markup_escape info)
+  with
+    Not_found -> ()
+
+(* Export a registry key/subkey tree as a reg file.  This is pretty
+ * effortless with hivexregedit.
+ *)
+let download_as_reg ({ model = model } as t) path hivexregedit () =
+  let row = model#get_iter path in
+  let hdata = get_hdata t row in
+      
+  (* Get path to the top of the registry tree. *)
+  let (_, rootkey, _, cachefile), nodes = get_registry_path t row in
+  let regpath = String.concat "\\" (List.rev nodes) in
+  debug "download_as_reg: %s %s %s" cachefile rootkey regpath;
+
+  let do_dialog () =
+    (* Put up the dialog. *)
+    let title = "Download as .reg file" in
+    let dlg = GWindow.file_chooser_dialog
+      ~action:`SAVE ~title ~modal:true () in
+    dlg#add_button_stock `CANCEL `CANCEL;
+    dlg#add_select_button_stock `SAVE `SAVE;
+    let name = match nodes with [] -> rootkey | (name::_) -> name in
+    dlg#set_current_name (name ^ ".reg");
+
+    match dlg#run () with
+    | `DELETE_EVENT | `CANCEL ->
+        dlg#destroy ()
+    | `SAVE ->
+        match dlg#filename with
+        | None -> ()
+        | Some localfile ->
+            dlg#destroy ();
+
+            (* Use hivexregedit to save it. *)
+            let cmd =
+              sprintf "%s --export --prefix %s %s %s > %s"
+                (Filename.quote hivexregedit)
+                (Filename.quote rootkey) (Filename.quote cachefile)
+                (Filename.quote regpath) (Filename.quote localfile) in
+            Slave.run_command cmd Slave.no_callback
+  in
+
+  match hdata with
+  | { content=RegKey _ } ->
+      do_dialog ()
+
+  | { content=TopWinReg (src, _, remotefile, cachefile) } ->
+      (* There's a subtle problem here: If the top node has not been
+       * opened, the registry cachefile won't have been downloaded.  If
+       * the top node has been opened, the registry might still be
+       * being downloaded as we are running here.  Either way we can't
+       * trust the cachefile.  Tell the slave thread to download the
+       * file if it's not downloaded already (since the slave thread
+       * runs in a serial loop, this is always race free).
+       *)
+      cache_registry_file t path src remotefile cachefile do_dialog
 
-and when_downloaded_dir_find0 t _ localfile () =
-  update_status t (sprintf "Finished downloading %s" localfile)
+  | _ -> () (* not a registry key, ignore *)