Index: client/libs/ar/writer.py |
diff --git a/client/libs/ar/writer.py b/client/libs/ar/writer.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bd411b02c2385231d3f7f12317ee80b6ee3d8afb |
--- /dev/null |
+++ b/client/libs/ar/writer.py |
@@ -0,0 +1,109 @@ |
+# Copyright 2016 The LUCI Authors. All rights reserved. |
+# Use of this source code is governed under the Apache License, Version 2.0 |
+# that can be found in the LICENSE file. |
+ |
+import shutil |
+ |
+ |
+class ArWriter(object): |
+ """Write an ar archive to the given output buffer.""" |
+ |
+ # Mode sentinels |
+ MODE_HEADER = [] |
M-A Ruel
2016/06/14 13:26:17
= object()
mithro
2016/06/16 11:37:11
Obsolete.
|
+ MODE_CONTENTS = [] |
+ |
+ def __init__(self, obuf): |
+ self.obuf = obuf |
M-A Ruel
2016/06/14 13:26:17
I'd prefer self._obuf and _ prefix for others too
mithro
2016/06/16 11:37:11
Obsolete.
|
+ self.obuf.write('!<arch>\n') |
+ self.mode = ArWriter.MODE_HEADER |
+ self.needspadding = False |
+ self.bytesrequired = 0 |
+ |
+ def header(self, fp, size, modtime, ownerid, groupid, filemod): |
+ """Write a file information to the archive.""" |
+ assert self.mode == ArWriter.MODE_HEADER |
+ assert self.bytesrequired == 0 |
+ |
+ # File name, 16 bytes |
+ self.obuf.write('#1/%-13s' % str(len(fp))) |
M-A Ruel
2016/06/14 13:26:17
Can you use struct.pack() like you did with unpack
mithro
2016/06/16 11:37:11
No stuct.pack doesn't do this style of output (lef
|
+ # Modtime, 12 bytes |
+ self.obuf.write('%-12i' % modtime) |
+ # Owner ID, 6 bytes |
+ self.obuf.write('%-6i' % ownerid) |
+ # Group ID, 6 bytes |
+ self.obuf.write('%-6i' % groupid) |
+ # File mode, 8 bytes |
+ self.obuf.write('%-8o' % filemod) |
+ |
+ datasize = size+len(fp) |
+ # File size, 10 bytes |
+ self.obuf.write('%-10s' % datasize) |
+ # File magic, 2 bytes |
+ self.obuf.write('\x60\n') |
+ |
+ # Filename - BSD variant |
+ self.obuf.write(fp) |
+ |
+ self.mode = ArWriter.MODE_CONTENTS |
+ self.bytesrequired = size |
+ self.needspadding = datasize % 2 != 0 |
+ |
+ def _write(self, ibuf=None, s=None): |
M-A Ruel
2016/06/14 13:26:17
I think I'd prefer two functions. This is confusin
mithro
2016/06/16 11:37:11
Obsolete.
|
+ assert self.mode == ArWriter.MODE_CONTENTS |
+ assert ibuf is not None or s is not None |
+ |
+ if ibuf is not None: |
+ start = ibuf.tell() |
+ shutil.copyfileobj(ibuf, self.obuf) |
+ end = ibuf.tell() |
+ self.bytesrequired -= end-start |
+ |
+ if s is not None: |
+ self.obuf.write(s) |
+ self.bytesrequired -= len(s) |
+ |
+ if self.bytesrequired == 0: |
M-A Ruel
2016/06/14 13:26:17
if not self._bytesrequired:
mithro
2016/06/16 11:37:11
Obsolete.
|
+ if self.needspadding: |
+ self.obuf.write('\n') |
+ self.mode = ArWriter.MODE_HEADER |
+ |
+ def write(self, ibuf_or_str): |
M-A Ruel
2016/06/14 13:26:17
I'd prefer a writestr() function and not try to gu
mithro
2016/06/16 11:37:11
Obsolete.
|
+ """Write the file body to the archive.""" |
+ try: |
+ self._write(ibuf=ibuf_or_str) |
+ except AttributeError: |
+ self._write(s=ibuf_or_str) |
+ |
+ def close(self): |
+ """Close the archive. Will close the output buffer.""" |
+ assert self.bytesrequired == 0 |
+ assert self.mode == ArWriter.MODE_HEADER |
+ self.obuf.close() |
+ |
+ |
+class ArDefaultWriter(ArWriter): |
+ """Write an ar archive using defaults to the given output buffer. |
+ |
+ Only a file's name and content are needed to create the archive, all of the |
+ modification time, user, group and mode information will be set to default |
+ values. This means that you don't need to perform an expensive stat the file. |
+ """ |
+ DEFAULT_MODTIME = 1447140471 |
+ DEFAULT_USER = 1000 |
+ DEFAULT_GROUP = 1000 |
+ DEFAULT_MODE = 0100640 # 100640 -- Octal |
+ |
+ def header(self, name, size, |
+ modtime=None, ownerid=None, groupid=None, filemod=None): |
M-A Ruel
2016/06/14 13:26:17
I'd prefer to not expose these arguments at all.
mithro
2016/06/16 11:37:11
Obsolete.
|
+ assert modtime is None |
+ assert ownerid is None |
+ assert groupid is None |
+ assert filemod is None |
+ ArWriter.header( |
+ self, name, size, |
+ self.DEFAULT_MODTIME, self.DEFAULT_USER, self.DEFAULT_GROUP, |
+ self.DEFAULT_MODE) |
+ |
+ def add(self, name, data): |
+ self.header(name, len(data)) |
+ self._write(s=data) |