exit from pyview by ESC
[lfm.git] / lfm / lfm.py
blobc5f90713007d0279cad06fc0957c18513122674d
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2001-7 Iñigo Serna
5 # Time-stamp: <2007-09-02 21:25:16 inigo>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 u"""lfm v2.0 - (C) 2001-7, by Iñigo Serna <inigoserna@telefonica.net>
23 'Last File Manager' is a file manager for UNIX console which born with
24 midnight commander as model. Released under GNU Public License, read
25 COPYING file for more details.
27 Usage:\tlfm <options> [path1 [path2]]
29 Arguments:
30 path1 Directory to show in left pane
31 path2 Directory to show in right pane
33 Options:
34 -1 Start in 1-pane mode
35 -2 Start in 2-panes mode (default)
36 -d, --debug Create debug file
37 -h, --help Show help
38 """
41 __author__ = u'Iñigo Serna'
42 __revision__ = '2.0'
45 import os, os.path
46 import sys
47 import time
48 import getopt
49 import logging
50 import curses
52 from __init__ import *
53 from config import Config, colors
54 import files
55 import actions
56 import utils
57 import vfs
58 import messages
59 import pyview
62 ######################################################################
63 ##### Global variables
64 LOG_FILE = os.path.join(os.getcwd(), 'lfm.log')
67 ######################################################################
68 ##### Lfm main class
69 class Lfm:
70 """Main application class"""
72 def __init__(self, win, prefs):
73 self.win = win # root window, needed for resizing
74 self.prefs = prefs # preferences
75 self.init_ui()
76 self.statusbar = StatusBar(self.maxh, self) # statusbar
77 self.lpane = Pane(PANE_MODE_LEFT, self) # left pane
78 self.rpane = Pane(PANE_MODE_RIGHT, self) # right pane
79 self.act_pane, self.noact_pane = self.lpane, self.rpane
80 if self.prefs.options['num_panes'] == 1:
81 self.lpane.mode = PANE_MODE_FULL
82 self.lpane.init_ui()
83 self.rpane.mode = PANE_MODE_HIDDEN
84 self.rpane.init_ui()
85 actions.app = messages.app = utils.app = vfs.app = pyview.app = self
88 def load_paths(self, paths1, paths2):
89 self.lpane.load_tabs_with_paths(paths1)
90 self.rpane.load_tabs_with_paths(paths2)
93 def init_ui(self):
94 """initialize curses stuff: windows, colors..."""
96 self.maxh, self.maxw = self.win.getmaxyx()
97 curses.cbreak()
98 curses.raw()
99 messages.cursor_hide()
101 # colors
102 if curses.has_colors():
103 prefs_colors = self.prefs.colors
104 # Translation table: color name -> curses color name
105 self.coltbl = {
106 'black': curses.COLOR_BLACK,
107 'blue': curses.COLOR_BLUE,
108 'cyan': curses.COLOR_CYAN,
109 'green': curses.COLOR_GREEN,
110 'magenta': curses.COLOR_MAGENTA,
111 'red': curses.COLOR_RED,
112 'white': curses.COLOR_WHITE,
113 'yellow': curses.COLOR_YELLOW }
114 # Initialize every color pair with user colors or with the defaults
115 color_items = ['title', 'files', 'current_file', 'messages', 'help',
116 'file_info', 'error_messages1', 'error_messages2',
117 'buttons', 'selected_file', 'current_selected_file',
118 'tabs', 'temp_files', 'document_files', 'media_files',
119 'archive_files', 'source_files', 'graphics_files',
120 'data_files']
121 for i, color_name in enumerate(color_items):
122 curses.init_pair(i+1,
123 self.__set_color(prefs_colors[color_name][0],
124 self.coltbl[colors[color_name][0]]),
125 self.__set_color(prefs_colors[color_name][1],
126 self.coltbl[colors[color_name][1]]))
129 def __set_color(self, col, defcol):
130 """return curses color value if exists, otherwise return default"""
132 if self.coltbl.has_key(col):
133 return self.coltbl[col]
134 else:
135 return defcol
138 def resize(self):
139 """resize windows"""
141 h, w = self.win.getmaxyx()
142 self.maxh, self.maxw = h, w
143 if w == 0 or h == 2:
144 return
145 self.win.resize(h, w)
146 self.lpane.do_resize(h, w)
147 self.rpane.do_resize(h, w)
148 self.statusbar.do_resize(h, w)
149 self.regenerate()
150 self.display()
153 def display(self):
154 """show files pane and status bar"""
156 self.lpane.display()
157 self.rpane.display()
158 self.statusbar.display()
161 def regenerate(self):
162 """Rebuild panes' directories"""
164 self.lpane.regenerate()
165 self.rpane.regenerate()
168 def quit_program(self, icode):
169 """save settings and prepare to quit"""
171 for tab in self.lpane.tabs + self.rpane.tabs:
172 if tab.vfs:
173 vfs.exit(tab)
174 if self.prefs.options['save_conf_at_exit']:
175 self.prefs.save()
176 if icode == -1: # change directory
177 return self.act_pane.act_tab.path
178 else: # exit, but don't change directory
179 return
182 def run(self):
183 """run application"""
185 while True:
186 self.display()
187 ret = self.act_pane.manage_keys()
188 if ret < 0:
189 return self.quit_program(ret)
190 elif ret == TOGGLE_PANE:
191 if self.act_pane == self.lpane:
192 self.act_pane, self.noact_pane = self.rpane, self.lpane
193 else:
194 self.act_pane, self.noact_pane = self.lpane, self.rpane
195 elif ret == TAB_NEW:
196 tab = self.act_pane.act_tab
197 if tab.vfs:
198 path = os.path.dirname(tab.vbase)
199 else:
200 path = tab.path
201 idx = self.act_pane.tabs.index(tab)
202 newtab = TabVfs(self.act_pane)
203 newtab.init(path)
204 self.act_pane.tabs.insert(idx+1, newtab)
205 self.act_pane.act_tab = newtab
206 elif ret == TAB_CLOSE:
207 tab = self.act_pane.act_tab
208 idx = self.act_pane.tabs.index(tab)
209 self.act_pane.act_tab = self.act_pane.tabs[idx-1]
210 self.act_pane.tabs.remove(tab)
211 del tab
214 ######################################################################
215 ##### StatusBar class
216 class StatusBar:
217 """Status bar"""
219 def __init__(self, maxh, app):
220 self.app = app
221 try:
222 self.win = curses.newwin(1, 0, maxh-1, 0)
223 except curses.error:
224 print 'Can\'t create StatusBar window'
225 sys.exit(-1)
226 if curses.has_colors():
227 self.win.bkgd(curses.color_pair(1))
230 def do_resize(self, h, w):
231 self.win.resize(1, w)
232 self.win.mvwin(h-1, 0)
235 def display(self):
236 """show status bar"""
238 self.win.erase()
239 adir = self.app.act_pane.act_tab
240 maxw = self.app.maxw
241 if len(adir.selections):
242 if maxw >= 45:
243 size = 0
244 for f in adir.selections:
245 size += adir.files[f][files.FT_SIZE]
246 self.win.addstr(' %s bytes in %d files' % \
247 (num2str(size), len(adir.selections)))
248 else:
249 if maxw >= 80:
250 self.win.addstr('File: %4d of %-4d' % \
251 (adir.file_i + 1, adir.nfiles))
252 filename = adir.sorted[adir.file_i]
253 if adir.vfs:
254 realpath = os.path.join(vfs.join(self.app.act_pane.act_tab),
255 filename)
256 else:
257 realpath = files.get_realpath(adir.path, filename,
258 adir.files[filename][files.FT_TYPE])
259 realpath = utils.decode(realpath)
260 if len(realpath) > maxw - 35:
261 path = '~' + realpath[-(maxw-37):]
262 else:
263 path = realpath
264 path = utils.encode(path)
265 self.win.addstr(0, 20, 'Path: ' + path)
266 if maxw > 10:
267 try:
268 self.win.addstr(0, maxw-8, 'F1=Help')
269 except:
270 pass
271 self.win.refresh()
274 ######################################################################
275 ##### Pane class
276 class Pane:
277 """The Pane class is like a notebook containing TabVfs"""
279 def __init__(self, mode, app):
280 self.app = app
281 self.mode = mode
282 self.dims = [0, 0, 0, 0] # h, w, y0, x0
283 self.maxh, self.maxw = app.maxh, app.maxw
284 self.init_ui()
285 self.tabs = []
288 def load_tabs_with_paths(self, paths):
289 for path in paths:
290 tab = TabVfs(self)
291 err = tab.init(path)
292 if err:
293 tab.init(os.path.abspath('.'))
294 self.tabs.append(tab)
295 self.act_tab = self.tabs[0]
298 def init_ui(self):
299 self.dims = self.__calculate_dims()
300 try:
301 self.win = curses.newwin(*self.dims)
302 except curses.error:
303 print 'Can\'t create Pane window'
304 sys.exit(-1)
305 self.win.keypad(1)
306 if curses.has_colors():
307 self.win.bkgd(curses.color_pair(2))
308 self.__calculate_columns()
311 def __calculate_dims(self):
312 if self.mode == PANE_MODE_HIDDEN:
313 return (self.maxh-2, self.maxw, 0, 0) # h, w, y0, x0
314 elif self.mode == PANE_MODE_LEFT:
315 return (self.maxh-2, int(self.maxw/2), 1, 0)
316 elif self.mode == PANE_MODE_RIGHT:
317 return (self.maxh-2, self.maxw-int(self.maxw/2), 1, int(self.maxw/2))
318 elif self.mode == PANE_MODE_FULL:
319 return (self.maxh-2, self.maxw, 1, 0) # h, w, y0, x0
320 else: # error
321 messages.error('Initialize Panes Error',
322 'Incorrect pane number.\nLook for bugs if you can see this.')
323 return (self.maxh-2, int(self.maxw/2), 1, int(self.maxw/2))
326 def __calculate_columns(self):
327 self.pos_col2 = self.dims[1] - 14 # sep between size and date
328 self.pos_col1 = self.pos_col2 - 8 # sep between filename and size
331 def do_resize(self, h, w):
332 self.maxh, self.maxw = h, w
333 self.dims = self.__calculate_dims()
334 self.win.resize(self.dims[0], self.dims[1])
335 self.win.mvwin(self.dims[2], self.dims[3])
336 self.__calculate_columns()
337 for tab in self.tabs:
338 tab.fix_limits()
341 def display(self):
342 """display pane"""
344 if self.mode == PANE_MODE_HIDDEN:
345 return
346 if self.maxw < 65:
347 return
348 self.display_tabs()
349 self.display_files()
350 self.display_cursorbar()
353 def display_tabs(self):
354 tabs = curses.newpad(1, self.dims[1]+1)
355 tabs.bkgd(curses.color_pair(12))
356 tabs.erase()
357 w = self.dims[1] / 4
358 if w < 10:
359 w = 5
360 tabs.addstr(('[' + ' '*(w-2) + ']') * len(self.tabs))
361 for i, tab in enumerate(self.tabs):
362 if w < 10:
363 path = '[ %d ]' % (i+1, )
364 else:
365 if tab.vfs:
366 path = os.path.basename(tab.vbase.split('#')[0])
367 else:
368 path = os.path.basename(tab.path)
369 if path == '':
370 path = os.path.dirname(tab.path)
371 path = utils.decode(path)
372 if len(path) > w - 2:
373 path = '[%s~]' % path[:w-3]
374 else:
375 path = '[' + path + ' ' * (w-2-len(path)) + ']'
376 path = utils.encode(path)
377 if tab == self.act_tab:
378 attr = curses.color_pair(10) #| curses.A_BOLD
379 else:
380 attr = curses.color_pair(1)
381 tabs.addstr(0, i*w, path, attr)
382 tabs.refresh(0, 0, 0, self.dims[3], 1, self.dims[3]+self.dims[1]-1)
385 def get_filetypecolorpair(self, f, typ):
386 if typ == files.FTYPE_DIR:
387 return curses.color_pair(5)
388 elif typ == files.FTYPE_EXE:
389 return curses.color_pair(6) | curses.A_BOLD
390 ext = os.path.splitext(f)[1].lower()
391 files_ext = self.app.prefs.files_ext
392 if ext in files_ext['temp_files']:
393 return curses.color_pair(13)
394 elif ext in files_ext['document_files']:
395 return curses.color_pair(14)
396 elif ext in files_ext['media_files']:
397 return curses.color_pair(15)
398 elif ext in files_ext['archive_files']:
399 return curses.color_pair(16)
400 elif ext in files_ext['source_files']:
401 return curses.color_pair(17)
402 elif ext in files_ext['graphics_files']:
403 return curses.color_pair(18)
404 elif ext in files_ext['data_files']:
405 return curses.color_pair(19)
406 else:
407 return curses.color_pair(2)
410 def display_files(self):
411 tab = self.act_tab
412 self.win.erase()
414 # calculate pane width, height and vertical start position
415 w = self.dims[1]
416 if self.mode != PANE_MODE_FULL:
417 h, y = self.maxh-5, 2
418 else:
419 h, y = self.maxh-2, 0
421 # headers
422 if self.mode != PANE_MODE_FULL:
423 if self == self.app.act_pane:
424 self.win.attrset(curses.color_pair(5))
425 attr = curses.color_pair(6) | curses.A_BOLD
426 else:
427 self.win.attrset(curses.color_pair(2))
428 attr = curses.color_pair(2)
429 if tab.vfs:
430 path = vfs.join(tab)
431 else:
432 path = tab.path
433 path = utils.decode(path)
434 if len(path) > w - 5:
435 title_path = '~' + path[-w+5:]
436 else:
437 title_path = path
438 title_path = utils.encode(title_path)
439 self.win.box()
440 self.win.addstr(0, 2, title_path, attr)
441 self.win.addstr(1, 1,
442 'Name'.center(self.pos_col1-2)[:self.pos_col1-2],
443 curses.color_pair(2) | curses.A_BOLD)
444 self.win.addstr(1, self.pos_col1+2, 'Size',
445 curses.color_pair(2) | curses.A_BOLD)
446 self.win.addstr(1, self.pos_col2+5, 'Date',
447 curses.color_pair(2) | curses.A_BOLD)
448 else:
449 if tab.nfiles > h:
450 self.win.vline(0, w-1, curses.ACS_VLINE, h)
452 # files
453 for i in xrange(tab.file_z - tab.file_a + 1):
454 filename = tab.sorted[i+tab.file_a]
455 # get file info
456 res = files.get_fileinfo_dict(tab.path, filename,
457 tab.files[filename])
458 # get file color
459 if not tab.selections.count(filename):
460 if self.app.prefs.options['color_files']:
461 attr = self.get_filetypecolorpair(filename, tab.files[filename][files.FT_TYPE])
462 else:
463 attr = curses.color_pair(2)
464 else:
465 attr = curses.color_pair(10) | curses.A_BOLD
467 # show
468 if self.mode == PANE_MODE_FULL:
469 buf = tab.get_fileinfo_str_long(res, w)
470 self.win.addstr(i, 0, buf, attr)
471 else:
472 buf = tab.get_fileinfo_str_short(res, w, self.pos_col1)
473 self.win.addstr(i+2, 1, buf, attr)
475 # vertical separators
476 if self.mode != PANE_MODE_FULL:
477 self.win.vline(1, self.pos_col1, curses.ACS_VLINE, self.dims[0]-2)
478 self.win.vline(1, self.pos_col2, curses.ACS_VLINE, self.dims[0]-2)
480 # vertical scroll bar
481 y0, n = self.__calculate_scrollbar_dims(h, tab.nfiles, tab.file_i)
482 self.win.vline(y+y0, w-1, curses.ACS_CKBOARD, n)
483 if tab.file_a != 0:
484 self.win.vline(y, w-1, '^', 1)
485 if (n == 1) and (y0 == 0):
486 self.win.vline(y+1, w-1, curses.ACS_CKBOARD, n)
487 if tab.nfiles > tab.file_a + h:
488 self.win.vline(h+y-1, w-1, 'v', 1)
489 if (n == 1) and (y0 == h-1):
490 self.win.vline(h+y-2, w-1, curses.ACS_CKBOARD, n)
492 self.win.refresh()
495 def __calculate_scrollbar_dims(self, h, nels, i):
496 """calculate scrollbar initial position and size"""
498 if nels > h:
499 n = max(int(h*h/nels), 1)
500 y0 = min(max(int(int(i/h)*h*h/nels),0), h-n)
501 else:
502 y0 = n = 0
503 return y0, n
506 def display_cursorbar(self):
507 if self != self.app.act_pane:
508 return
509 if self.mode == PANE_MODE_FULL:
510 cursorbar = curses.newpad(1, self.maxw)
511 else:
512 cursorbar = curses.newpad(1, self.dims[1]-1)
513 cursorbar.bkgd(curses.color_pair(3))
514 cursorbar.erase()
516 tab = self.act_tab
517 filename = tab.sorted[tab.file_i]
519 try:
520 tab.selections.index(filename)
521 except ValueError:
522 attr = curses.color_pair(3)
523 else:
524 attr = curses.color_pair(11) | curses.A_BOLD
526 res = files.get_fileinfo_dict(tab.path, filename, tab.files[filename])
527 if self.mode == PANE_MODE_FULL:
528 buf = tab.get_fileinfo_str_long(res, self.maxw)
529 cursorbar.addstr(0, 0, buf, attr)
530 cursorbar.refresh(0, 0,
531 tab.file_i % self.dims[0] + 1, 0,
532 tab.file_i % self.dims[0] + 1, self.maxw-2)
533 else:
534 buf = tab.get_fileinfo_str_short(res, self.dims[1], self.pos_col1)
535 cursorbar.addstr(0, 0, buf, attr)
536 cursorbar.addch(0, self.pos_col1-1, curses.ACS_VLINE)
537 cursorbar.addch(0, self.pos_col2-1, curses.ACS_VLINE)
538 row = tab.file_i % (self.dims[0]-3) + 3
539 if self.mode == PANE_MODE_LEFT:
540 cursorbar.refresh(0, 0,
541 row, 1, row, int(self.maxw/2)-2)
542 else:
543 cursorbar.refresh(0, 0,
544 row, int(self.maxw/2)+1, row, self.maxw-2)
547 def regenerate(self):
548 """Rebuild tabs' directories, this is needed because panel
549 could be changed"""
551 for tab in self.tabs:
552 tab.backup()
553 tab.regenerate()
554 tab.fix_limits()
555 tab.restore()
558 def manage_keys(self):
559 self.win.nodelay(1)
560 while 1:
561 ch = self.win.getch()
562 if ch == -1: # no key pressed
563 # curses.napms(1)
564 time.sleep(0.05)
565 curses.doupdate()
566 continue
567 # if ch == 0x1B: # ESC
568 # ch = self.app.win.getch() + 0x100
569 # print 'key: \'%s\' <=> %c <=> 0x%X <=> %d' % \
570 # (curses.keyname(ch), ch & 255, ch, ch)
571 # messages.win('Keyboard hitted:',
572 # 'key: \'%s\' <=> %c <=> 0x%X <=> %d' % \
573 # (curses.keyname(ch), ch & 255, ch, ch))
574 ret = actions.do(self.act_tab, ch)
575 if ret != None:
576 return ret
578 self.app.display()
581 ######################################################################
582 ##### Vfs class
583 class Vfs:
584 """Vfs class contains files information in a directory"""
586 def __init__(self):
587 self.path = ''
588 self.nfiles = 0
589 self.files = []
590 self.sorted = []
591 self.selections = []
592 self.sort_mode = 0
593 # vfs variables
594 self.vfs = '' # vfs? if not -> blank string
595 self.base = '' # tempdir basename
596 self.vbase = self.path # virtual directory basename
599 def init_dir(self, path):
600 try:
601 app = self.pane.app
602 self.nfiles, self.files = files.get_dir(path, app.prefs.options['show_dotfiles'])
603 sortmode = app.prefs.options['sort']
604 sort_mix_dirs = app.prefs.options['sort_mix_dirs']
605 sort_mix_cases = app.prefs.options['sort_mix_cases']
607 self.sorted = files.sort_dir(self.files, sortmode,
608 sort_mix_dirs, sort_mix_cases)
609 self.sort_mode = sortmode
610 self.path = os.path.abspath(path)
611 self.selections = []
612 except (IOError, OSError), (errno, strerror):
613 return (strerror, errno)
614 # vfs variables
615 self.vfs = ''
616 self.base = ''
617 self.vbase = self.path
620 def init(self, path, old_file = ''):
621 raise NotImplementedError
624 def enter_dir(self, filename):
625 if self.vfs:
626 if self.path == self.base and filename == os.pardir:
627 vfs.exit(self)
628 self.init(os.path.dirname(self.vbase),
629 old_file=os.path.basename(self.vbase).replace('#vfs', ''))
630 else:
631 pvfs, base, vbase = self.vfs, self.base, self.vbase
632 self.init(os.path.join(self.path, filename))
633 self.vfs, self.base, self.vbase = pvfs, base, vbase
634 else:
635 if filename == os.pardir:
636 self.init(os.path.dirname(self.path),
637 old_file=os.path.basename(self.path))
638 else:
639 self.init(os.path.join(self.path, filename),
640 old_file=self.sorted[self.file_i],
641 check_oldfile=False)
644 def exit_dir(self):
645 if self.vfs:
646 if self.path == self.base:
647 vfs.exit(self)
648 self.init(os.path.dirname(self.vbase),
649 old_file=os.path.basename(self.vbase).replace('#vfs', ''))
650 else:
651 pvfs, base, vbase = self.vfs, self.base, self.vbase
652 self.init(os.path.dirname(self.path),
653 old_file=os.path.basename(self.path))
654 self.vfs, self.base, self.vbase = pvfs, base, vbase
655 else:
656 if self.path != os.sep:
657 self.init(os.path.dirname(self.path),
658 old_file=os.path.basename(self.path))
661 def backup(self):
662 self.old_file = self.sorted[self.file_i]
663 self.old_file_i = self.file_i
664 self.old_vfs = self.vfs, self.base, self.vbase
667 def restore(self):
668 try:
669 self.file_i = self.sorted.index(self.old_file)
670 except ValueError:
671 if self.old_file_i < len(self.sorted):
672 self.file_i = self.old_file_i
673 else:
674 self.file_i = len(self.sorted) - 1
675 self.vfs, self.base, self.vbase = self.old_vfs
677 del(self.old_file)
678 del(self.old_file_i)
679 del(self.old_vfs)
682 def regenerate(self):
683 """Rebuild tabs' directories"""
685 path = self.path
686 if path[-1] == os.sep:
687 path = path[:-1]
688 if path == '': path = os.sep
689 while not os.path.exists(path):
690 path = os.path.dirname(path)
692 if path != self.path:
693 self.path = path
694 self.file_i = 0
695 pvfs, base, vbase = self.vfs, self.base, self.vbase
696 self.init_dir(self.path)
697 self.vfs, self.base, self.vbase = pvfs, base, vbase
698 self.selections = []
699 else:
700 filename_old = self.sorted[self.file_i]
701 selections_old = self.selections[:]
702 pvfs, base, vbase = self.vfs, self.base, self.vbase
703 self.init_dir(self.path)
704 self.vfs, self.base, self.vbase = pvfs, base, vbase
705 try:
706 self.file_i = self.sorted.index(filename_old)
707 except ValueError:
708 self.file_i = 0
709 self.selections = selections_old[:]
710 for f in self.selections:
711 if f not in self.sorted:
712 self.selections.remove(f)
715 def refresh(self):
716 file_i_old = self.file_i
717 file_old = self.sorted[self.file_i]
718 self.pane.app.regenerate()
719 try:
720 self.file_i = self.sorted.index(file_old)
721 except ValueError:
722 self.file_i = file_i_old
723 self.fix_limits()
726 def get_fileinfo_str_short(self, res, maxw, pos_col1):
727 filewidth = maxw - 24
728 fname = utils.decode(res['filename'])
729 if len(fname) > filewidth:
730 half = int(filewidth/2)
731 fname = fname[:half+2] + '~' + fname[-half+3:]
732 fname = fname.ljust(pos_col1-2)[:pos_col1-2]
733 fname = utils.encode(fname)
734 if res['dev']:
735 buf = '%c%s %3d,%3d %12s' % \
736 (res['type_chr'], fname,
737 res['maj_rdev'], res['min_rdev'],
738 res['mtime2'])
739 else:
740 buf = '%c%s %7s %12s' % \
741 (res['type_chr'], fname,
742 res['size'], res['mtime2'])
743 return buf
746 def get_fileinfo_str_long(self, res, maxw):
747 filewidth = maxw - 57
748 fname = utils.decode(res['filename'])
749 if len(fname) > filewidth:
750 half = int(filewidth/2)
751 fname = fname[:half+2] + '~' + fname[-half+2:]
752 fname = utils.encode(fname)
753 if res['dev']:
754 buf = '%c%9s %-8s %-8s %3d,%3d %16s %s' % \
755 (res['type_chr'], res['perms'],
756 res['owner'][:8], res['group'][:8],
757 res['maj_rdev'], res['min_rdev'],
758 res['mtime'], fname)
759 else:
760 buf = '%c%9s %-8s %-8s %7s %16s %s' % \
761 (res['type_chr'], res['perms'],
762 res['owner'][:8], res['group'][:8],
763 res['size'],
764 res['mtime'], fname)
765 return buf
768 def get_file(self):
769 """return pointed file"""
770 return self.sorted[self.file_i]
773 def get_fullpathfile(self):
774 """return full path for pointed file"""
775 return os.path.join(self.path, self.sorted[self.file_i])
778 ######################################################################
779 ##### TabVfs class
780 class TabVfs(Vfs):
781 """TabVfs class is the UI container for Vfs class"""
783 def __init__(self, pane):
784 Vfs.__init__(self)
785 self.pane = pane
788 def init(self, path, old_file = '', check_oldfile=True):
789 path = os.path.expanduser(path)
790 path = os.path.expandvars(path)
791 err = self.init_dir(path)
792 if err:
793 messages.error('Enter In Directory', '%s (%d)' % err, path)
794 if (check_oldfile and old_file) or (old_file and err):
795 try:
796 self.file_i = self.sorted.index(old_file)
797 except ValueError:
798 self.file_i = 0
799 else:
800 self.file_i = 0
801 self.fix_limits()
802 return err
805 def fix_limits(self):
806 self.file_i = max(0, min(self.file_i, self.nfiles-1))
807 if self.pane.mode == PANE_MODE_HIDDEN or \
808 self.pane.mode == PANE_MODE_FULL:
809 height = self.pane.dims[0]
810 else:
811 height = self.pane.dims[0] - 3
812 self.file_a = int(self.file_i/height) * height
813 self.file_z = min(self.file_a+height-1, self.nfiles-1)
816 ######################################################################
817 ##### Utils
818 def num2str(num):
819 # Fatal in #pys60
820 # return (len(num) < 4) and num or (num2str(num[:-3]) + "." + num[-3:])
821 num_list = []
822 while num / 1000.0 >= 0.001:
823 num_list.append('%.3d' % (num % 1000))
824 num /= 1000.0
825 else:
826 num_str = '0'
827 if len(num_list) != 0:
828 num_list.reverse()
829 num_str = ','.join(num_list)
830 while num_str[0] == '0':
831 num_str = num_str[1:]
832 return num_str
835 ######################################################################
836 ##### Main
837 def usage(msg = ''):
838 if msg != "":
839 print 'lfm:\tERROR: %s\n' % msg
840 print __doc__
843 def lfm_exit(ret_code, ret_path='.'):
844 f = open('/tmp/lfm-%s.path' % (os.getppid()), 'w')
845 f.write(ret_path)
846 f.close()
847 sys.exit(ret_code)
850 def main(win, prefs, paths1, paths2):
851 app = Lfm(win, prefs)
852 app.load_paths(paths1, paths2)
853 if app == OSError:
854 sys.exit(-1)
855 ret = app.run()
856 return ret
859 def lfm_start(sysargs):
860 # get configuration & preferences
861 DEBUG = 0
862 paths1, paths2 = [], []
863 prefs = Config()
864 ret = prefs.load()
865 if ret == -1:
866 print 'Config file does not exist, we\'ll use default values'
867 prefs.save()
868 time.sleep(1)
869 elif ret == -2:
870 print 'Config file seems corrupted, we\'ll use default values'
871 prefs.save()
872 time.sleep(1)
874 # parse args
875 # hack, 'lfm' shell function returns a string, not a list,
876 # so we have to build a list
877 if len(sysargs) <= 2:
878 lst = sysargs[:]
879 sysargs = [lst[0]]
880 if len(lst) > 1:
881 sysargs.extend(lst[1].split())
882 try:
883 opts, args = getopt.getopt(sysargs[1:], '12dh', ['debug', 'help'])
884 except getopt.GetoptError:
885 usage('Bad argument(s)')
886 lfm_exit(-1)
887 for o, a in opts:
888 if o == '-1':
889 prefs.options['num_panes'] = 1
890 if o == '-2':
891 prefs.options['num_panes'] = 2
892 if o in ('-d', '--debug'):
893 DEBUG = 1
894 if o in ('-h', '--help'):
895 usage()
896 lfm_exit(2)
898 if len(args) == 0:
899 paths1.append(os.path.abspath('.'))
900 paths2.append(os.path.abspath('.'))
901 elif len(args) == 1:
902 buf = os.path.abspath(args[0])
903 if not os.path.isdir(buf):
904 usage('<%s> is not a directory' % args[0])
905 lfm_exit(-1)
906 paths1.append(buf)
907 paths2.append(os.path.abspath('.'))
908 elif len(args) == 2:
909 buf = os.path.abspath(args[0])
910 if not os.path.isdir(buf):
911 usage('<%s> is not a directory' % args[0])
912 lfm_exit(-1)
913 paths1.append(buf)
914 buf = os.path.abspath(args[1])
915 if not os.path.isdir(buf):
916 usage('<%s> is not a directory' % args[1])
917 lfm_exit(-1)
918 paths2.append(buf)
919 else:
920 usage('Incorrect number of arguments')
921 lfm_exit(-1)
923 # logging
924 if DEBUG:
925 log_file = os.path.join(os.path.abspath('.'), LOG_FILE)
926 logging.basicConfig(level=logging.DEBUG,
927 format='%(asctime)s %(levelname)s\t%(message)s',
928 datefmt='%Y-%m-%d %H:%M:%S ',
929 filename=log_file,
930 filemode='w')
931 logging.info('Starting Lfm...')
933 # main app
934 logging.info('Main application call')
935 path = curses.wrapper(main, prefs, paths1, paths2)
936 logging.info('End')
938 # change to directory
939 if path != None:
940 lfm_exit(0, path)
941 else:
942 lfm_exit(0)
945 if __name__ == '__main__':
946 lfm_start(sys.argv)
949 ######################################################################