Version 0.5.0. Removed debug output. Transport file now may have any name;
[extfs.d.git] / obexftp
1 #! /usr/local/bin/python -O
2
3 """
4 ObexFTP VFS for Midnight Commander. Manipulate a cell phone's filesystem using obexftp.
5
6 Author: Oleg BroytMann <phd@phd.pp.ru>.
7 Copyright (C) 2004 PhiloSoft Design.
8 License: GPL.
9
10 The script requires Midnight Commander 3.1+ (http://www.ibiblio.org/mc/),
11 Python 2.2+ (http://www.python.org/), OpenOBEX 1.0.1+ (http://openobex.sourceforge.net/)
12 and ObexFTP 0.10.4+ (http://triq.net/obexftp).
13
14 Edit the full path to the obexftp binary (see below). Put the file to the
15 /usr/[local/]lib/mc/extfs, and add a line "obexftp" to the
16 /usr/[local/]lib/mc/extfs/extfs.ini. Then create somewhere a transport file.
17
18 The transport file can have any name, and is expected to be a text file with at
19 least one line defining the transport to your device. Other lines in the file
20 are ignored.
21
22 First word in the line is a transport name - Bluetooth, IrDA or TTY. The name
23 is case-insensitive.
24
25 For the Bluetooth transport put there a line "Bluetooth CP:AD:RE:SS channel",
26 where CP:AD:RE:SS is the hardware address of the device you want to connect to,
27 and channel is the OBEX File Transfer channel; you can discover the address and
28 the channel for your device by using commands like "hcitool scan" and "sdptool
29 browse".
30
31 For the TTY put a device name: "tty /dev/rfcomm0".
32
33 The content with the IrDA just put "IrDA" in the file.
34
35 Now run this "cd" command in the Midnight Commander (in the "bindings" files
36 the command is "%cd"): cd description#obexftp. The VFS script uses obexftp to
37 connect to the device and list files and directories. Plese be warned that
38 opening the VFS for the first time is VERY slow, because the script needs to
39 scan the entire cell phone's filesystem. And there must be a timeout between
40 connections, which doesn't make the scanning process faster. Midnight Commander
41 caches the result, so you can browse directories quickly.
42
43 """
44
45 __version__ = "0.5.0"
46 __revision__ = "$Id: obexftp,v 1.6 2004/06/13 21:48:59 phd Exp $"
47 __date__ = "$Date: 2004/06/13 21:48:59 $"[7:-2]
48 __author__ = "Oleg Broytmann <phd@phd.pp.ru>"
49 __copyright__ = "Copyright (C) 2004 PhiloSoft Design"
50
51
52 # Change this to suite your needs
53 obexftp_prog = "/usr/local/obex/bin/obexftp"
54
55
56 import sys, time
57 import os, shutil
58 import xml.dom.minidom
59 from tempfile import mkdtemp
60
61
62 def log_error(msg):
63    sys.stderr.write(msg + '\n')
64
65 def error(msg):
66    log_error(msg + '\n')
67    sys.exit(1)
68
69
70 if len(sys.argv) < 2:
71    error("""\
72 It is not a program - it is a VFS for Midnight Commander.
73 Put it in /usr/lib/mc/extfs.""")
74
75
76 def setup_transport():
77    """Setup transport parameters for the obexftp program"""
78    transport_file = open(sys.argv[2], 'r')
79    line = transport_file.readline().strip()
80    transport_file.close()
81
82    parts = line.split()
83    transport = parts[0].lower()
84
85    if transport == "bluetooth":
86       return ' '.join(["-b", parts[1], "-B", parts[2]])
87    elif transport == "tty":
88       return ' '.join(["-t", parts[1]])
89    elif transport == "irda":
90       return "-i"
91    else:
92       error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'" % base_filename)
93
94
95 # Parse ObexFTP XML directory listings
96
97 class DirectoryEntry(object):
98    def __init__(self, type):
99       self.type = type
100       self.size = 0
101       if type == "file":
102          self.perm = "-rw-rw-rw-"
103       elif type == "folder":
104          self.perm = "drwxrwxrwx"
105       else:
106          raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
107
108    def mtime(self):
109       if not hasattr(self, "modified"): # telecom
110          return "01-01-70 0:0"
111       date, time = self.modified.split('T')
112       year, month, day = date[2:4], date[4:6], date[6:8]
113       hour, minute = time[:2], time[2:4]
114       return "%s-%s-%s %s:%s" % (month, day, year, hour, minute)
115    mtime = property(mtime)
116
117    def __repr__(self):
118       if self.type == "file":
119          return """<%s: type=file, name=%s, size=%s, mtime=%s at 0x%x>""" % (
120             self.__class__.__name__, self.name, self.size, self.mtime, id(self)
121          )
122       if self.type == "folder":
123          if hasattr(self, "modified"):
124             return """<%s: type=directory, name=%s, mtime=%s at 0x%x>""" % (
125                self.__class__.__name__, self.name, self.mtime, id(self)
126             )
127          else: # telecom
128             return """<%s: type=directory, name=%s at 0x%x>""" % (
129                self.__class__.__name__, self.name, id(self)
130             )
131       raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
132
133 def get_entries(dom, tag):
134    entries = []
135    for subtag in dom.getElementsByTagName(tag):
136       entry = DirectoryEntry(tag)
137       attrs = subtag.attributes
138       for i in range(attrs.length):
139          attr = attrs.item(i)
140          setattr(entry, attr.name, attr.value)
141       entries.append(entry)
142    return entries
143
144
145 def recursive_list(obexftp_args, directory):
146    """List the directory recursively"""
147    pipe = os.popen("%s %s -l '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, directory), 'r')
148    listing = pipe.read()
149    pipe.close()
150
151    if not listing:
152       return
153
154    dom = xml.dom.minidom.parseString(listing)
155    directories = get_entries(dom, "folder")
156    files = get_entries(dom, "file")
157
158    for entry in directories + files:
159       fullpath = "%s/%s" % (directory, entry.name)
160       if fullpath.startswith('//'): fullpath = fullpath[1:]
161       print entry.perm, "1 user group", entry.size, entry.mtime, fullpath
162
163    for entry in directories:
164       fullpath = "%s/%s" % (directory, entry.name)
165       if fullpath.startswith('//'): fullpath = fullpath[1:]
166       time.sleep(1)
167       recursive_list(obexftp_args, fullpath)
168
169 def mcobex_list():
170    """List the entire VFS"""
171    obexftp_args = setup_transport()
172    recursive_list(obexftp_args, '/')
173
174
175 # A unique directory for temporary files
176
177 tmpdir_name = None
178
179 def setup_tmpdir():
180    global tmpdir_name
181    tmpdir_name = mkdtemp(".tmp", "mcobex-")
182    os.chdir(tmpdir_name)
183
184 def cleanup_tmpdir():
185    os.chdir(os.pardir)
186    shutil.rmtree(tmpdir_name)
187
188
189 def mcobex_copyout():
190    """Get a file from the VFS"""
191    obexftp_args = setup_transport()
192    obex_filename = sys.argv[3]
193    real_filename = sys.argv[4]
194
195    setup_tmpdir()
196    os.system("%s %s -g '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
197    try:
198       os.rename(os.path.basename(obex_filename), real_filename)
199    except OSError:
200       pass
201    cleanup_tmpdir()
202
203
204 def mcobex_copyin():
205    """Put a file to the VFS"""
206    obexftp_args = setup_transport()
207    obex_filename = sys.argv[3]
208    real_filename = sys.argv[4]
209    dirname, filename = os.path.split(obex_filename)
210
211    setup_tmpdir()
212    os.rename(real_filename, filename)
213    os.system("%s %s -c '%s' -p '%s' 2>/dev/null" % (obexftp_prog, obexftp_args,
214       dirname, filename
215    ))
216    os.rename(filename, real_filename) # by some reason MC wants the file back
217    cleanup_tmpdir()
218
219
220 def mcobex_rm():
221    """Remove a file from the VFS"""
222    obexftp_args = setup_transport()
223    obex_filename = sys.argv[3]
224
225    setup_tmpdir()
226    os.system("%s %s -k '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
227    cleanup_tmpdir()
228
229
230 def mcobex_mkdir():
231    """Create a directory in the VFS"""
232    obexftp_args = setup_transport()
233    obex_dirname = sys.argv[3]
234
235    setup_tmpdir()
236    os.system("%s %s -C '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_dirname))
237    cleanup_tmpdir()
238
239
240 mcobex_rmdir = mcobex_rm
241
242
243 g = globals()
244 command = sys.argv[1]
245 procname = "mcobex_" + command
246
247 if not g.has_key(procname):
248    error("Unknown command %s" % command)
249
250 g[procname]()