123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- # -*- coding: utf-8 -*-
- """
- # Simple object file format.
- # Copyright (c) 2011-2023 Michael Büsch <m@bues.ch>
- # Licensed under the GNU/GPL version 2 or later.
- """
- import errno
- __all__ = [
- "FileObjError",
- "FileObj",
- "FileObjCollection",
- ]
- class FileObjError(Exception):
- pass
- class FileObj:
- # Raw object layout:
- # [ 1 byte ] => Name length
- # [ x bytes ] => Name
- # [ 4 bytes ] => Payload data length
- # [ x bytes ] => Payload data
- def __init__(self, name, data):
- """Construct FileObj().
- name: The object name. Must be bytes-like.
- data: The object payload. Must be bytes-like.
- """
- assert isinstance(name, (bytes, bytearray)),\
- "FileObj: Invalid 'name' type."
- assert isinstance(data, (bytes, bytearray)),\
- "FileObj: Invalid 'data' type."
- if len(name) > 0x7F:
- raise FileObjError("FileObj: Name too long")
- self.__name = name
- if len(data) > 0x7FFFFFFF:
- raise FileObjError("FileObj: Data too long")
- self.__data = data
- def getName(self):
- return self.__name
- def getData(self):
- return self.__data
- def getRaw(self):
- r = bytearray()
- nameLen = len(self.__name)
- assert nameLen <= 0x7F
- r += b"%c" % (nameLen & 0xFF)
- r += self.__name
- dataLen = len(self.__data)
- assert dataLen <= 0x7FFFFFFF
- r += b"%c" % (dataLen & 0xFF)
- r += b"%c" % ((dataLen >> 8) & 0xFF)
- r += b"%c" % ((dataLen >> 16) & 0xFF)
- r += b"%c" % ((dataLen >> 24) & 0xFF)
- r += self.__data
- return r
- @classmethod
- def parseRaw(cls, raw):
- assert isinstance(raw, (bytes, bytearray)),\
- "FileObj: Invalid 'raw' type."
- try:
- off = 0
- nameLen = raw[off]
- if nameLen & 0x80:
- raise FileObjError("FileObj: Name length extension bit is set, "
- "but not supported by this pwman version.")
- off += 1
- name = raw[off : off + nameLen]
- off += nameLen
- dataLen = (raw[off] |
- (raw[off + 1] << 8) |
- (raw[off + 2] << 16) |
- (raw[off + 3] << 24))
- if dataLen & 0x80000000:
- raise FileObjError("FileObj: Data length extension bit is set, "
- "but not supported by this pwman version.")
- off += 4
- data = raw[off : off + dataLen]
- off += dataLen
- except (IndexError, KeyError) as e:
- raise FileObjError("Failed to parse file object")
- return (cls(name, data), off)
- class FileObjCollection:
- def __init__(self, *objects):
- self.objects = objects
- def writeFile(self, filepath):
- try:
- with open(filepath, "wb") as f:
- f.write(self.getRaw())
- f.flush()
- except IOError as e:
- raise FileObjError("Failed to write file: %s" % e.strerror)
- def getRaw(self):
- raw = bytearray()
- for obj in self.objects:
- raw += obj.getRaw()
- return raw
- def get(self, name):
- return [ o.getData()
- for o in self.objects
- if o.getName() == name ]
- def getOne(self, name, errorMsg=None, default=None):
- objs = self.get(name)
- if len(objs) != 1:
- if errorMsg:
- raise FileObjError(errorMsg)
- return default
- return objs[0]
- @classmethod
- def parseRaw(cls, raw):
- assert isinstance(raw, (bytes, bytearray)),\
- "FileObjCollection: Invalid 'raw' type."
- offset = 0
- objects = []
- while offset < len(raw):
- obj, objLen = FileObj.parseRaw(raw[offset:])
- objects.append(obj)
- offset += objLen
- return cls(*objects)
- @classmethod
- def parseFile(cls, filepath):
- try:
- with open(filepath, "rb") as f:
- rawData = f.read()
- except IOError as e:
- if e.errno != errno.ENOENT:
- raise FileObjError("Failed to read file: %s" % e.strerror)
- return None
- return cls.parseRaw(rawData)
|