5029895beb1a9e5f68ec21c920520ea212688c7a
[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 complete
11 user-mode solution, no kernel modules required (unlike SieFS or such). The
12 script implements all commands of Midnight VFS, except for undocumented "run";
13 but there are no runnable files in the cell phone. The script is written in
14 Python because I love Python, the best of all languages, and I need to parse
15 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/)
20 and 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 can have any name, and is
28 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, IrDA or TTY. 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 to,
36 and channel is the OBEX File Transfer channel; you can discover the address and
37 the channel for your device by using commands like "hcitool scan" and "sdptool
38 browse".
39
40 For the IrDA: just put "IrDA" in the file.
41
42 For the TTY put a device name: "tty /dev/rfcomm0".
43
44 Now run this "cd" command in the Midnight Commander (in the "bindings" files
45 the command is "%cd"): cd description#obexftp, where "description" is the name
46 of your file. The VFS script uses obexftp to connect to the device and list
47 files and directories. Please be warned that opening the VFS for the first time
48 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 directories quickly.
52
53 """
54
55 __version__ = "1.0.0"
56 __revision__ = "$Id: obexftp,v 1.9 2004/06/19 17:28:44 phd Exp $"
57 __date__ = "$Date: 2004/06/19 17:28:44 $"[7:-2]
58 __author__ = "Oleg Broytmann <phd@phd.pp.ru>"
59 __copyright__ = "Copyright (C) 2004 PhiloSoft Design"
60
61
62 # Change this to suite your needs
63 obexftp_prog = "/usr/local/obex/bin/obexftp"
64
65
66 import sys, time
67 import os, shutil
68 import xml.dom.minidom
69 from tempfile import mkdtemp
70
71
72 def log_error(msg):
73    sys.stderr.write(msg + '\n')
74
75 def error(msg):
76    log_error(msg)
77    sys.exit(1)
78
79
80 if len(sys.argv) < 2:
81    error("""\
82 ObexFTP Virtual FileSystem for Midnight Commander version %s
83 Author: %s
84 %s
85 Put it in /usr/lib/mc/extfs. For more information read the source!""" % (
86    __version__, __author__, __copyright__
87 ))
88
89
90 def setup_transport():
91    """Setup transport parameters for the obexftp program"""
92    transport_file = open(sys.argv[2], 'r')
93    line = transport_file.readline().strip()
94    transport_file.close()
95
96    parts = line.split()
97    transport = parts[0].lower()
98
99    if transport == "bluetooth":
100       return ' '.join(["-b", parts[1], "-B", parts[2]])
101    elif transport == "tty":
102       return ' '.join(["-t", parts[1]])
103    elif transport == "irda":
104       return "-i"
105    else:
106       error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'" % base_filename)
107
108
109 # Parse ObexFTP XML directory listings
110
111 class DirectoryEntry(object):
112    """Represent remote files and directories"""
113
114    def __init__(self, type):
115       self.type = type
116       self.size = 0
117       if type == "file":
118          self.perm = "-rw-rw-rw-"
119       elif type == "folder":
120          self.perm = "drwxrwxrwx"
121       else:
122          raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
123
124    def mtime(self):
125       if not hasattr(self, "modified"): # telecom
126          return "01-01-70 0:0"
127       date, time = self.modified.split('T')
128       year, month, day = date[2:4], date[4:6], date[6:8]
129       hour, minute = time[:2], time[2:4]
130       return "%s-%s-%s %s:%s" % (month, day, year, hour, minute)
131    mtime = property(mtime)
132
133    def __repr__(self):
134       if self.type == "file":
135          return """<%s: type=file, name=%s, size=%s, mtime=%s at 0x%x>""" % (
136             self.__class__.__name__, self.name, self.size, self.mtime, id(self)
137          )
138       if self.type == "folder":
139          if hasattr(self, "modified"):
140             return """<%s: type=directory, name=%s, mtime=%s at 0x%x>""" % (
141                self.__class__.__name__, self.name, self.mtime, id(self)
142             )
143          else: # telecom
144             return """<%s: type=directory, name=%s at 0x%x>""" % (
145                self.__class__.__name__, self.name, id(self)
146             )
147       raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
148
149 def get_entries(dom, type):
150    entries = []
151    for obj in dom.getElementsByTagName(type):
152       entry = DirectoryEntry(type)
153       attrs = obj.attributes
154       for i in range(attrs.length):
155          attr = attrs.item(i)
156          setattr(entry, attr.name, attr.value)
157       entries.append(entry)
158    return entries
159
160
161 def recursive_list(obexftp_args, directory):
162    """List the directory recursively"""
163    pipe = os.popen("%s %s -l '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, directory), 'r')
164    listing = pipe.read()
165    pipe.close()
166
167    if not listing:
168       return
169
170    dom = xml.dom.minidom.parseString(listing)
171    directories = get_entries(dom, "folder")
172    files = get_entries(dom, "file")
173
174    for entry in directories + files:
175       fullpath = "%s/%s" % (directory, entry.name)
176       if fullpath.startswith('//'): fullpath = fullpath[1:]
177       print entry.perm, "1 user group", entry.size, entry.mtime, fullpath
178
179    for entry in directories:
180       fullpath = "%s/%s" % (directory, entry.name)
181       if fullpath.startswith('//'): fullpath = fullpath[1:]
182       time.sleep(1)
183       recursive_list(obexftp_args, fullpath)
184
185 def mcobex_list():
186    """List the entire VFS"""
187    obexftp_args = setup_transport()
188    recursive_list(obexftp_args, '/')
189
190
191 # A unique directory for temporary files
192
193 tmpdir_name = None
194
195 def setup_tmpdir():
196    global tmpdir_name
197    tmpdir_name = mkdtemp(".tmp", "mcobex-")
198    os.chdir(tmpdir_name)
199
200 def cleanup_tmpdir():
201    os.chdir(os.pardir)
202    shutil.rmtree(tmpdir_name)
203
204
205 def mcobex_copyout():
206    """Get a file from the VFS"""
207    obexftp_args = setup_transport()
208    obex_filename = sys.argv[3]
209    real_filename = sys.argv[4]
210
211    setup_tmpdir()
212    os.system("%s %s -g '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
213    try:
214       os.rename(os.path.basename(obex_filename), real_filename)
215    except OSError:
216       pass
217    cleanup_tmpdir()
218
219
220 def mcobex_copyin():
221    """Put a file to the VFS"""
222    obexftp_args = setup_transport()
223    obex_filename = sys.argv[3]
224    real_filename = sys.argv[4]
225    dirname, filename = os.path.split(obex_filename)
226
227    setup_tmpdir()
228    os.rename(real_filename, filename)
229    os.system("%s %s -c '%s' -p '%s' 2>/dev/null" % (obexftp_prog, obexftp_args,
230       dirname, filename
231    ))
232    os.rename(filename, real_filename) # by some reason MC wants the file back
233    cleanup_tmpdir()
234
235
236 def mcobex_rm():
237    """Remove a file from the VFS"""
238    obexftp_args = setup_transport()
239    obex_filename = sys.argv[3]
240    os.system("%s %s -k '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
241
242
243 def mcobex_mkdir():
244    """Create a directory in the VFS"""
245    obexftp_args = setup_transport()
246    obex_dirname = sys.argv[3]
247    os.system("%s %s -C '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_dirname))
248
249
250 mcobex_rmdir = mcobex_rm
251
252
253 g = globals()
254 command = sys.argv[1]
255 procname = "mcobex_" + command
256
257 if not g.has_key(procname):
258    error("Unknown command %s" % command)
259
260 g[procname]()