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