Add icon.
[guestfs-browser.git] / filetree_markup.ml
1 (* Guestfs Browser.
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *)
18
19 open ExtString
20 open ExtList
21 open Unix
22
23 open Utils
24 open Slave_types
25 open Filetree_type
26
27 open Printf
28
29 (* Base colours. XXX Should be configurable somewhere. *)
30 let file_color = 0x20, 0x20, 0xff  (* regular file *)
31 let dir_color = 0x80, 0x80, 0x20   (* directory *)
32 let symlink_color = file_color     (* symlink *)
33 let suid_color = 0x20, 0x20, 0x80  (* setuid bit set on regular file *)
34 let suid_bgcolor = 0xff, 0xc0, 0xc0
35 let sgid_color = suid_color        (* setgid bit set on regular file *)
36 let sgid_bgcolor = suid_bgcolor
37 let block_color = 0x00, 0x60, 0x60 (* block device *)
38 let char_color = block_color       (* char device *)
39 let fifo_color = 0x60, 0x00, 0x60  (* fifo *)
40 let socket_color = fifo_color      (* socket *)
41 let other_color = file_color       (* anything not one of the above *)
42
43 (* Mark up a filename for the name_col column.
44  *
45  * See also
46  * http://library.gnome.org/devel/pango/stable/PangoMarkupFormat.html
47  *)
48 let rec markup_of_name ?(visited = false) direntry =
49   let name = direntry.dent_name in
50   let mode = direntry.dent_stat.Guestfs.mode in
51   if is_directory mode then (           (* directory *)
52     let fg = if not visited then normal dir_color else darken dir_color in
53     sprintf "<span weight=\"bold\" fgcolor=\"%s\">%s</span>"
54       fg (markup_escape name)
55   )
56   else if is_symlink mode then (        (* symlink *)
57     let link = direntry.dent_link in
58     let fg =
59       if not visited then normal symlink_color else darken symlink_color in
60     sprintf "<span style=\"italic\" fgcolor=\"%s\">%s</span> %s <span style=\"italic\" fgcolor=\"%s\">%s</span>"
61       fg (markup_escape name) utf8_rarrow fg (markup_escape link)
62   )
63   else (                                (* not directory, not symlink *)
64     let fg, bg =
65       if is_regular_file mode then (
66         if is_suid mode then suid_color, Some suid_bgcolor
67         else if is_sgid mode then sgid_color, Some sgid_bgcolor
68         else file_color, None
69       )
70       else if is_block mode then block_color, None
71       else if is_char mode then char_color, None
72       else if is_fifo mode then fifo_color, None
73       else if is_socket mode then socket_color, None
74       else other_color, None in
75     let fg = if not visited then normal fg else darken fg in
76     let bg =
77       match bg with
78       | Some bg -> sprintf " bgcolor=\"%s\"" (normal bg)
79       | None -> "" in
80     sprintf "<span fgcolor=\"%s\"%s>%s</span>"
81       fg bg (markup_escape name)
82   )
83
84 (* Mark up a registry key. *)
85 and markup_of_regkey ?(visited = false) h node =
86   let name = Hivex.node_name h node in
87   let fg = if not visited then normal dir_color else darken dir_color in
88   sprintf "<span fgcolor=\"%s\">%s</span>" fg (markup_escape name)
89
90 (* Mark up a registry value. *)
91 and markup_of_regvalue ?(visited = false) h value =
92   let k = Hivex.value_key h value in
93   let k = if k = "" then "@" else k in
94   let t, v = Hivex.value_value h value in
95
96   (* Ignore long values. *)
97   let len = String.length v in
98   let v =
99     if len >= 512 then sprintf "&lt;%d bytes not printed&gt;" len
100     else markup_escape (printable_hivex_value ~split_long_lines:true t v) in
101
102   let fg = if not visited then normal file_color else darken file_color in
103   sprintf "<span fgcolor=\"%s\">%s</span>=<span fgcolor=\"%s\">%s</span>"
104     fg (markup_escape k) fg v
105
106 and normal (r, g, b) =
107   let r = if r < 0 then 0 else if r > 255 then 255 else r in
108   let g = if g < 0 then 0 else if g > 255 then 255 else g in
109   let b = if b < 0 then 0 else if b > 255 then 255 else b in
110   sprintf "#%02x%02x%02x" r g b
111
112 and darken (r, g, b) =
113   normal (r * 4 / 10, g * 4 / 10, b * 4 / 10)
114
115 (* Mark up mode. *)
116 let markup_of_mode mode =
117   let c =
118     if is_socket mode then 's'
119     else if is_symlink mode then 'l'
120     else if is_regular_file mode then '-'
121     else if is_block mode then 'b'
122     else if is_directory mode then 'd'
123     else if is_char mode then 'c'
124     else if is_fifo mode then 'p' else '?' in
125   let ru = if is_ru mode then 'r' else '-' in
126   let wu = if is_wu mode then 'w' else '-' in
127   let xu = if is_xu mode then 'x' else '-' in
128   let rg = if is_rg mode then 'r' else '-' in
129   let wg = if is_wg mode then 'w' else '-' in
130   let xg = if is_xg mode then 'x' else '-' in
131   let ro = if is_ro mode then 'r' else '-' in
132   let wo = if is_wo mode then 'w' else '-' in
133   let xo = if is_xo mode then 'x' else '-' in
134   let str = sprintf "%c%c%c%c%c%c%c%c%c%c" c ru wu xu rg wg xg ro wo xo in
135
136   let suid = is_suid mode in
137   let sgid = is_sgid mode in
138   let svtx = is_svtx mode in
139   if suid then str.[3] <- 's';
140   if sgid then str.[6] <- 's';
141   if svtx then str.[9] <- 't';
142
143   "<span color=\"#222222\" size=\"small\">" ^ str ^ "</span>"
144
145 (* Mark up dates. *)
146 let markup_of_date t =
147   (* Guestfs gives us int64's, we want float which is OCaml's
148    * equivalent of time_t.
149    *)
150   let t = Int64.to_float t in
151
152   let show_full_date () =
153     let tm = localtime t in
154     sprintf "<span color=\"#222222\" size=\"small\">%04d-%02d-%02d %02d:%02d:%02d</span>"
155       (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
156       tm.tm_hour tm.tm_min tm.tm_sec
157   in
158
159   (* How long ago? *)
160   let now = time () in
161   let ago = now -. t in
162   if ago < 0. then (* future *)
163     show_full_date ()
164   else if ago < 60. then
165     "<small>now</small>"
166   else if ago < 60. *. 60. then
167     sprintf "<small>%.0f minutes ago</small>" (ago /. 60.)
168   else if ago < 60. *. 60. *. 24. then
169     sprintf "<small>%.0f hours ago</small>" (ago /. 60. /. 60.)
170   else if ago < 60. *. 60. *. 24. *. 28. then
171     sprintf "<small>%.0f days ago</small>" (ago /. 60. /. 60. /. 24.)
172   else
173     show_full_date ()
174
175 (* Mark up file sizes. *)
176 let markup_of_size bytes =
177   sprintf "<small>%s</small>" (human_size bytes)
178
179 (* Mark up registry value types. *)
180 let markup_of_regvaluetype h value =
181   let t, _ = Hivex.value_value h value in
182
183   match t with
184   | Hivex.REG_NONE -> "none(0)"
185   | Hivex.REG_SZ -> "str(1)"
186   | Hivex.REG_EXPAND_SZ -> "str(2)"
187   | Hivex.REG_BINARY -> "hex(3)"
188   | Hivex.REG_DWORD -> "dword(4)"
189   | Hivex.REG_DWORD_BIG_ENDIAN -> "dword(5)"
190   | Hivex.REG_LINK -> "link(6)"
191   | Hivex.REG_MULTI_SZ -> "multi string (7)"
192   | Hivex.REG_RESOURCE_LIST -> "resource list (8)"
193   | Hivex.REG_FULL_RESOURCE_DESCRIPTOR -> "full resource descriptor (9)"
194   | Hivex.REG_RESOURCE_REQUIREMENTS_LIST -> "resource requirements list (10)"
195   | Hivex.REG_QWORD -> "qword (11)"
196   | Hivex.REG_UNKNOWN i32 -> sprintf "type 0x%08lx" i32
197
198 (* Mark up registry value sizes. *)
199 let markup_of_regvaluesize h value =
200   let _, len = Hivex.value_type h value in
201   sprintf "%d" len
202
203 (* This is a bit of a hack.  Ideally just setting 'visited' would
204  * darken the colour when the cell was re-rendered.  However that would
205  * mean we couldn't store other stuff in the name column.  Therefore,
206  * repopulate the name column.
207  *)
208 let set_visited ({ model = model; name_col = name_col } as t) row =
209   let hdata = get_hdata t row in
210   if hdata.visited = false then (
211     hdata.visited <- true;
212     match hdata.content with
213     | Directory direntry | File direntry ->
214         debug "set_visited %s" direntry.dent_name;
215         model#set ~row ~column:name_col
216           (markup_of_name ~visited:true direntry)
217     | RegKey node ->
218         debug "set_visited RegKey";
219         let h = Option.get hdata.hiveh in
220         model#set ~row ~column:name_col
221           (markup_of_regkey ~visited:true h node)
222     | RegValue value ->
223         debug "set_visited RegValue";
224         let h = Option.get hdata.hiveh in
225         model#set ~row ~column:name_col
226           (markup_of_regvalue ~visited:true h value)
227     | Loading | ErrorMessage _ | Info _ | Top _ | TopWinReg _ -> ()
228   )