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