Ejemplo n.º 1
0
 def test_uid(self):
     self.generate_and_parse(uid(1))
     self.generate_and_parse(uid(255))
     self.generate_and_parse(uid(256))
     self.generate_and_parse(uid(32_767))
     self.generate_and_parse(uid(65_535))
     self.generate_and_parse(uid(4_000_000_000))
Ejemplo n.º 2
0
    def archive(self, obj) -> uid:
        "Add the encoded form of obj to the archive, returning the UID of obj."

        if obj is None:
            return null_uid

        # the ref_map allows us to avoid infinite recursion caused by
        # cycles in the object graph by functioning as a sort of promise
        ref = self.ref_map.get(id(obj))
        if ref:
            return ref

        index = uid(len(self.objects))
        self.ref_map[id(obj)] = index

        cls = obj.__class__
        if cls in Archive.primitive_types:
            self.objects.append(obj)
            return index

        archive_obj = {}
        self.objects.append(archive_obj)
        self.encode_top_level(obj, archive_obj)

        return index
Ejemplo n.º 3
0
    def uid_for_archiver(self, archiver: type) -> uid:
        """
        Ensure the class definition for the archiver is included in the arcive.

        Non-primitive objects are encoded as a dictionary of key-value pairs;
        there is always a $class key, which has a UID value...the UID is itself
        a pointer/index which points to the definition of the class (which is
        also in the archive).

        This method makes sure that all the metadata is included in the archive
        exactly once (no duplicates class metadata).
        """

        val = self.class_map.get(archiver)
        if val:
            return val

        val = uid(len(self.objects))
        self.class_map[archiver] = val

        # TODO: this is where we might need to include the full class ancestry;
        #       though the open source code from apple does not appear to check
        self.objects.append({'$classes': [archiver], '$classname': archiver})

        return val
Ejemplo n.º 4
0
 def test_circular_ref(self):
     foo = FooArchive('herp', timestamp(9001), 42,
                      ['strawberries', 'dragonfruit'], {'key': 'value'},
                      False, None)
     foo.recursive = foo
     plist = bplist.parse(archiver.archive(foo))
     foo_obj = plist['$objects'][1]
     self.assertEqual(uid(1), foo_obj['recurse'])
Ejemplo n.º 5
0
    def to_bytes(self) -> bytes:
        "Generate the archive and return it as a bytes blob"

        # avoid regenerating
        if len(self.objects) == 1:
            self.archive(self.input)

        d = {
            '$archiver': 'NSKeyedArchiver',
            '$version': NSKeyedArchiveVersion,
            '$objects': self.objects,
            '$top': {
                'root': uid(1)
            }
        }

        return bplist.generate(d)
Ejemplo n.º 6
0
from bpylist import bplist
from bpylist.archive_types import timestamp, uid
from typing import Mapping

# The magic number which Cocoa uses as an implementation version.
# I don' think there were 99_999 previous implementations, I think
# Apple just likes to store a lot of zeros
NSKeyedArchiveVersion = 100_000

# Cached for convenience
null_uid = uid(0)


def unarchive(plist: bytes) -> object:
    "Unpack an NSKeyedArchived byte blob into a more useful object tree."
    return Unarchive(plist).top_object()


def unarchive_file(path: str) -> object:
    "A convenience for unarchive(plist) which loads an archive from a file for you"
    with open(path, 'rb') as fd:
        return unarchive(fd.read())


def archive(obj: object) -> bytes:
    "Pack an object tree into an NSKeyedArchived blob."
    return Archive(obj).to_bytes()


class ArchiverError(Exception):
    pass