""" Parses ai1wm file header. """ import collections import struct from error import Ai1wmError from tools import b__, s__ class Ai1wmHeader(tuple): """ Parses an `All-in-One WP Migration` header. """ SIZE = 4377 EOF = b'\x00' * SIZE _Location = collections.namedtuple('_Location', ['offset', 'size']) _LOC_NAME = _Location(0, 255) # File name _LOC_SIZE = _Location(255, 14) # File size _LOC_TIME = _Location(269, 12) # Last modified time _LOC_PATH = _Location(281, 4096) # File path def __new__(cls, path=None, name=None, size=None, time=None): """ Returns a new instance of the object. """ if path or name or size or time: if not isinstance(path, str) or path == '': raise ValueError('<path> must be a nonempty string') if not isinstance(name, str) or name == '': raise ValueError('<name> must be a nonempty string') if not isinstance(size, int) or size < 0: raise ValueError('<size> must be a non-negative integer') if not isinstance(time, int) or time < 0: raise ValueError('<time> must be a non-negative integer') return super(Ai1wmHeader, cls).__new__(cls, [path, name, size, time]) @classmethod def unpack(cls, header): """ Unpacks a binary header. """ if len(header) != cls.SIZE: raise Ai1wmError('invalid header size') if header == cls.EOF: return cls() return cls( path=s__(cls.__extract_field(header, cls._LOC_PATH)), name=s__(cls.__extract_field(header, cls._LOC_NAME)), size=cls.__extract_int(header, cls._LOC_SIZE), time=cls.__extract_int(header, cls._LOC_TIME), ) def pack(self): """ Packs to a binary header. """ attributes, formats, locations = [], '', [ ('name', self._LOC_NAME), ('size', self._LOC_SIZE), ('time', self._LOC_TIME), ('path', self._LOC_PATH), ] for name, location in locations: attribute = b__(getattr(self, name)) if len(attribute) > location.size: raise Ai1wmError('{} is too long to pack: {}'.format(name, getattr(self, name))) attributes.append(attribute) formats += '{}s'.format(location.size) return struct.pack(formats, *attributes) @property def path(self): """ Path of the file. """ return self[0] @property def name(self): """ Name of the file. """ return self[1] @property def size(self): """ Size of the file. """ return self[2] @property def time(self): """ Time of the file. """ return self[3] @property def eof(self): """ Indicates if this is an EOF header. """ return not any(self) @classmethod def __extract_field(cls, header, location): """ Extracts a header field. """ try: field = struct.unpack_from('{}s'.format(location.size), header, offset=location.offset)[0] except struct.error as e: raise Ai1wmError('error extracting a header field, error: {}'.format(e)) return field.rstrip(b'\x00') @classmethod def __extract_int(cls, header, location): """ Extracts an integral header field. """ try: return int(cls.__extract_field(header, location)) except ValueError: raise Ai1wmError('invalid header field')