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