Import logging requires python 2.3+.
[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 you can help by turning log_level to INFO (see below)
63 or DEBUG and looking in the obexftp-mcextfs.log file.
64
65 """
66
67 __version__ = "1.1.0"
68 __revision__ = "$Id: obexftp,v 1.15 2004/07/27 16:35:28 phd Exp $"
69 __date__ = "$Date: 2004/07/27 16:35:28 $"[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 import logging
78 log_level = logging.DEBUG
79
80
81 import sys, time
82 import os, shutil
83 import xml.dom.minidom
84 from tempfile import mkdtemp
85
86
87 logger = logging.getLogger('obexftp-mcextfs')
88 logger.addHandler(logging.FileHandler('obexftp-mcextfs.log'))
89 logger.setLevel(log_level)
90
91
92 if len(sys.argv) < 2:
93    logger.error("""\
94 ObexFTP Virtual FileSystem for Midnight Commander version %s
95 Author: %s
96 %s
97 Put it in /usr/lib/mc/extfs. For more information read the source!""",
98    __version__, __author__, __copyright__
99 )
100    sys.exit(1)
101
102
103 # Parse ObexFTP XML directory listings
104
105 class DirectoryEntry(object):
106    """Represent a remote file or a directory"""
107
108    def __init__(self, type):
109       self.type = type
110       self.size = 0
111       if type == "file":
112          self.perm = "-rw-rw-rw-"
113       elif type == "folder":
114          self.perm = "drwxrwxrwx"
115       else:
116          raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
117
118    def mtime(self):
119       if not hasattr(self, "modified"): # telecom
120          return "01-01-70 0:0"
121       date, time = self.modified.split('T')
122       year, month, day = date[2:4], date[4:6], date[6:8]
123       hour, minute = time[:2], time[2:4]
124       return "%s-%s-%s %s:%s" % (month, day, year, hour, minute)
125    mtime = property(mtime)
126
127    def __repr__(self):
128       if self.type == "file":
129          return """<%s: type=file, name=%s, size=%s, mtime=%s at 0x%x>""" % (
130             self.__class__.__name__, self.name, self.size, self.mtime, id(self)
131          )
132       if self.type == "folder":
133          if hasattr(self, "modified"):
134             return """<%s: type=directory, name=%s, mtime=%s at 0x%x>""" % (
135                self.__class__.__name__, self.name, self.mtime, id(self)
136             )
137          else: # telecom
138             return """<%s: type=directory, name=%s at 0x%x>""" % (
139                self.__class__.__name__, self.name, id(self)
140             )
141       raise ValueError, "unknown type '%s'; expected 'file' or 'folder'" % self.type
142
143 def get_entries(dom, type):
144    entries = []
145    for obj in dom.getElementsByTagName(type):
146       entry = DirectoryEntry(type)
147       attrs = obj.attributes
148       for i in range(attrs.length):
149          attr = attrs.item(i)
150          setattr(entry, attr.name, attr.value)
151       entries.append(entry)
152    return entries
153
154
155 def recursive_list(directory='/'):
156    """List the directory recursively"""
157    pipe = os.popen("%s %s -l '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, directory), 'r')
158    listing = pipe.read()
159    pipe.close()
160
161    if not listing:
162       return
163
164    dom = xml.dom.minidom.parseString(listing)
165    directories = get_entries(dom, "folder")
166    files = get_entries(dom, "file")
167
168    for entry in directories + files:
169       fullpath = "%s/%s" % (directory, entry.name)
170       if fullpath.startswith('//'): fullpath = fullpath[1:]
171       print entry.perm, "1 user group", entry.size, entry.mtime, fullpath
172
173    for entry in directories:
174       fullpath = "%s/%s" % (directory, entry.name)
175       if fullpath.startswith('//'): fullpath = fullpath[1:]
176       time.sleep(1)
177       recursive_list(fullpath)
178
179 def mcobex_list():
180    """List the entire VFS"""
181    recursive_list()
182
183
184 # A unique directory for temporary files
185 tmpdir_name = None
186
187 def setup_tmpdir():
188    global tmpdir_name
189    tmpdir_name = mkdtemp(".tmp", "mcobex-")
190    os.chdir(tmpdir_name)
191
192 def cleanup_tmpdir():
193    os.chdir(os.pardir)
194    shutil.rmtree(tmpdir_name)
195
196
197 def mcobex_copyout():
198    """Get a file from the VFS"""
199    obex_filename = sys.argv[3]
200    real_filename = sys.argv[4]
201
202    setup_tmpdir()
203    try:
204       os.system("%s %s -g '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
205       try:
206          os.rename(os.path.basename(obex_filename), real_filename)
207       except OSError:
208          pass
209    finally:
210       cleanup_tmpdir()
211
212
213 def mcobex_copyin():
214    """Put a file to the VFS"""
215    obex_filename = sys.argv[3]
216    real_filename = sys.argv[4]
217    dirname, filename = os.path.split(obex_filename)
218
219    setup_tmpdir()
220    try:
221       os.rename(real_filename, filename)
222       os.system("%s %s -c '%s' -p '%s' 2>/dev/null" % (obexftp_prog, obexftp_args,
223          dirname, filename
224       ))
225       os.rename(filename, real_filename) # by some reason MC wants the file back
226    finally:
227       cleanup_tmpdir()
228
229
230 def mcobex_rm():
231    """Remove a file from the VFS"""
232    obex_filename = sys.argv[3]
233    os.system("%s %s -k '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_filename))
234
235
236 def mcobex_mkdir():
237    """Create a directory in the VFS"""
238    obex_dirname = sys.argv[3]
239    os.system("%s %s -C '%s' 2>/dev/null" % (obexftp_prog, obexftp_args, obex_dirname))
240
241
242 mcobex_rmdir = mcobex_rm
243
244
245 g = globals()
246 command = sys.argv[1]
247 procname = "mcobex_" + command
248
249 if not g.has_key(procname):
250    logger.error("Unknown command %s", command)
251    sys.exit(1)
252
253
254 def setup_transport():
255    """Setup transport parameters for the obexftp program"""
256    transport_file = open(sys.argv[2], 'r')
257    line = transport_file.readline()
258    transport_file.close()
259
260    parts = line.strip().split()
261    transport = parts[0].lower()
262
263    if transport == "bluetooth":
264       return ' '.join(["-b", parts[1], "-B", parts[2]])
265    elif transport == "tty":
266       return ' '.join(["-t", parts[1]])
267    elif transport == "irda":
268       return "-i"
269    else:
270       logger.error("Unknown transport '%s'; expected 'bluetooth', 'tty' or 'irda'", transport)
271       sys.exit(1)
272
273 try:
274    obexftp_args = setup_transport()
275 except:
276    logger.exception("Exception while parsing the transport file")
277    sys.exit(1)
278
279 try:
280    g[procname]()
281 except:
282    logger.exception("Exception during run")