1#!/usr/bin/env python
2"""
3fs.browsewin
4============
5
6Creates a window which can be used to browse the contents of a filesystem.
7To use, call the 'browse' method with a filesystem object. Double click a file
8or directory to display its properties.
9
10Requires wxPython.
11
12"""
13
14import wx
15import wx.gizmos
16
17from fs.path import isdotfile, pathsplit
18from fs.errors import FSError
19
20class InfoFrame(wx.Frame):
21
22    def __init__(self, parent, path, desc, info):
23        wx.Frame.__init__(self, parent, -1, style=wx.DEFAULT_FRAME_STYLE, size=(500, 500))
24
25        self.SetTitle("FS Object info - %s (%s)" % (path, desc))
26
27        keys = info.keys()
28        keys.sort()
29
30        self.list_ctrl = wx.ListCtrl(self, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
31
32        self.list_ctrl.InsertColumn(0, "Key")
33        self.list_ctrl.InsertColumn(1, "Value")
34
35        self.list_ctrl.SetColumnWidth(0, 190)
36        self.list_ctrl.SetColumnWidth(1, 300)
37
38        for key in sorted(keys, key=lambda k:k.lower()):
39            self.list_ctrl.Append((key, unicode(info.get(key))))
40
41        self.Center()
42
43
44
45class BrowseFrame(wx.Frame):
46
47    def __init__(self, fs, hide_dotfiles=False):
48
49        wx.Frame.__init__(self, None, size=(1000, 600))
50
51        self.fs = fs
52        self.hide_dotfiles = hide_dotfiles
53        self.SetTitle("FS Browser - " + unicode(fs))
54
55        self.tree = wx.gizmos.TreeListCtrl(self, -1, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT)
56
57        self.tree.AddColumn("File System")
58        self.tree.AddColumn("Description")
59        self.tree.AddColumn("Size")
60        self.tree.AddColumn("Created")
61
62        self.tree.SetColumnWidth(0, 300)
63        self.tree.SetColumnWidth(1, 250)
64        self.tree.SetColumnWidth(2, 150)
65        self.tree.SetColumnWidth(3, 250)
66
67        self.root_id = self.tree.AddRoot('root', data=wx.TreeItemData( {'path':"/", 'expanded':False} ))
68
69        rid = self.tree.GetItemData(self.root_id)
70
71        isz = (16, 16)
72        il = wx.ImageList(isz[0], isz[1])
73        self.fldridx     = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FOLDER,      wx.ART_OTHER, isz))
74        self.fldropenidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN,   wx.ART_OTHER, isz))
75        self.fileidx     = il.Add(wx.ArtProvider_GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, isz))
76
77        self.tree.SetImageList(il)
78        self.il = il
79
80        self.tree.SetItemImage(self.root_id, self.fldridx, wx.TreeItemIcon_Normal)
81        self.tree.SetItemImage(self.root_id, self.fldropenidx, wx.TreeItemIcon_Expanded)
82
83        self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnItemExpanding)
84        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
85
86        wx.CallAfter(self.OnInit)
87
88    def OnInit(self):
89
90        self.expand(self.root_id)
91
92
93    def expand(self, item_id):
94
95        item_data = self.tree.GetItemData(item_id).GetData()
96
97        path = item_data["path"]
98
99        if not self.fs.isdir(path):
100            return
101
102        if item_data['expanded']:
103            return
104
105        try:
106            paths = ( [(True, p) for p in self.fs.listdir(path, absolute=True, dirs_only=True)] +
107                      [(False, p) for p in self.fs.listdir(path, absolute=True, files_only=True)] )
108        except FSError, e:
109            msg = "Failed to get directory listing for %s\n\nThe following error was reported:\n\n%s" % (path, e)
110            wx.MessageDialog(self, msg, "Error listing directory", wx.OK).ShowModal()
111            paths = []
112
113
114        #paths = [(self.fs.isdir(p), p) for p in self.fs.listdir(path, absolute=True)]
115
116        if self.hide_dotfiles:
117            paths = [p for p in paths if not isdotfile(p[1])]
118
119        if not paths:
120            #self.tree.SetItemHasChildren(item_id, False)
121            #self.tree.Collapse(item_id)
122            return
123
124        paths.sort(key=lambda p:(not p[0], p[1].lower()))
125
126        for is_dir, new_path in paths:
127
128            name = pathsplit(new_path)[-1]
129
130            new_item = self.tree.AppendItem(item_id, name, data=wx.TreeItemData({'path':new_path, 'expanded':False}))
131
132            info = self.fs.getinfo(new_path)
133
134            if is_dir:
135
136                self.tree.SetItemHasChildren(new_item)
137                self.tree.SetItemImage(new_item, self.fldridx, 0, wx.TreeItemIcon_Normal)
138                self.tree.SetItemImage(new_item, self.fldropenidx, 0, wx.TreeItemIcon_Expanded)
139
140                self.tree.SetItemText(new_item, "", 2)
141
142                ct = info.get('created_time', None)
143                if ct is not None:
144                    self.tree.SetItemText(new_item, ct.ctime(), 3)
145                else:
146                    self.tree.SetItemText(new_item, 'unknown', 3)
147
148            else:
149                self.tree.SetItemImage(new_item, self.fileidx, 0, wx.TreeItemIcon_Normal)
150
151                self.tree.SetItemText(new_item, str(info.get('size', '?'))+ " bytes", 2)
152
153                ct = info.get('created_time', None)
154                if ct is not None:
155                    self.tree.SetItemText(new_item, ct.ctime(), 3)
156                else:
157                    self.tree.SetItemText(new_item, 'unknown', 3)
158
159            self.tree.SetItemText(new_item, self.fs.desc(new_path), 1)
160
161        item_data['expanded'] = True
162        self.tree.Expand(item_id)
163
164
165    def OnItemExpanding(self, e):
166
167        self.expand(e.GetItem())
168        e.Skip()
169
170    def OnItemActivated(self, e):
171
172        item_data = self.tree.GetItemData(e.GetItem()).GetData()
173        path = item_data["path"]
174        info = self.fs.getinfo(path)
175
176        info_frame = InfoFrame(self, path, self.fs.desc(path), info)
177        info_frame.Show()
178        info_frame.CenterOnParent()
179
180
181def browse(fs, hide_dotfiles=False):
182    """Displays a window containing a tree control that displays an FS
183    object. Double-click a file/folder to display extra info.
184
185    :param fs: A filesystem object
186    :param hide_dotfiles: If True, files and folders that begin with a dot will be hidden
187
188    """
189
190    app = wx.PySimpleApp()
191    frame = BrowseFrame(fs, hide_dotfiles=hide_dotfiles)
192    frame.Show()
193    app.MainLoop()
194
195
196if __name__ == "__main__":
197    from osfs import OSFS
198    home_fs = OSFS("~/")
199    browse(home_fs, True)
200