]> git.phdru.name Git - extfs.d.git/blob - obexftp
Version 1.0.2. More comments.
[extfs.d.git] / obexftp
1 #! /usr/local/bin/python -O
2
3 """
4 ObexFTP Virtual FileSystem for Midnight Commander.
5
6 Author: Oleg BroytMann <phd@phd.pp.ru>.
7 Copyright (C) 2004 PhiloSoft Design.
8 License: GPL.
9
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.
16
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).
21
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.
26
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.
30
31 First word in the line is a transport name - Bluetooth, TTY or IrDA. The name
32 is case-insensitive.
33
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"
38 and "sdptool browse".
39
40 For the TTY put the device name: "tty /dev/ttyUSB0".
41
42 For the IrDA: just put "IrDA" in the file.
43
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.
52
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
60 restart it.
61
62 """
63
64 __version__ = "1.0.2"
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"
69
70
71 # Change this to suite your needs
72 obexftp_prog = "/usr/local/obex/bin/obexftp"
73
74
75 import sys, time
76 import os, shutil
77 import xml.dom.minidom
78 from tempfile import mkdtemp
79
80
81 def log_error(msg):
82    sys.stderr.write(msg + '\n')
83
84 def error(msg):
85    log_error(msg)
86    sys.exit(1)
87
88
89 if len(sys.argv) < 2:
90    error("""\
91 ObexFTP Virtual FileSystem for Midnight Commander version %s
92 Author: %s
93 %s
94 Put it in /usr/lib/mc/extfs. For more information read the source!""" % (
95    __version__, __author__, __copyright__
96 ))
97
98
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()
104
105    parts = line.strip().split()
106    transport = parts[0].lower()
107
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":
113       return "-i"
114    else:
115       error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'" % transport)
116
117
118 # Parse ObexFTP XML directory listings
119
120 class DirectoryEntry(object):
121    """Represent remote files and directories"""
122
123    def __init__(self, type):
124       self.type = type
125       self.size = 0
126       if type == "file":
127          self.perm = "-rw-rw-rw-"
128       elif type == "folder":
129          self.perm = "drwxrwxrwx"
130       else:
131          raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
132
133    def mtime(self):
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)
141
142    def __repr__(self):
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)
146          )
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)
151             )
152          else: # telecom
153             return """<%s: type=directory, name=%s at 0x%x>""" % (
154                self.__class__.__name__, self.name, id(self)
155             )
156       raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
157
158 def get_entries(dom, type):
159    entries = []
160    for obj in dom.getElementsByTagName(type):
161       entry = DirectoryEntry(type)
162       attrs = obj.attributes
163       for i in range(attrs.length):
164          attr = attrs.item(i)
165          setattr(entry, attr.name, attr.value)
166       entries.append(entry)
167    return entries
168
169
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()
174    pipe.close()
175
176    if not listing:
177       return
178
179    dom = xml.dom.minidom.parseString(listing)
180    directories = get_entries(dom, "folder")
181    files = get_entries(dom, "file")
182
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
187
188    for entry in directories:
189       fullpath = "%s/%s" % (directory, entry.name)
190       if fullpath.startswith('//'): fullpath = fullpath[1:]
191       time.sleep(1)
192       recursive_list(obexftp_args, fullpath)
193
194 def mcobex_list():
195    """List the entire VFS"""
196    obexftp_args = setup_transport()
197    recursive_list(obexftp_args, '/')
198
199
200 # A unique directory for temporary files
201
202 tmpdir_name = None
203
204 def setup_tmpdir():
205    global tmpdir_name
206    tmpdir_name = mkdtemp(".tmp", "mcobex-")
207    os.chdir(tmpdir_name)
208
209 def cleanup_tmpdir():
210    os.chdir(os.pardir)
211    shutil.rmtree(tmpdir_name)
212
213
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]
219
220    setup_tmpdir()
221    os.system("%s %s -g '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
222    try:
223       os.rename(os.path.basename(obex_filename), real_filename)
224    except OSError:
225       pass
226    cleanup_tmpdir()
227
228
229 def mcobex_copyin():
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)
235
236    setup_tmpdir()
237    os.rename(real_filename, filename)
238    os.system("%s %s -c '%s' -p '%s' 2>/dev/null" % (obexftp_prog, obexftp_args,
239       dirname, filename
240    ))
241    os.rename(filename, real_filename) # by some reason MC wants the file back
242    cleanup_tmpdir()
243
244
245 def mcobex_rm():
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))
250
251
252 def mcobex_mkdir():
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))
257
258
259 mcobex_rmdir = mcobex_rm
260
261
262 g = globals()
263 command = sys.argv[1]
264 procname = "mcobex_" + command
265
266 if not g.has_key(procname):
267    error("Unknown command %s" % command)
268
269 g[procname]()