]> git.phdru.name Git - extfs.d.git/blob - obexftp
12c9f529859583cb3be38ccefe5eeef8890bd67a
[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.3+ (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 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).
64
65 """
66
67 __version__ = "1.1.0"
68 __revision__ = "$Id: obexftp,v 1.16 2004/07/27 17:55:15 phd Exp $"
69 __date__ = "$Date: 2004/07/27 17:55:15 $"[7:-2]
70 __author__ = "Oleg Broytmann <phd@phd.pp.ru>"
71 __copyright__ = "Copyright (C) 2004 PhiloSoft Design"
72
73
74 # Change this to suite your needs
75 obexftp_prog = "/usr/local/obex/bin/obexftp"
76
77
78 import sys, time
79 import os, shutil
80 import xml.dom.minidom
81 from tempfile import mkdtemp
82
83
84 import logging
85 logger = logging.getLogger('obexftp-mcextfs')
86 logger.addHandler(logging.FileHandler('obexftp-mcextfs.log'))
87 logger.setLevel(logging.ERROR)
88
89
90 if len(sys.argv) < 2:
91    logger.error("""\
92 ObexFTP Virtual FileSystem for Midnight Commander version %s
93 Author: %s
94 %s
95 Put it in /usr/lib/mc/extfs. For more information read the source!""",
96    __version__, __author__, __copyright__
97 )
98    sys.exit(1)
99
100
101 # Parse ObexFTP XML directory listings
102
103 class DirectoryEntry(object):
104    """Represent a remote file or a directory"""
105
106    def __init__(self, type):
107       self.type = type
108       self.size = 0
109       if type == "file":
110          self.perm = "-rw-rw-rw-"
111       elif type == "folder":
112          self.perm = "drwxrwxrwx"
113       else:
114          raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
115
116    def mtime(self):
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)
124
125    def __repr__(self):
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)
129          )
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)
134             )
135          else: # telecom
136             return """<%s: type=directory, name=%s at 0x%x>""" % (
137                self.__class__.__name__, self.name, id(self)
138             )
139       raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
140
141 def get_entries(dom, type):
142    entries = []
143    for obj in dom.getElementsByTagName(type):
144       entry = DirectoryEntry(type)
145       attrs = obj.attributes
146       for i in range(attrs.length):
147          attr = attrs.item(i)
148          setattr(entry, attr.name, attr.value)
149       entries.append(entry)
150    return entries
151
152
153 def _run(*args):
154    """Run the obexftp binary catching errors"""
155    command = "%s %s %s" % (obexftp_prog, obexftp_args, ' '.join(args))
156    logger.debug("Running command %s", command)
157    w, r, e = os.popen3(command, 'r')
158    w.close()
159    errors = e.read()
160    e.close()
161    result = r.read()
162    r.close()
163    logger.debug("    errors: %s", errors)
164    logger.debug("    result: %s", result)
165    return result, errors
166
167
168 def recursive_list(directory='/'):
169    """List the directory recursively"""
170    listing, errors = _run("-l '%s'" % directory)
171
172    if not listing:
173       logger.error("Error reading XML listing: %s", errors)
174       return
175
176    dom = xml.dom.minidom.parseString(listing)
177    directories = get_entries(dom, "folder")
178    files = get_entries(dom, "file")
179
180    for entry in directories + files:
181       fullpath = "%s/%s" % (directory, entry.name)
182       if fullpath.startswith('//'): fullpath = fullpath[1:]
183       print entry.perm, "1 user group", entry.size, entry.mtime, fullpath
184
185    for entry in directories:
186       fullpath = "%s/%s" % (directory, entry.name)
187       if fullpath.startswith('//'): fullpath = fullpath[1:]
188       time.sleep(1)
189       recursive_list(fullpath)
190
191 def mcobex_list():
192    """List the entire VFS"""
193    recursive_list()
194
195
196 # A unique directory for temporary files
197 tmpdir_name = None
198
199 def setup_tmpdir():
200    global tmpdir_name
201    tmpdir_name = mkdtemp(".tmp", "mcobex-")
202    os.chdir(tmpdir_name)
203
204 def cleanup_tmpdir():
205    os.chdir(os.pardir)
206    shutil.rmtree(tmpdir_name)
207
208
209 def mcobex_copyout():
210    """Get a file from the VFS"""
211    obex_filename = sys.argv[3]
212    real_filename = sys.argv[4]
213
214    setup_tmpdir()
215    try:
216       _run("-g '%s'" % obex_filename)
217       try:
218          os.rename(os.path.basename(obex_filename), real_filename)
219       except OSError:
220          logger.exception("CopyOut %s to %s", obex_filename, real_filename)
221    finally:
222       cleanup_tmpdir()
223
224
225 def mcobex_copyin():
226    """Put a file to the VFS"""
227    obex_filename = sys.argv[3]
228    real_filename = sys.argv[4]
229    dirname, filename = os.path.split(obex_filename)
230
231    setup_tmpdir()
232    try:
233       try:
234          os.rename(real_filename, filename)
235          _run("-c '%s' -p '%s'" % (dirname, filename))
236          os.rename(filename, real_filename) # by some reason MC wants the file back
237       except OSError:
238          logger.exception("CopyIn %s to %s", real_filename, obex_filename)
239    finally:
240       cleanup_tmpdir()
241
242
243 def mcobex_rm():
244    """Remove a file from the VFS"""
245    obex_filename = sys.argv[3]
246    _run("-k '%s'" % obex_filename)
247
248
249 def mcobex_mkdir():
250    """Create a directory in the VFS"""
251    obex_dirname = sys.argv[3]
252    _run("-C '%s'" % obex_dirname)
253
254
255 mcobex_rmdir = mcobex_rm
256
257
258 g = globals()
259 command = sys.argv[1]
260 procname = "mcobex_" + command
261
262 if not g.has_key(procname):
263    logger.error("Unknown command %s", command)
264    sys.exit(1)
265
266
267 def setup_transport():
268    """Setup transport parameters for the obexftp program"""
269    transport_file = open(sys.argv[2], 'r')
270    line = transport_file.readline()
271    transport_file.close()
272
273    parts = line.strip().split()
274    transport = parts[0].lower()
275
276    if transport == "bluetooth":
277       return ' '.join(["-b", parts[1], "-B", parts[2]])
278    elif transport == "tty":
279       return ' '.join(["-t", parts[1]])
280    elif transport == "irda":
281       return "-i"
282    else:
283       logger.error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'", transport)
284       sys.exit(1)
285
286 try:
287    obexftp_args = setup_transport()
288 except:
289    logger.exception("Exception while parsing the transport file")
290    sys.exit(1)
291
292 try:
293    g[procname]()
294 except:
295    logger.exception("Exception during run")