#! /usr/local/bin/python -O """ ObexFTP Virtual FileSystem for Midnight Commander. Author: Oleg BroytMann . Copyright (C) 2004 PhiloSoft Design. License: GPL. Manipulate a cell phone's filesystem calling obexftp binary. This is a complete user-mode solution, no kernel modules required (unlike SieFS or such). The script implements all commands of Midnight VFS, except for undocumented "run"; anyway there are no runnable files in the cell phone. The script is written in Python because I love Python, the best of all languages ;), and I need to parse XML directory listings from obexftp. The script requires Midnight Commander 3.1+ (http://www.ibiblio.org/mc/), Python 2.3+ (http://www.python.org/), OpenOBEX 1.0.1+ (http://openobex.sourceforge.net/) and ObexFTP 0.10.4+ (http://triq.net/obexftp). Edit the script, and correct the shebang path, if your python is not in the /usr/local. Edit the full path to the obexftp binary (see below). Put the file in the /usr/[local/]lib/mc/extfs, and add a line "obexftp" to the /usr/[local/]lib/mc/extfs/extfs.ini. Create somewhere a transport file. The transport file may have any name, and is expected to be a text file with at least one line defining the transport to your device. Other lines in the file are ignored. First word in the line is a transport name - Bluetooth, TTY or IrDA. The name is case-insensitive. For the Bluetooth transport put there a line "Bluetooth CP:AD:RE:SS channel", where CP:AD:RE:SS is the hardware address of the device you want to connect to, and "channel" is the OBEX File Transfer channel; you can discover the address and the channel for your device by using commands like "hcitool scan" and "sdptool browse". For the TTY put the device name: "tty /dev/ttyUSB0". For the IrDA: just put "IrDA" in the file. Now run this "cd" command in the Midnight Commander (in the "bindings" file the command is "%cd"): cd transport#obexftp, where "transport" is the name of your transport file. The script uses obexftp to connect to the device and list files and directories. Please be warned that opening the VFS for the first time is VERY slow, because the script needs to scan the entire cell phone's filesystem. And there must be timeouts between connections, which don't make the scanning faster. Midnight Commander caches the result so you can browse and manipulate files and directories quickly. Please note that manipulating the filesystem using your phone's internal filemanager in parallel with the VFS leads to disagreement between the VFS cache and the phone. It is not very dangerous but inconvenient. There is no way to clear the VFS cache in Midnight Commander and reread the filesystem. You have to exit the VFS (cd /, for example) and return back using cd transport#obexftp command. Sometimes even this doesn't help - Midnight Commander shows the same cached VFS image. Exit Midnight Commander and restart it. If something goes wrong set the logging level (see setLevel() below) to INFO or DEBUG and look in the obexftp-mcextfs.log file(s). """ __version__ = "1.1.0" __revision__ = "$Id: obexftp,v 1.16 2004/07/27 17:55:15 phd Exp $" __date__ = "$Date: 2004/07/27 17:55:15 $"[7:-2] __author__ = "Oleg Broytmann " __copyright__ = "Copyright (C) 2004 PhiloSoft Design" # Change this to suite your needs obexftp_prog = "/usr/local/obex/bin/obexftp" import sys, time import os, shutil import xml.dom.minidom from tempfile import mkdtemp import logging logger = logging.getLogger('obexftp-mcextfs') logger.addHandler(logging.FileHandler('obexftp-mcextfs.log')) logger.setLevel(logging.ERROR) if len(sys.argv) < 2: logger.error("""\ ObexFTP Virtual FileSystem for Midnight Commander version %s Author: %s %s Put it in /usr/lib/mc/extfs. For more information read the source!""", __version__, __author__, __copyright__ ) sys.exit(1) # Parse ObexFTP XML directory listings class DirectoryEntry(object): """Represent a remote file or a directory""" def __init__(self, type): self.type = type self.size = 0 if type == "file": self.perm = "-rw-rw-rw-" elif type == "folder": self.perm = "drwxrwxrwx" else: raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type def mtime(self): if not hasattr(self, "modified"): # telecom return "01-01-70 0:0" date, time = self.modified.split('T') year, month, day = date[2:4], date[4:6], date[6:8] hour, minute = time[:2], time[2:4] return "%s-%s-%s %s:%s" % (month, day, year, hour, minute) mtime = property(mtime) def __repr__(self): if self.type == "file": return """<%s: type=file, name=%s, size=%s, mtime=%s at 0x%x>""" % ( self.__class__.__name__, self.name, self.size, self.mtime, id(self) ) if self.type == "folder": if hasattr(self, "modified"): return """<%s: type=directory, name=%s, mtime=%s at 0x%x>""" % ( self.__class__.__name__, self.name, self.mtime, id(self) ) else: # telecom return """<%s: type=directory, name=%s at 0x%x>""" % ( self.__class__.__name__, self.name, id(self) ) raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type def get_entries(dom, type): entries = [] for obj in dom.getElementsByTagName(type): entry = DirectoryEntry(type) attrs = obj.attributes for i in range(attrs.length): attr = attrs.item(i) setattr(entry, attr.name, attr.value) entries.append(entry) return entries def _run(*args): """Run the obexftp binary catching errors""" command = "%s %s %s" % (obexftp_prog, obexftp_args, ' '.join(args)) logger.debug("Running command %s", command) w, r, e = os.popen3(command, 'r') w.close() errors = e.read() e.close() result = r.read() r.close() logger.debug(" errors: %s", errors) logger.debug(" result: %s", result) return result, errors def recursive_list(directory='/'): """List the directory recursively""" listing, errors = _run("-l '%s'" % directory) if not listing: logger.error("Error reading XML listing: %s", errors) return dom = xml.dom.minidom.parseString(listing) directories = get_entries(dom, "folder") files = get_entries(dom, "file") for entry in directories + files: fullpath = "%s/%s" % (directory, entry.name) if fullpath.startswith('//'): fullpath = fullpath[1:] print entry.perm, "1 user group", entry.size, entry.mtime, fullpath for entry in directories: fullpath = "%s/%s" % (directory, entry.name) if fullpath.startswith('//'): fullpath = fullpath[1:] time.sleep(1) recursive_list(fullpath) def mcobex_list(): """List the entire VFS""" recursive_list() # A unique directory for temporary files tmpdir_name = None def setup_tmpdir(): global tmpdir_name tmpdir_name = mkdtemp(".tmp", "mcobex-") os.chdir(tmpdir_name) def cleanup_tmpdir(): os.chdir(os.pardir) shutil.rmtree(tmpdir_name) def mcobex_copyout(): """Get a file from the VFS""" obex_filename = sys.argv[3] real_filename = sys.argv[4] setup_tmpdir() try: _run("-g '%s'" % obex_filename) try: os.rename(os.path.basename(obex_filename), real_filename) except OSError: logger.exception("CopyOut %s to %s", obex_filename, real_filename) finally: cleanup_tmpdir() def mcobex_copyin(): """Put a file to the VFS""" obex_filename = sys.argv[3] real_filename = sys.argv[4] dirname, filename = os.path.split(obex_filename) setup_tmpdir() try: try: os.rename(real_filename, filename) _run("-c '%s' -p '%s'" % (dirname, filename)) os.rename(filename, real_filename) # by some reason MC wants the file back except OSError: logger.exception("CopyIn %s to %s", real_filename, obex_filename) finally: cleanup_tmpdir() def mcobex_rm(): """Remove a file from the VFS""" obex_filename = sys.argv[3] _run("-k '%s'" % obex_filename) def mcobex_mkdir(): """Create a directory in the VFS""" obex_dirname = sys.argv[3] _run("-C '%s'" % obex_dirname) mcobex_rmdir = mcobex_rm g = globals() command = sys.argv[1] procname = "mcobex_" + command if not g.has_key(procname): logger.error("Unknown command %s", command) sys.exit(1) def setup_transport(): """Setup transport parameters for the obexftp program""" transport_file = open(sys.argv[2], 'r') line = transport_file.readline() transport_file.close() parts = line.strip().split() transport = parts[0].lower() if transport == "bluetooth": return ' '.join(["-b", parts[1], "-B", parts[2]]) elif transport == "tty": return ' '.join(["-t", parts[1]]) elif transport == "irda": return "-i" else: logger.error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'", transport) sys.exit(1) try: obexftp_args = setup_transport() except: logger.exception("Exception while parsing the transport file") sys.exit(1) try: g[procname]() except: logger.exception("Exception during run")