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