]> arthur.barton.de Git - bup.git/blob - cmd/server-cmd.py
Change server_mode=='dumb' to just a dumb_server_mode bool.
[bup.git] / cmd / server-cmd.py
1 #!/usr/bin/env python
2 import os, sys, struct
3 from bup import options, git
4 from bup.helpers import *
5
6 suspended_w = None
7 dumb_server_mode = False
8
9 def _set_mode():
10     global dumb_server_mode
11     dumb_server_mode = os.path.exists(git.repo('bup-dumb-server'))
12     debug1('bup server: serving in %s mode\n' 
13            % (dumb_server_mode and 'dumb' or 'smart'))
14
15
16 def init_dir(conn, arg):
17     git.init_repo(arg)
18     debug1('bup server: bupdir initialized: %r\n' % git.repodir)
19     _set_mode()
20     conn.ok()
21
22
23 def set_dir(conn, arg):
24     git.check_repo_or_die(arg)
25     debug1('bup server: bupdir is %r\n' % git.repodir)
26     _set_mode()
27     conn.ok()
28
29     
30 def list_indexes(conn, junk):
31     git.check_repo_or_die()
32     suffix = ''
33     if dumb_server_mode:
34         suffix = ' load'
35     for f in os.listdir(git.repo('objects/pack')):
36         if f.endswith('.idx'):
37             conn.write('%s%s\n' % (f, suffix))
38     conn.ok()
39
40
41 def send_index(conn, name):
42     git.check_repo_or_die()
43     assert(name.find('/') < 0)
44     assert(name.endswith('.idx'))
45     idx = git.open_idx(git.repo('objects/pack/%s' % name))
46     conn.write(struct.pack('!I', len(idx.map)))
47     conn.write(idx.map)
48     conn.ok()
49
50
51 def receive_objects_v2(conn, junk):
52     global suspended_w
53     git.check_repo_or_die()
54     suggested = {}
55     if suspended_w:
56         w = suspended_w
57         suspended_w = None
58     else:
59         w = git.PackWriter()
60     while 1:
61         ns = conn.read(4)
62         if not ns:
63             w.abort()
64             raise Exception('object read: expected length header, got EOF\n')
65         n = struct.unpack('!I', ns)[0]
66         #debug2('expecting %d bytes\n' % n)
67         if not n:
68             debug1('bup server: received %d object%s.\n' 
69                 % (w.count, w.count!=1 and "s" or ''))
70             fullpath = w.close(run_midx=not dumb_server_mode)
71             if fullpath:
72                 (dir, name) = os.path.split(fullpath)
73                 conn.write('%s.idx\n' % name)
74             conn.ok()
75             return
76         elif n == 0xffffffff:
77             debug2('bup server: receive-objects suspended.\n')
78             suspended_w = w
79             conn.ok()
80             return
81             
82         shar = conn.read(20)
83         crcr = struct.unpack('!I', conn.read(4))[0]
84         n -= 20 + 4
85         buf = conn.read(n)  # object sizes in bup are reasonably small
86         #debug2('read %d bytes\n' % n)
87         if len(buf) < n:
88             w.abort()
89             raise Exception('object read: expected %d bytes, got %d\n'
90                             % (n, len(buf)))
91         if dumb_server_mode:
92             oldpack = None
93         else:
94             oldpack = w.exists(shar)
95         # FIXME: we only suggest a single index per cycle, because the client
96         # is currently too dumb to download more than one per cycle anyway.
97         # Actually we should fix the client, but this is a minor optimization
98         # on the server side.
99         if not suggested and \
100           oldpack and (oldpack == True or oldpack.endswith('.midx')):
101             # FIXME: we shouldn't really have to know about midx files
102             # at this layer.  But exists() on a midx doesn't return the
103             # packname (since it doesn't know)... probably we should just
104             # fix that deficiency of midx files eventually, although it'll
105             # make the files bigger.  This method is certainly not very
106             # efficient.
107             oldpack = w.objcache.packname_containing(shar)
108             debug2('new suggestion: %r\n' % oldpack)
109             assert(oldpack)
110             assert(oldpack != True)
111             assert(not oldpack.endswith('.midx'))
112             w.objcache.refresh()
113         if not suggested and oldpack:
114             assert(oldpack.endswith('.idx'))
115             (dir,name) = os.path.split(oldpack)
116             if not (name in suggested):
117                 debug1("bup server: suggesting index %s\n" % name)
118                 conn.write('index %s\n' % name)
119                 suggested[name] = 1
120         else:
121             nw, crc = w._raw_write([buf], sha=shar)
122             _check(w, crcr, crc, 'object read: expected crc %d, got %d\n')
123             _check(w, n, nw, 'object read: expected %d bytes, got %d\n')
124     # NOTREACHED
125     
126
127 def _check(w, expected, actual, msg):
128     if expected != actual:
129         w.abort()
130         raise Exception(msg % (expected, actual))
131
132
133 def read_ref(conn, refname):
134     git.check_repo_or_die()
135     r = git.read_ref(refname)
136     conn.write('%s\n' % (r or '').encode('hex'))
137     conn.ok()
138
139
140 def update_ref(conn, refname):
141     git.check_repo_or_die()
142     newval = conn.readline().strip()
143     oldval = conn.readline().strip()
144     git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
145     conn.ok()
146
147
148 cat_pipe = None
149 def cat(conn, id):
150     global cat_pipe
151     git.check_repo_or_die()
152     if not cat_pipe:
153         cat_pipe = git.CatPipe()
154     try:
155         for blob in cat_pipe.join(id):
156             conn.write(struct.pack('!I', len(blob)))
157             conn.write(blob)
158     except KeyError, e:
159         log('server: error: %s\n' % e)
160         conn.write('\0\0\0\0')
161         conn.error(e)
162     else:
163         conn.write('\0\0\0\0')
164         conn.ok()
165
166
167 optspec = """
168 bup server
169 """
170 o = options.Options('bup server', optspec)
171 (opt, flags, extra) = o.parse(sys.argv[1:])
172
173 if extra:
174     o.fatal('no arguments expected')
175
176 debug2('bup server: reading from stdin.\n')
177
178 commands = {
179     'init-dir': init_dir,
180     'set-dir': set_dir,
181     'list-indexes': list_indexes,
182     'send-index': send_index,
183     'receive-objects-v2': receive_objects_v2,
184     'read-ref': read_ref,
185     'update-ref': update_ref,
186     'cat': cat,
187 }
188
189 # FIXME: this protocol is totally lame and not at all future-proof.
190 # (Especially since we abort completely as soon as *anything* bad happens)
191 conn = Conn(sys.stdin, sys.stdout)
192 lr = linereader(conn)
193 for _line in lr:
194     line = _line.strip()
195     if not line:
196         continue
197     debug1('bup server: command: %r\n' % line)
198     words = line.split(' ', 1)
199     cmd = words[0]
200     rest = len(words)>1 and words[1] or ''
201     if cmd == 'quit':
202         break
203     else:
204         cmd = commands.get(cmd)
205         if cmd:
206             cmd(conn, rest)
207         else:
208             raise Exception('unknown server command: %r\n' % line)
209
210 debug1('bup server: done\n')