]> arthur.barton.de Git - bup.git/blob - lib/bup/cmd/ftp.py
chmod -x lib/bup/cmd/*.py
[bup.git] / lib / bup / cmd / ftp.py
1
2 # For now, this completely relies on the assumption that the current
3 # encoding (LC_CTYPE, etc.) is ASCII compatible, and that it returns
4 # the exact same bytes from a decode/encode round-trip (or the reverse
5 # (e.g. ISO-8859-1).
6
7 from __future__ import absolute_import, print_function
8 import os, fnmatch, stat, sys, traceback
9
10 from bup import _helpers, options, git, shquote, ls, vfs
11 from bup.compat import argv_bytes
12 from bup.helpers import chunkyreader, log, saved_errors
13 from bup.io import byte_stream, path_msg
14 from bup.repo import LocalRepo
15
16
17 repo = None
18
19 class OptionError(Exception):
20     pass
21
22
23 def do_ls(repo, pwd, args, out):
24     pwd_str = b'/'.join(name for name, item in pwd) or b'/'
25     try:
26         opt = ls.opts_from_cmdline(args, onabort=OptionError, pwd=pwd_str)
27     except OptionError as e:
28         log('error: %s' % e)
29         return None
30     return ls.within_repo(repo, opt, out, pwd_str)
31
32
33 def write_to_file(inf, outf):
34     for blob in chunkyreader(inf):
35         outf.write(blob)
36
37
38 def _completer_get_subs(repo, line):
39     (qtype, lastword) = shquote.unfinished_word(line)
40     dir, name = os.path.split(lastword)
41     dir_path = vfs.resolve(repo, dir or b'/')
42     _, dir_item = dir_path[-1]
43     if not dir_item:
44         subs = tuple()
45     else:
46         subs = tuple(dir_path + (entry,)
47                      for entry in vfs.contents(repo, dir_item)
48                      if (entry[0] != b'.' and entry[0].startswith(name)))
49     return qtype, lastword, subs
50
51
52 _attempt_start = None
53 _attempt_end = None
54 def attempt_completion(text, start, end):
55     global _attempt_start, _attempt_end
56     _attempt_start = start
57     _attempt_end = end
58
59 _last_line = None
60 _last_res = None
61 def enter_completion(text, iteration):
62     global repo
63     global _attempt_end
64     global _last_line
65     global _last_res
66     try:
67         line = _helpers.get_line_buffer()[:_attempt_end]
68         if _last_line != line:
69             _last_res = _completer_get_subs(repo, line)
70             _last_line = line
71         qtype, lastword, subs = _last_res
72         if iteration < len(subs):
73             path = subs[iteration]
74             leaf_name, leaf_item = path[-1]
75             res = vfs.try_resolve(repo, leaf_name, parent=path[:-1])
76             leaf_name, leaf_item = res[-1]
77             fullname = os.path.join(*(name for name, item in res))
78             if stat.S_ISDIR(vfs.item_mode(leaf_item)):
79                 ret = shquote.what_to_add(qtype, lastword, fullname + b'/',
80                                           terminate=False)
81             else:
82                 ret = shquote.what_to_add(qtype, lastword, fullname,
83                                           terminate=True) + b' '
84             return text + ret
85     except Exception as e:
86         log('\n')
87         _, _, tb = sys.exc_info()
88         traceback.print_tb(tb)
89         log('\nError in completion: %s\n' % e)
90     return None
91
92
93 optspec = """
94 bup ftp [commands...]
95 """
96
97
98 def inputiter(f, pwd, out):
99     if os.isatty(f.fileno()):
100         while 1:
101             prompt = b'bup %s> ' % (b'/'.join(name for name, item in pwd) or b'/', )
102             if hasattr(_helpers, 'readline'):
103                 try:
104                     yield _helpers.readline(prompt)
105                 except EOFError:
106                     print()  # Clear the line for the terminal's next prompt
107                     break
108             else:
109                 out.write(prompt)
110                 out.flush()
111                 read_line = f.readline()
112                 if not read_line:
113                     print('')
114                     break
115                 yield read_line
116     else:
117         for line in f:
118             yield line
119
120
121 def present_interface(stdin, out, extra, repo):
122     pwd = vfs.resolve(repo, b'/')
123
124     if extra:
125         lines = (argv_bytes(arg) for arg in extra)
126     else:
127         if hasattr(_helpers, 'readline'):
128             _helpers.set_completer_word_break_characters(b' \t\n\r/')
129             _helpers.set_attempted_completion_function(attempt_completion)
130             _helpers.set_completion_entry_function(enter_completion)
131             if sys.platform.startswith('darwin'):
132                 # MacOS uses a slightly incompatible clone of libreadline
133                 _helpers.parse_and_bind(b'bind ^I rl_complete')
134             _helpers.parse_and_bind(b'tab: complete')
135         lines = inputiter(stdin, pwd, out)
136
137     for line in lines:
138         if not line.strip():
139             continue
140         words = [word for (wordstart,word) in shquote.quotesplit(line)]
141         cmd = words[0].lower()
142         #log('execute: %r %r\n' % (cmd, parm))
143         try:
144             if cmd == b'ls':
145                 do_ls(repo, pwd, words[1:], out)
146                 out.flush()
147             elif cmd == b'cd':
148                 np = pwd
149                 for parm in words[1:]:
150                     res = vfs.resolve(repo, parm, parent=np)
151                     _, leaf_item = res[-1]
152                     if not leaf_item:
153                         raise Exception('%s does not exist'
154                                         % path_msg(b'/'.join(name for name, item
155                                                              in res)))
156                     if not stat.S_ISDIR(vfs.item_mode(leaf_item)):
157                         raise Exception('%s is not a directory' % path_msg(parm))
158                     np = res
159                 pwd = np
160             elif cmd == b'pwd':
161                 if len(pwd) == 1:
162                     out.write(b'/')
163                 out.write(b'/'.join(name for name, item in pwd) + b'\n')
164                 out.flush()
165             elif cmd == b'cat':
166                 for parm in words[1:]:
167                     res = vfs.resolve(repo, parm, parent=pwd)
168                     _, leaf_item = res[-1]
169                     if not leaf_item:
170                         raise Exception('%s does not exist' %
171                                         path_msg(b'/'.join(name for name, item
172                                                            in res)))
173                     with vfs.fopen(repo, leaf_item) as srcfile:
174                         write_to_file(srcfile, out)
175                 out.flush()
176             elif cmd == b'get':
177                 if len(words) not in [2,3]:
178                     raise Exception('Usage: get <filename> [localname]')
179                 rname = words[1]
180                 (dir,base) = os.path.split(rname)
181                 lname = len(words) > 2 and words[2] or base
182                 res = vfs.resolve(repo, rname, parent=pwd)
183                 _, leaf_item = res[-1]
184                 if not leaf_item:
185                     raise Exception('%s does not exist' %
186                                     path_msg(b'/'.join(name for name, item in res)))
187                 with vfs.fopen(repo, leaf_item) as srcfile:
188                     with open(lname, 'wb') as destfile:
189                         log('Saving %s\n' % path_msg(lname))
190                         write_to_file(srcfile, destfile)
191             elif cmd == b'mget':
192                 for parm in words[1:]:
193                     dir, base = os.path.split(parm)
194
195                     res = vfs.resolve(repo, dir, parent=pwd)
196                     _, dir_item = res[-1]
197                     if not dir_item:
198                         raise Exception('%s does not exist' % path_msg(dir))
199                     for name, item in vfs.contents(repo, dir_item):
200                         if name == b'.':
201                             continue
202                         if fnmatch.fnmatch(name, base):
203                             if stat.S_ISLNK(vfs.item_mode(item)):
204                                 deref = vfs.resolve(repo, name, parent=res)
205                                 deref_name, deref_item = deref[-1]
206                                 if not deref_item:
207                                     raise Exception('%s does not exist' %
208                                                     path_msg('/'.join(name for name, item
209                                                                       in deref)))
210                                 item = deref_item
211                             with vfs.fopen(repo, item) as srcfile:
212                                 with open(name, 'wb') as destfile:
213                                     log('Saving %s\n' % path_msg(name))
214                                     write_to_file(srcfile, destfile)
215             elif cmd in (b'help', b'?'):
216                 out.write(b'Commands: ls cd pwd cat get mget help quit\n')
217                 out.flush()
218             elif cmd in (b'quit', b'exit', b'bye'):
219                 break
220             else:
221                 raise Exception('no such command %r' % cmd)
222         except Exception as e:
223             log('error: %s\n' % e)
224             raise
225
226 def main(argv):
227     global repo
228
229     o = options.Options(optspec)
230     opt, flags, extra = o.parse_bytes(argv[1:])
231
232     git.check_repo_or_die()
233     sys.stdout.flush()
234     out = byte_stream(sys.stdout)
235     stdin = byte_stream(sys.stdin)
236     with LocalRepo() as repo:
237         present_interface(stdin, out, extra, repo)
238     if saved_errors:
239         log('warning: %d errors encountered\n' % len(saved_errors))
240         sys.exit(1)