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