1 #! /usr/local/bin/python -O
4 ObexFTP Virtual FileSystem for Midnight Commander.
6 Author: Oleg BroytMann <phd@phd.pp.ru>.
7 Copyright (C) 2004 PhiloSoft Design.
10 Manipulate a cell phone's filesystem calling obexftp binary. This is a
11 complete user-mode solution, no kernel modules required (unlike SieFS or
12 such). The script implements all commands of Midnight VFS, except for
13 undocumented "run"; anyway there are no runnable files in the cell phone. The
14 script is written in Python because I love Python, the best of all languages ;),
15 and I need to parse XML directory listings from obexftp.
17 The script requires Midnight Commander 3.1+ (http://www.ibiblio.org/mc/),
18 Python 2.3+ (http://www.python.org/),
19 OpenOBEX 1.0.1+ (http://openobex.sourceforge.net/) and
20 ObexFTP 0.10.4+ (http://triq.net/obexftp).
22 Edit the script, and correct the shebang path, if your python is not in the
23 /usr/local. Edit the full path to the obexftp binary (see below). Put the file
24 in the /usr/[local/]lib/mc/extfs, and add a line "obexftp" to the
25 /usr/[local/]lib/mc/extfs/extfs.ini.
27 Create somewhere a transport file. The transport file may have any name, and
28 is expected to be a text file with at least one line defining the transport to
29 your device. Other lines in the file are ignored.
31 First word in the line is a transport name - Bluetooth, TTY or IrDA. The name
34 For the Bluetooth transport put there a line "Bluetooth CP:AD:RE:SS channel",
35 where CP:AD:RE:SS is the hardware address of the device you want to connect
36 to, and "channel" is the OBEX File Transfer channel; you can discover the
37 address and the channel for your device by using commands like "hcitool scan"
40 For the TTY put the device name: "tty /dev/ttyUSB0".
42 For the IrDA: just put "IrDA" in the file.
44 Now run this "cd" command in the Midnight Commander (in the "bindings" file
45 the command is "%cd"): cd transport#obexftp, where "transport" is the name of
46 your transport file. The script uses obexftp to connect to the device and list
47 files and directories. Please be warned that opening the VFS for the first
48 time is VERY slow, because the script needs to scan the entire cell phone's
49 filesystem. And there must be timeouts between connections, which don't make
50 the scanning faster. Midnight Commander caches the result so you can browse
51 and manipulate files and directories quickly.
53 Please note that manipulating the filesystem using your phone's internal
54 filemanager in parallel with the VFS leads to disagreement between the VFS
55 cache and the phone. It is not very dangerous but inconvenient. There is no
56 way to clear the VFS cache in Midnight Commander and reread the filesystem.
57 You have to exit the VFS (cd /, for example) and return back using cd
58 transport#obexftp command. Sometimes even this doesn't help - Midnight
59 Commander shows the same cached VFS image. Exit Midnight Commander and
62 If something goes wrong set the logging level (see setLevel() below) to INFO
63 or DEBUG and look in the obexftp-mcextfs.log file. The file is put in the same
64 directory as the transport file, if it possible; if not the file will be put
65 into a temporary directory, usually /tmp, or /var/tmp, or whatever directory
66 is named in $TMP environment variable.
70 __revision__ = "$Id: obexftp,v 1.18 2004/10/03 13:33:03 phd Exp $"
71 __date__ = "$Date: 2004/10/03 13:33:03 $"[7:-2]
72 __author__ = "Oleg Broytmann <phd@phd.pp.ru>"
73 __copyright__ = "Copyright (C) 2004 PhiloSoft Design"
76 # Change this to suite your needs
77 obexftp_prog = "/usr/local/obex/bin/obexftp"
80 import sys, os, shutil
81 from time import sleep
82 import xml.dom.minidom
83 from tempfile import mkstemp, mkdtemp, _candidate_tempdir_list
87 logger = logging.getLogger('obexftp-mcextfs')
88 log_err_handler = logging.StreamHandler(sys.stderr)
89 logger.addHandler(log_err_handler)
90 logger.setLevel(logging.ERROR)
95 ObexFTP Virtual FileSystem for Midnight Commander version %s
99 This is not a program. It is ObexFTP Virtual FileSystem for Midnight Commander.
100 Put it in /usr/lib/mc/extfs. For more information read the source!""",
101 __version__, __author__, __copyright__
106 tempdirlist = _candidate_tempdir_list()
107 tempdirlist.insert(0, os.path.abspath(os.path.dirname(sys.argv[2])))
110 for tempdir in tempdirlist:
112 logfile_name = os.path.join(tempdir, 'obexftp-mcextfs.log')
113 logfile = open(logfile_name, 'w')
122 logger.error("Cannot initialize error log file in directories %s" % str(tempdirlist))
125 logger.removeHandler(log_err_handler)
126 logger.addHandler(logging.FileHandler(logfile_name))
129 # Parse ObexFTP XML directory listings
131 class DirectoryEntry(object):
132 """Represent a remote file or a directory"""
134 def __init__(self, type):
138 self.perm = "-rw-rw-rw-"
139 elif type == "folder":
140 self.perm = "drwxrwxrwx"
142 raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
145 if not hasattr(self, "modified"): # telecom
146 return "01-01-70 0:0"
147 date, time = self.modified.split('T')
148 year, month, day = date[2:4], date[4:6], date[6:8]
149 hour, minute = time[:2], time[2:4]
150 return "%s-%s-%s %s:%s" % (month, day, year, hour, minute)
151 mtime = property(mtime)
154 if self.type == "file":
155 return """<%s: type=file, name=%s, size=%s, mtime=%s at 0x%x>""" % (
156 self.__class__.__name__, self.name, self.size, self.mtime, id(self)
158 if self.type == "folder":
159 if hasattr(self, "modified"):
160 return """<%s: type=directory, name=%s, mtime=%s at 0x%x>""" % (
161 self.__class__.__name__, self.name, self.mtime, id(self)
164 return """<%s: type=directory, name=%s at 0x%x>""" % (
165 self.__class__.__name__, self.name, id(self)
167 raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
169 def get_entries(dom, type):
171 for obj in dom.getElementsByTagName(type):
172 entry = DirectoryEntry(type)
173 attrs = obj.attributes
174 for i in range(attrs.length):
176 setattr(entry, attr.name, attr.value)
177 entries.append(entry)
181 # A unique directory for temporary files
186 tmpdir_name = mkdtemp(".tmp", "mcobex-")
187 os.chdir(tmpdir_name)
189 def cleanup_tmpdir():
191 shutil.rmtree(tmpdir_name)
197 s = os.read(fd, 1024)
205 """Run the obexftp binary catching errors"""
207 out_fd, out_filename = mkstemp(".tmp", "mcobex-", tmpdir_name)
208 err_fd, err_filename = mkstemp(".tmp", "mcobex-", tmpdir_name)
210 command = "%s %s %s >%s 2>%s" % (obexftp_prog, obexftp_args, ' '.join(args),
211 out_filename, err_filename)
213 logger.debug("Running command %s", command)
216 result = _read(out_fd)
217 os.remove(out_filename)
219 errors = _read(err_fd)
220 os.remove(err_filename)
222 logger.debug(" result: %s", result)
223 logger.debug(" errors: %s", errors)
224 return result, errors
227 def recursive_list(directory='/'):
228 """List the directory recursively"""
229 listing, errors = _run("-l '%s'" % directory)
232 logger.error("Error reading XML listing: %s", errors)
235 dom = xml.dom.minidom.parseString(listing)
236 directories = get_entries(dom, "folder")
237 files = get_entries(dom, "file")
239 for entry in directories + files:
240 fullpath = "%s/%s" % (directory, entry.name)
241 if fullpath.startswith('//'): fullpath = fullpath[1:]
242 print entry.perm, "1 user group", entry.size, entry.mtime, fullpath
244 for entry in directories:
245 fullpath = "%s/%s" % (directory, entry.name)
246 if fullpath.startswith('//'): fullpath = fullpath[1:]
248 recursive_list(fullpath)
251 """List the entire VFS"""
259 def mcobex_copyout():
260 """Get a file from the VFS"""
261 obex_filename = sys.argv[3]
262 real_filename = sys.argv[4]
266 _run("-g '%s'" % obex_filename)
268 os.rename(os.path.basename(obex_filename), real_filename)
270 logger.exception("Error CopyOut %s to %s", obex_filename, real_filename)
276 """Put a file to the VFS"""
277 obex_filename = sys.argv[3]
278 real_filename = sys.argv[4]
279 dirname, filename = os.path.split(obex_filename)
284 os.rename(real_filename, filename)
285 _run("-c '%s' -p '%s'" % (dirname, filename))
286 os.rename(filename, real_filename) # by some reason MC wants the file back
288 logger.exception("Error CopyIn %s to %s", real_filename, obex_filename)
294 """Remove a file from the VFS"""
295 obex_filename = sys.argv[3]
297 _run("-k '%s'" % obex_filename)
303 """Create a directory in the VFS"""
304 obex_dirname = sys.argv[3]
306 _run("-C '%s'" % obex_dirname)
311 mcobex_rmdir = mcobex_rm
314 def transport_error(error_str):
315 logger.error("Error parsing the transport file: %s" % error_str)
318 def setup_transport():
319 """Setup transport parameters for the obexftp program"""
321 transport_file = open(sys.argv[2], 'r')
322 line = transport_file.readline()
323 transport_file.close()
325 transport_error("cannot read '%s'" % sys.argv[2])
327 parts = line.strip().split()
328 transport = parts[0].lower()
330 if transport == "bluetooth":
332 transport_error("not enough arguments for 'bluetooth' transport")
334 transport_error("too many arguments for 'bluetooth' transport")
335 return ' '.join(["-b", parts[1], "-B", parts[2]])
336 elif transport == "tty":
338 transport_error("not enough arguments for 'tty' transport")
340 transport_error("too many arguments for 'tty' transport")
341 return ' '.join(["-t", parts[1]])
342 elif transport == "irda":
344 transport_error("too many arguments for 'irda' transport")
347 logger.error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'", transport)
351 command = sys.argv[1]
352 procname = "mcobex_" + command
355 if not g.has_key(procname):
356 logger.error("Unknown command %s", command)
361 obexftp_args = setup_transport()
365 logger.exception("Error parsing the transport file")
373 logger.exception("Error during run")