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