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
65 __revision__ = "$Id: obexftp,v 1.13 2004/07/27 15:39:54 phd Exp $"
66 __date__ = "$Date: 2004/07/27 15:39:54 $"[7:-2]
67 __author__ = "Oleg Broytmann <phd@phd.pp.ru>"
68 __copyright__ = "Copyright (C) 2004 PhiloSoft Design"
71 # Change this to suite your needs
72 obexftp_prog = "/usr/local/obex/bin/obexftp"
77 import xml.dom.minidom
78 from tempfile import mkdtemp
82 sys.stderr.write(msg + '\n')
91 ObexFTP Virtual FileSystem for Midnight Commander version %s
94 Put it in /usr/lib/mc/extfs. For more information read the source!""" % (
95 __version__, __author__, __copyright__
99 def setup_transport():
100 """Setup transport parameters for the obexftp program"""
101 transport_file = open(sys.argv[2], 'r')
102 line = transport_file.readline()
103 transport_file.close()
105 parts = line.strip().split()
106 transport = parts[0].lower()
108 if transport == "bluetooth":
109 return ' '.join(["-b", parts[1], "-B", parts[2]])
110 elif transport == "tty":
111 return ' '.join(["-t", parts[1]])
112 elif transport == "irda":
115 error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'" % transport)
118 # Parse ObexFTP XML directory listings
120 class DirectoryEntry(object):
121 """Represent remote files and directories"""
123 def __init__(self, type):
127 self.perm = "-rw-rw-rw-"
128 elif type == "folder":
129 self.perm = "drwxrwxrwx"
131 raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
134 if not hasattr(self, "modified"): # telecom
135 return "01-01-70 0:0"
136 date, time = self.modified.split('T')
137 year, month, day = date[2:4], date[4:6], date[6:8]
138 hour, minute = time[:2], time[2:4]
139 return "%s-%s-%s %s:%s" % (month, day, year, hour, minute)
140 mtime = property(mtime)
143 if self.type == "file":
144 return """<%s: type=file, name=%s, size=%s, mtime=%s at 0x%x>""" % (
145 self.__class__.__name__, self.name, self.size, self.mtime, id(self)
147 if self.type == "folder":
148 if hasattr(self, "modified"):
149 return """<%s: type=directory, name=%s, mtime=%s at 0x%x>""" % (
150 self.__class__.__name__, self.name, self.mtime, id(self)
153 return """<%s: type=directory, name=%s at 0x%x>""" % (
154 self.__class__.__name__, self.name, id(self)
156 raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
158 def get_entries(dom, type):
160 for obj in dom.getElementsByTagName(type):
161 entry = DirectoryEntry(type)
162 attrs = obj.attributes
163 for i in range(attrs.length):
165 setattr(entry, attr.name, attr.value)
166 entries.append(entry)
170 def recursive_list(obexftp_args, directory):
171 """List the directory recursively"""
172 pipe = os.popen("%s %s -l '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, directory), 'r')
173 listing = pipe.read()
179 dom = xml.dom.minidom.parseString(listing)
180 directories = get_entries(dom, "folder")
181 files = get_entries(dom, "file")
183 for entry in directories + files:
184 fullpath = "%s/%s" % (directory, entry.name)
185 if fullpath.startswith('//'): fullpath = fullpath[1:]
186 print entry.perm, "1 user group", entry.size, entry.mtime, fullpath
188 for entry in directories:
189 fullpath = "%s/%s" % (directory, entry.name)
190 if fullpath.startswith('//'): fullpath = fullpath[1:]
192 recursive_list(obexftp_args, fullpath)
195 """List the entire VFS"""
196 obexftp_args = setup_transport()
197 recursive_list(obexftp_args, '/')
200 # A unique directory for temporary files
206 tmpdir_name = mkdtemp(".tmp", "mcobex-")
207 os.chdir(tmpdir_name)
209 def cleanup_tmpdir():
211 shutil.rmtree(tmpdir_name)
214 def mcobex_copyout():
215 """Get a file from the VFS"""
216 obexftp_args = setup_transport()
217 obex_filename = sys.argv[3]
218 real_filename = sys.argv[4]
221 os.system("%s %s -g '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
223 os.rename(os.path.basename(obex_filename), real_filename)
230 """Put a file to the VFS"""
231 obexftp_args = setup_transport()
232 obex_filename = sys.argv[3]
233 real_filename = sys.argv[4]
234 dirname, filename = os.path.split(obex_filename)
237 os.rename(real_filename, filename)
238 os.system("%s %s -c '%s' -p '%s' 2>/dev/null" % (obexftp_prog, obexftp_args,
241 os.rename(filename, real_filename) # by some reason MC wants the file back
246 """Remove a file from the VFS"""
247 obexftp_args = setup_transport()
248 obex_filename = sys.argv[3]
249 os.system("%s %s -k '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
253 """Create a directory in the VFS"""
254 obexftp_args = setup_transport()
255 obex_dirname = sys.argv[3]
256 os.system("%s %s -C '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_dirname))
259 mcobex_rmdir = mcobex_rm
263 command = sys.argv[1]
264 procname = "mcobex_" + command
266 if not g.has_key(procname):
267 error("Unknown command %s" % command)