adding docstring and minor fixes
[findit] / src / findit.py
1 #!/usr/bin/env python
2 # -*-coding: utf-8 -*-
3 # vim: sw=4 ts=4 expandtab ai
4 # pylint: disable-msg=C0301
5
6 import gtk
7 import gobject
8 import pango
9 from os import walk
10 from os.path import join, abspath, normcase, basename, \
11                     isdir, getsize, getatime, getmtime, expanduser
12 from heapq import nlargest
13 import gettext
14 import time
15 from sys import platform
16
17 try: 
18     import hildon
19     hildonFound = True
20 except ImportError:
21     hildonFound = False
22
23 try:
24     # Подразумевается, что ru/LC_MESSAGES/program.mo находится в текущем каталоге (sys.path[0])
25     # Для стандартного /usr/share/locale писать gettext.translation('findit')
26     #langRU = gettext.translation('findit', sys.path[0], languages=['ru'])
27     langRU = gettext.translation('findit')
28     langRU.install()
29 except IOError:
30     # Закомментировать перед использованием pygettext
31     def _(text): 
32         return text
33
34
35 ### Common functions ###########################################################
36
37 # Функция которая возвращает строку из числа и единиц для столбца "Размер"("Size")
38 def size_convert(size):
39     """This function return string with file size in
40     b or Kb or Mb or Gb or Tb
41     """
42     for i, unit in enumerate(['%d b', '%.1f Kb', '%.2f Mb', '%.3f Gb', '%.4f Tb']):
43         if size < 1024**(i+1):
44             return unit % (size/1024.**i)
45     return '>1024 Tb'
46
47 # Функция поставляющая размер файла и путь к нему
48 def filegetter(startdir, obj):
49     """This function-generator return size 
50     and path for all files in start directory
51     """
52     # Список игнорируемых каталогов:
53     ignore_dirs = ['/dev', '/proc', '/sys', '/mnt']
54     # Проходим по всем папкам вглубь от заданного пути
55     for dirpath, dirnames, fnames in walk(startdir):
56     # Исключаем каталоги из поиска в соответствии со списком исключений
57         for ign_dir in ignore_dirs[:]:
58             for dirname in dirnames[:]:
59                 if ign_dir == normcase(join(abspath(dirpath), dirname)):
60                     dirnames.remove(dirname)
61                     ignore_dirs.remove(ign_dir)
62
63         for fname in fnames:
64             flpath = abspath(join(dirpath, fname))
65             # Выводим текущий опрашиваемый файл в строку статуса
66             obj.currFileLbl.set_text(flpath)
67             # обновляем окно
68             gtk.main_iteration()
69             # Останавливаем цикл по нажатию кнопки стоп
70             if obj.stopit:
71                 obj.stopit = False
72                 raise StopIteration
73             # Проверяем можем ли мы определить размер файла - иначе пропускаем его
74             try:
75                 flsize = getsize(flpath)
76                 # Возвращаем размер и полный путь файла
77                 yield flsize, flpath
78             except OSError:
79                 continue
80
81 # Fullscreen
82 def toggle_fullscreen(obj):
83     """This function switch and unswitch to fullscreen"""
84     if obj.fullscreen:
85         obj.window.unfullscreen()
86     else: 
87         obj.window.fullscreen()
88     obj.fullscreen = not obj.fullscreen
89
90 # Нажатие на кнопку клавиатуры
91 def on_key_press(obj, event):
92     """This function start then FullScreen button on N8xx pressed"""
93     if hildonFound and event.keyval == gtk.keysyms.F6:
94         toggle_fullscreen(obj)
95
96 ### Properties dialog ##########################################################
97
98 class PropertiesDialog(gtk.Dialog):
99     """This class describe property window"""
100     def __init__(self, path, size, bytesize):
101         """Creating new object of PropertiesDialog class"""
102         gtk.Dialog.__init__(self)
103         self.set_title( _('File properties') )
104         self.set_transient_for(app)
105         self.set_wmclass('PropertiesDialog', 'FindIT')
106         self.add_buttons(gtk.STOCK_OK, gtk.RESPONSE_OK)
107         self.set_resizable(False)
108
109         # Достаем свойства выбранного файла
110         name = basename(path)
111         access = time.strftime('%x %X', time.localtime(getatime(path)))
112         modified = time.strftime('%x %X', time.localtime(getmtime(path)))
113
114         # Таблица надписей
115         table = gtk.Table()
116         table.set_border_width(10)
117         table.set_col_spacings(10)
118         table.set_row_spacings(10)
119
120         # Надписи (подпись: значение)
121         nameLbl = gtk.Label( _('Name') )
122         nameValueLbl = gtk.Label(name)
123
124         sizeLbl = gtk.Label( _('Size') )
125         sizeValueLbl = gtk.Label(size + ' (' + `bytesize` + ' b)')
126
127         accessLbl = gtk.Label( _('Opened') )
128         accessValueLbl = gtk.Label(access)
129
130         modifiedLbl = gtk.Label( _('Modified') )
131         modifiedValueLbl = gtk.Label(modified)
132
133         # Список надписей
134         lbls = [(nameLbl,   nameValueLbl),   (sizeLbl,     sizeValueLbl),
135                 (accessLbl, accessValueLbl), (modifiedLbl, modifiedValueLbl)]
136
137         # Упаковка надписей в таблицу и выравнивание
138         for i, lbl in enumerate(lbls):
139             name, value = lbl
140             table.attach(name, 0, 1, i, i+1)
141             table.attach(value, 1, 2, i, i+1)
142             name.set_alignment(1, 0.5)
143             value.set_alignment(0, 0.5)
144
145         # Упаковка таблицы в vbox диалога
146         self.vbox.add(table)
147         self.show_all()
148         self.run()
149         self.destroy()
150
151 ### Main window ################################################################
152
153 class MainWindow(gtk.Window):
154     """This class describe main window of FindIT"""
155
156     # Окно сообщения заданного типа с заданным текстом
157     def mess_window(self, mestype, content):
158         """This function show popup message window"""
159         dialog = gtk.MessageDialog(parent=self, flags=gtk.DIALOG_MODAL,
160                                    type=mestype, buttons=gtk.BUTTONS_OK,
161                                    message_format=content)
162         dialog.set_wmclass('ErrorDialog', 'FindIT')
163         dialog.set_title( _('Error!') )
164         dialog.run()
165         dialog.destroy()
166
167     # Функция выполняющаяся при нажатии на кнопку "Показать"
168     def start_print(self, widget):
169         """This function start then "Go" button pressed.
170         Starting file search.               
171         """
172         self.start_path = self.srch_p_entr.get_text()
173         # Проверяем правильное ли значение введено
174         if isdir(self.start_path):
175             self.butt_start.set_sensitive(False)
176             self.butt_stop.set_sensitive(True)
177             self.propertiesBtn.set_sensitive(False)
178             # Получаем значение количества файлов из SpinButton
179             self.fl_cnt = int( self.file_cnt.get_value() )
180             # Очищаем список
181             self.treestore.clear()
182             # Получаем нужное количество самых больших файлов
183             for fsize, fpath in nlargest(self.fl_cnt, filegetter(self.start_path, self)):
184                 # Возвращаем значения в treeview в таком порядке - путь,
185                 # размер в Мб строкой и размер в байтах
186                 # self.treestore.append(None, [fpath.replace(self.start_path,'', 1),
187                 #        size_convert(fsize), fsize])
188
189                 # Выдает какую-то перманентную ошибку при присвоении значений treestore -
190                 # кто увидит скажите - нужна статистика
191                 try: 
192                     self.treestore.append(None, [fpath, size_convert(fsize), fsize])
193                 except SystemError:
194 #                    print 'error', fpath, size_convert(fsize), fsize
195                     self.mess_window('error','Error in %s' % fpath)
196             self.butt_start.set_sensitive(True)
197             self.butt_stop.set_sensitive(False)
198             self.propertiesBtn.set_sensitive(True)
199             self.srch_p_entr.grab_focus()
200         else:
201             # Иначе выводим окошко с ошибкой
202             self.mess_window('error', _('Invalid directory') )
203
204     # Функция выполняющаяся при нажатии на кнопку "Стоп"
205     def stop_print(self, widget):
206         """This function start then "Stop" button pressed.
207         Stoping file search.
208         """
209         self.stopit = True
210
211     # Функция выполняющаяся при нажатии на кнопку "Свойства файла"
212     def show_properties_dialog(self, *args):
213         """This function show property window"""
214         selection = self.treeview.get_selection()
215         (model, it) = selection.get_selected()
216         try:
217             path = model.get_value(it, 0)
218             size = model.get_value(it, 1)
219             bytesize = model.get_value(it, 2)
220         except (TypeError, ValueError):
221             self.mess_window('error', _('Please select file') )
222             return
223         PropertiesDialog(path, size, bytesize)
224
225     ### Window initialization ##################################################
226
227     def __init__(self, win_width, win_height, st_path):
228         """Creating new object of MainWindow class"""
229         # Создаем новое окно
230         gtk.Window.__init__(self)
231         self.set_default_size(win_width, win_height)
232         self.set_border_width(4)
233         self.fullscreen = False
234         self.connect('delete_event', gtk.main_quit)
235         self.connect("key-press-event", on_key_press)
236         self.set_wmclass('MainWindow', 'FindIT')
237
238         #########  Добавляем элементы ################
239         # 1. Строка ввода каталога с которого начинать поиск
240         #    переменная в которой храниться стартовый каталог = self.start_path
241         self.srch_p_entr = gtk.Entry()
242         self.start_path = st_path
243         self.srch_p_entr.set_text(self.start_path)
244         # Отключаем автокапитализацию(ввод первой буквы заглавной) на таблетке
245         if hildonFound:
246             self.srch_p_entr.set_property("hildon-input-mode", 'full')
247         # Нажатие Enter в поле ввода
248         self.srch_p_entr.connect("activate", self.start_print)
249
250         # 2. Кнопка "Обзор"
251
252         # 3. Надпись1 "Количество отображаемых файлов:"
253         label1 = gtk.Label( _('Files quantity') )
254
255         # 4. Окошко ввода количества файлов, мин значение=1 макс=65536 по умолчанию 10
256         #    данные храняться в переменной self.fl_cnt
257         self.fl_cnt = 10
258         if hildonFound:
259             self.file_cnt = hildon.NumberEditor(1, 99)
260             self.file_cnt.set_value(self.fl_cnt)
261         else:
262             adj = gtk.Adjustment(self.fl_cnt, 1, 65536, 1, 5, 0)
263             self.file_cnt = gtk.SpinButton(adj, 0, 0)
264
265         # 5.1 Кнопка "Показать"
266         self.butt_start = gtk.Button( _('Go') )
267         self.butt_start.connect('released', self.start_print)
268
269         # 5.2 Кнопка "Остановить"
270         self.butt_stop = gtk.Button( _('Stop') )
271         self.butt_stop.set_sensitive(False)
272         self.butt_stop.connect('clicked', self.stop_print)
273         self.stopit = False
274
275         # 5.3 Кнопка "Свойства файла"
276         self.propertiesBtn = gtk.Button( _('File properties') )
277         self.propertiesBtn.connect('clicked', self.show_properties_dialog)
278         self.propertiesBtn.set_sensitive(False)
279
280         # 6. Закладки
281
282         # 6.1 Список файлов
283         scrollwind = gtk.ScrolledWindow()
284         scrollwind.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
285
286         # Определяем переменную в которой будет храниться выводимый список
287         self.treestore = gtk.TreeStore(str, str, int)
288         self.treeview = gtk.TreeView(self.treestore)
289         # На таблетке не отображаються заголовки столбцов по умолчанию -
290         # след строка заставляет их отображаться принудительно
291         self.treeview.set_headers_visible(1)
292         self.treeview.connect('row-activated', self.show_properties_dialog)
293
294         self.treestore.append(None, ['', '', 0])
295
296         # Создаем и настраиваем колонку с размером файла
297         size_col = gtk.TreeViewColumn( _('Size') )
298         cell = gtk.CellRendererText()
299         cell.set_property('width', 90)
300         size_col.pack_start(cell, True)
301         size_col.add_attribute(cell, 'text', 1)
302         self.treeview.append_column(size_col)
303         # Создаем и настраиваем колонку с именем файла
304         path_col = gtk.TreeViewColumn( _('Path') )
305         cell2 = gtk.CellRendererText()
306         path_col.pack_start(cell2, True)
307         path_col.add_attribute(cell2, 'text', 0)
308         self.treeview.append_column(path_col)
309
310         # Добавляем сортировку для колонок
311         self.treeview.set_search_column(1)
312         path_col.set_sort_column_id(0)
313         size_col.set_sort_column_id(2)
314
315         # 6.2 Надпись "Найти"
316
317         # 6.3 Строка выводящая текущий осматриваемый файл
318         self.currFileLbl = gtk.Label()
319         self.currFileLbl.set_alignment(0, 0.5)
320         self.currFileLbl.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
321         self.currFileLbl.set_padding(2, 2)
322         currFileFrm = gtk.Frame()
323         currFileFrm.add(self.currFileLbl)
324
325         #########  Упаковываем элементы ################
326         # Создаем основной вертикальный контейнер
327         main_Vbox = gtk.VBox(False, 4)
328
329         # Создаем вспомогательный горизонтальный контейнер для Надписи1,
330         # окошка ввода количества файлов и кнопки "Показать"
331         hbox1 = gtk.HBox(False, 5)
332         # Добавляем вышеперечисленные элементы во вспомогат. контейнер
333         hbox1.pack_start(label1, False, False, 5)
334         hbox1.pack_start(self.file_cnt, False, False, 0)
335         hbox1.pack_start(self.butt_start, True, True, 0)
336         hbox1.pack_start(self.butt_stop, True, True, 0)
337         hbox1.pack_start(self.propertiesBtn, True, True, 0)
338
339         # Добавляем элементы в основной контейнер
340         main_Vbox.pack_start(self.srch_p_entr, False, False, 0)
341         main_Vbox.pack_start(hbox1, False, False, 0)
342         scrollwind.add(self.treeview)
343         main_Vbox.pack_start(scrollwind, True, True, 0)
344         main_Vbox.pack_start(currFileFrm, False, False, 0)
345
346         self.add(main_Vbox)
347
348     def run(self):
349         """This function show main window of FindIT"""
350         self.show_all()
351         gtk.main()
352
353
354 ### Main call ##################################################################
355
356 if __name__ == '__main__':
357     gobject.set_application_name( _('FindIT') )
358
359     if platform == 'win32':
360         startpath = 'c:\\'
361     else:
362         startpath = expanduser('~')
363
364     app = MainWindow(575, 345, startpath)
365     app.run()