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(s).
68 __revision__ = "$Id: obexftp,v 1.17 2004/09/25 16:13:39 phd Exp $"
69 __date__ = "$Date: 2004/09/25 16:13:39 $"[7:-2]
70 __author__ = "Oleg Broytmann <phd@phd.pp.ru>"
71 __copyright__ = "Copyright (C) 2004 PhiloSoft Design"
74 # Change this to suite your needs
75 obexftp_prog = "/usr/local/obex/bin/obexftp"
80 import xml.dom.minidom
81 from tempfile import mkstemp, mkdtemp
85 logger = logging.getLogger('obexftp-mcextfs')
86 logger.addHandler(logging.FileHandler('obexftp-mcextfs.log'))
87 logger.setLevel(logging.ERROR)
92 ObexFTP Virtual FileSystem for Midnight Commander version %s
95 Put it in /usr/lib/mc/extfs. For more information read the source!""",
96 __version__, __author__, __copyright__
101 # Parse ObexFTP XML directory listings
103 class DirectoryEntry(object):
104 """Represent a remote file or a directory"""
106 def __init__(self, type):
110 self.perm = "-rw-rw-rw-"
111 elif type == "folder":
112 self.perm = "drwxrwxrwx"
114 raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
117 if not hasattr(self, "modified"): # telecom
118 return "01-01-70 0:0"
119 date, time = self.modified.split('T')
120 year, month, day = date[2:4], date[4:6], date[6:8]
121 hour, minute = time[:2], time[2:4]
122 return "%s-%s-%s %s:%s" % (month, day, year, hour, minute)
123 mtime = property(mtime)
126 if self.type == "file":
127 return """<%s: type=file, name=%s, size=%s, mtime=%s at 0x%x>""" % (
128 self.__class__.__name__, self.name, self.size, self.mtime, id(self)
130 if self.type == "folder":
131 if hasattr(self, "modified"):
132 return """<%s: type=directory, name=%s, mtime=%s at 0x%x>""" % (
133 self.__class__.__name__, self.name, self.mtime, id(self)
136 return """<%s: type=directory, name=%s at 0x%x>""" % (
137 self.__class__.__name__, self.name, id(self)
139 raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
141 def get_entries(dom, type):
143 for obj in dom.getElementsByTagName(type):
144 entry = DirectoryEntry(type)
145 attrs = obj.attributes
146 for i in range(attrs.length):
148 setattr(entry, attr.name, attr.value)
149 entries.append(entry)
153 # A unique directory for temporary files
158 tmpdir_name = mkdtemp(".tmp", "mcobex-")
159 os.chdir(tmpdir_name)
161 def cleanup_tmpdir():
163 shutil.rmtree(tmpdir_name)
169 s = os.read(fd, 1024)
177 """Run the obexftp binary catching errors"""
179 out_fd, out_filename = mkstemp(".tmp", "mcobex-", tmpdir_name)
180 err_fd, err_filename = mkstemp(".tmp", "mcobex-", tmpdir_name)
182 command = "%s %s %s >%s 2>%s" % (obexftp_prog, obexftp_args, ' '.join(args),
183 out_filename, err_filename)
185 logger.debug("Running command %s", command)
188 result = _read(out_fd)
189 os.remove(out_filename)
191 errors = _read(err_fd)
192 os.remove(err_filename)
194 logger.debug(" result: %s", result)
195 logger.debug(" errors: %s", errors)
196 return result, errors
199 def recursive_list(directory='/'):
200 """List the directory recursively"""
201 listing, errors = _run("-l '%s'" % directory)
204 logger.error("Error reading XML listing: %s", errors)
207 dom = xml.dom.minidom.parseString(listing)
208 directories = get_entries(dom, "folder")
209 files = get_entries(dom, "file")
211 for entry in directories + files:
212 fullpath = "%s/%s" % (directory, entry.name)
213 if fullpath.startswith('//'): fullpath = fullpath[1:]
214 print entry.perm, "1 user group", entry.size, entry.mtime, fullpath
216 for entry in directories:
217 fullpath = "%s/%s" % (directory, entry.name)
218 if fullpath.startswith('//'): fullpath = fullpath[1:]
220 recursive_list(fullpath)
223 """List the entire VFS"""
231 def mcobex_copyout():
232 """Get a file from the VFS"""
233 obex_filename = sys.argv[3]
234 real_filename = sys.argv[4]
238 _run("-g '%s'" % obex_filename)
240 os.rename(os.path.basename(obex_filename), real_filename)
242 logger.exception("Error CopyOut %s to %s", obex_filename, real_filename)
248 """Put a file to the VFS"""
249 obex_filename = sys.argv[3]
250 real_filename = sys.argv[4]
251 dirname, filename = os.path.split(obex_filename)
256 os.rename(real_filename, filename)
257 _run("-c '%s' -p '%s'" % (dirname, filename))
258 os.rename(filename, real_filename) # by some reason MC wants the file back
260 logger.exception("Error CopyIn %s to %s", real_filename, obex_filename)
266 """Remove a file from the VFS"""
267 obex_filename = sys.argv[3]
269 _run("-k '%s'" % obex_filename)
275 """Create a directory in the VFS"""
276 obex_dirname = sys.argv[3]
278 _run("-C '%s'" % obex_dirname)
283 mcobex_rmdir = mcobex_rm
286 def transport_error(error_str):
287 logger.error("Error parsing the transport file: %s" % error_str)
290 def setup_transport():
291 """Setup transport parameters for the obexftp program"""
293 transport_file = open(sys.argv[2], 'r')
294 line = transport_file.readline()
295 transport_file.close()
297 transport_error("cannot read '%s'" % sys.argv[2])
299 parts = line.strip().split()
300 transport = parts[0].lower()
302 if transport == "bluetooth":
304 transport_error("not enough arguments for 'bluetooth' transport")
306 transport_error("too many arguments for 'bluetooth' transport")
307 return ' '.join(["-b", parts[1], "-B", parts[2]])
308 elif transport == "tty":
310 transport_error("not enough arguments for 'tty' transport")
312 transport_error("too many arguments for 'tty' transport")
313 return ' '.join(["-t", parts[1]])
314 elif transport == "irda":
316 transport_error("too many arguments for 'irda' transport")
319 logger.error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'", transport)
323 command = sys.argv[1]
324 procname = "mcobex_" + command
327 if not g.has_key(procname):
328 logger.error("Unknown command %s", command)
333 obexftp_args = setup_transport()
337 logger.exception("Error parsing the transport file")
345 logger.exception("Error during run")