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.2+ (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 you can help by turning log_level to INFO (see below)
63 or DEBUG and looking in the obexftp-mcextfs.log file.
68 __revision__ = "$Id: obexftp,v 1.14 2004/07/27 16:23:18 phd Exp $"
69 __date__ = "$Date: 2004/07/27 16:23:18 $"[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"
78 log_level = logging.DEBUG
83 import xml.dom.minidom
84 from tempfile import mkdtemp
87 logger = logging.getLogger('obexftp-mcextfs')
88 logger.addHandler(logging.FileHandler('obexftp-mcextfs.log'))
89 logger.setLevel(log_level)
94 ObexFTP Virtual FileSystem for Midnight Commander version %s
97 Put it in /usr/lib/mc/extfs. For more information read the source!""",
98 __version__, __author__, __copyright__
103 # Parse ObexFTP XML directory listings
105 class DirectoryEntry(object):
106 """Represent a remote file or a directory"""
108 def __init__(self, type):
112 self.perm = "-rw-rw-rw-"
113 elif type == "folder":
114 self.perm = "drwxrwxrwx"
116 raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
119 if not hasattr(self, "modified"): # telecom
120 return "01-01-70 0:0"
121 date, time = self.modified.split('T')
122 year, month, day = date[2:4], date[4:6], date[6:8]
123 hour, minute = time[:2], time[2:4]
124 return "%s-%s-%s %s:%s" % (month, day, year, hour, minute)
125 mtime = property(mtime)
128 if self.type == "file":
129 return """<%s: type=file, name=%s, size=%s, mtime=%s at 0x%x>""" % (
130 self.__class__.__name__, self.name, self.size, self.mtime, id(self)
132 if self.type == "folder":
133 if hasattr(self, "modified"):
134 return """<%s: type=directory, name=%s, mtime=%s at 0x%x>""" % (
135 self.__class__.__name__, self.name, self.mtime, id(self)
138 return """<%s: type=directory, name=%s at 0x%x>""" % (
139 self.__class__.__name__, self.name, id(self)
141 raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
143 def get_entries(dom, type):
145 for obj in dom.getElementsByTagName(type):
146 entry = DirectoryEntry(type)
147 attrs = obj.attributes
148 for i in range(attrs.length):
150 setattr(entry, attr.name, attr.value)
151 entries.append(entry)
155 def recursive_list(directory='/'):
156 """List the directory recursively"""
157 pipe = os.popen("%s %s -l '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, directory), 'r')
158 listing = pipe.read()
164 dom = xml.dom.minidom.parseString(listing)
165 directories = get_entries(dom, "folder")
166 files = get_entries(dom, "file")
168 for entry in directories + files:
169 fullpath = "%s/%s" % (directory, entry.name)
170 if fullpath.startswith('//'): fullpath = fullpath[1:]
171 print entry.perm, "1 user group", entry.size, entry.mtime, fullpath
173 for entry in directories:
174 fullpath = "%s/%s" % (directory, entry.name)
175 if fullpath.startswith('//'): fullpath = fullpath[1:]
177 recursive_list(fullpath)
180 """List the entire VFS"""
184 # A unique directory for temporary files
189 tmpdir_name = mkdtemp(".tmp", "mcobex-")
190 os.chdir(tmpdir_name)
192 def cleanup_tmpdir():
194 shutil.rmtree(tmpdir_name)
197 def mcobex_copyout():
198 """Get a file from the VFS"""
199 obex_filename = sys.argv[3]
200 real_filename = sys.argv[4]
204 os.system("%s %s -g '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
206 os.rename(os.path.basename(obex_filename), real_filename)
214 """Put a file to the VFS"""
215 obex_filename = sys.argv[3]
216 real_filename = sys.argv[4]
217 dirname, filename = os.path.split(obex_filename)
221 os.rename(real_filename, filename)
222 os.system("%s %s -c '%s' -p '%s' 2>/dev/null" % (obexftp_prog, obexftp_args,
225 os.rename(filename, real_filename) # by some reason MC wants the file back
231 """Remove a file from the VFS"""
232 obex_filename = sys.argv[3]
233 os.system("%s %s -k '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
237 """Create a directory in the VFS"""
238 obex_dirname = sys.argv[3]
239 os.system("%s %s -C '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_dirname))
242 mcobex_rmdir = mcobex_rm
246 command = sys.argv[1]
247 procname = "mcobex_" + command
249 if not g.has_key(procname):
250 logger.error("Unknown command %s", command)
254 def setup_transport():
255 """Setup transport parameters for the obexftp program"""
256 transport_file = open(sys.argv[2], 'r')
257 line = transport_file.readline()
258 transport_file.close()
260 parts = line.strip().split()
261 transport = parts[0].lower()
263 if transport == "bluetooth":
264 return ' '.join(["-b", parts[1], "-B", parts[2]])
265 elif transport == "tty":
266 return ' '.join(["-t", parts[1]])
267 elif transport == "irda":
270 logger.error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'", transport)
274 obexftp_args = setup_transport()
276 logger.exception("Exception while parsing the transport file")
282 logger.exception("Exception during run")