Revision 37bee317

b/docs/source/devguide.rst
25 25
=========================  ================================
26 26
Revision                   Description
27 27
=========================  ================================
28
0.8 (Dec 16, 2011)         Update allowed versioning values.
28
0.8 (Dec 19, 2011)         Update allowed versioning values.
29 29
\                          Change policy/meta formatting in JSON/XML replies.
30 30
\                          Document that all non-ASCII characters in headers should be URL-encoded.
31 31
\                          Support metadata-based queries when listing objects at the container level.
32 32
\                          Note Content-Type issue when using the internal django web server.
33
\                          Add object UUID field.
33 34
0.7 (Nov 21, 2011)         Suggest upload/download methods using hashmaps.
34 35
\                          Propose syncing algorithm.
35 36
\                          Support cross-account object copy and move.
......
478 479
content-disposition         The presentation style of the object (optional)
479 480
last_modified               The last object modification date (regardless of version)
480 481
x_object_hash               The Merkle hash
482
x_object_uuid               The object's UUID
481 483
x_object_version            The object's version identifier
482 484
x_object_version_timestamp  The object's version timestamp
483 485
x_object_modified_by        The user that committed the object's version
......
505 507
    "last_modified": "2011-12-02T08:10:41.565891+00:00",
506 508
    "x_object_meta": {"asdf": "qwerty"},
507 509
    "x_object_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
510
    "x_object_uuid": "8ed9af1b-c948-4bb6-82b0-48344f5c822c",
508 511
    "x_object_version": 98,
509 512
    "x_object_version_timestamp": "1322813441.565891",
510 513
    "x_object_modified_by": "user"}, ...]
......
525 528
        <key>asdf</key><value>qwerty</value>
526 529
      </x_object_meta>
527 530
      <x_object_hash>e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855</x_object_hash>
531
      <x_object_uuid>8ed9af1b-c948-4bb6-82b0-48344f5c822c</x_object_uuid>
528 532
      <x_object_version>98</x_object_version>
529 533
      <x_object_version_timestamp>1322813441.565891</x_object_version_timestamp>
530 534
      <x_object_modified_by>chazapis</x_object_modified_by>
......
680 684
Content-Encoding            The encoding of the object (optional)
681 685
Content-Disposition         The presentation style of the object (optional)
682 686
X-Object-Hash               The Merkle hash
687
X-Object-UUID               The object's UUID
683 688
X-Object-Version            The object's version identifier
684 689
X-Object-Version-Timestamp  The object's version timestamp
685 690
X-Object-Modified-By        The user that comitted the object's version
......
776 781
Content-Encoding            The encoding of the object (optional)
777 782
Content-Disposition         The presentation style of the object (optional)
778 783
X-Object-Hash               The Merkle hash
784
X-Object-UUID               The object's UUID
779 785
X-Object-Version            The object's version identifier
780 786
X-Object-Version-Timestamp  The object's version timestamp
781 787
X-Object-Modified-By        The user that comitted the object's version
......
1063 1069
* Object hashmap retrieval through ``GET`` and the ``format`` parameter.
1064 1070
* Object create via hashmap through ``PUT`` and the ``format`` parameter.
1065 1071
* The object's Merkle hash is always returned in the ``X-Object-Hash`` header.
1072
* The object's UUID is always returned in the ``X-Object-UUID`` header. The UUID remains unchanged, even when the object's data or metadata changes, or the object is moved to another path (is renamed). A new UUID is assigned when creating or copying an object.
1066 1073
* Object create using ``POST`` to support standard HTML forms.
1067 1074
* Partial object updates through ``POST``, using the ``Content-Length``, ``Content-Type``, ``Content-Range`` and ``Transfer-Encoding`` headers. Use another object's data to update with ``X-Source-Object`` and ``X-Source-Version``. Truncate with ``X-Object-Bytes``. New ETag corresponds to the Merkle hash of the object's hashmap.
1068 1075
* Include new version identifier in replies for object replace/change requests.
b/pithos/api/functions.py
551 551
            else:
552 552
                rename_meta_key(meta, 'hash', 'x_object_hash') # Will be replaced by ETag.
553 553
                rename_meta_key(meta, 'ETag', 'hash')
554
                rename_meta_key(meta, 'uuid', 'x_object_uuid')
554 555
                rename_meta_key(meta, 'modified', 'last_modified')
555 556
                rename_meta_key(meta, 'modified_by', 'x_object_modified_by')
556 557
                rename_meta_key(meta, 'version', 'x_object_version')
b/pithos/api/util.py
186 186
    response['Last-Modified'] = http_date(int(meta['modified']))
187 187
    if not restricted:
188 188
        response['X-Object-Hash'] = meta['hash']
189
        response['X-Object-UUID'] = meta['uuid']
189 190
        response['X-Object-Modified-By'] = smart_str(meta['modified_by'], strings_only=True)
190 191
        response['X-Object-Version'] = meta['version']
191 192
        response['X-Object-Version-Timestamp'] = http_date(int(meta['version_timestamp']))
b/pithos/backends/base.py
531 531
        """
532 532
        return []
533 533
    
534
    def get_uuid(self, user, uuid):
535
        """Return the (account, container, name) for the UUID given.
536
        
537
        Raises:
538
            NotAllowedError: Operation not permitted
539
            
540
            NameError: UUID does not exist
541
        """
542
        return None
543
    
534 544
    def get_public(self, user, public):
535 545
        """Return the (account, container, name) for the public id given.
536 546
        
b/pithos/backends/lib/sqlalchemy/node.py
599 599
        """Return a sequence of values for the properties of
600 600
           the version specified by serial and the keys, in the order given.
601 601
           If keys is empty, return all properties in the order
602
           (serial, node, hash, size, source, mtime, muser, cluster).
602
           (serial, node, hash, size, source, mtime, muser, uuid, cluster).
603 603
        """
604 604
        
605 605
        v = self.versions.alias()
606
        s = select([v.c.serial, v.c.node, v.c.hash, v.c.size,
607
                    v.c.source, v.c.mtime, v.c.muser, v.c.cluster], v.c.serial == serial)
606
        s = select([v.c.serial, v.c.node, v.c.hash,
607
                    v.c.size, v.c.source, v.c.mtime,
608
                    v.c.muser, v.c.uuid, v.c.cluster], v.c.serial == serial)
608 609
        rp = self.conn.execute(s)
609 610
        r = rp.fetchone()
610 611
        rp.close()
......
897 898
        rp.close()
898 899
        
899 900
        return matches, prefixes
901
    
902
    def latest_uuid(self, uuid):
903
        """Return a (path, serial) tuple, for the latest version of the given uuid."""
904
        
905
        v = self.versions.alias('v')
906
        n = self.nodes.alias('n')
907
        s = select([n.c.path, v.c.serial])
908
        filtered = select([func.max(self.versions.c.serial)])
909
        s = s.where(v.c.serial == filtered.where(self.versions.c.uuid == uuid))
910
        s = s.where(n.c.node == v.c.node)
911
        
912
        r = self.conn.execute(s)
913
        l = r.fetchone()
914
        r.close()
915
        return l
b/pithos/backends/lib/sqlite/node.py
499 499
        """Return a sequence of values for the properties of
500 500
           the version specified by serial and the keys, in the order given.
501 501
           If keys is empty, return all properties in the order
502
           (serial, node, hash, size, source, mtime, muser, cluster).
502
           (serial, node, hash, size, source, mtime, muser, uuid, cluster).
503 503
        """
504 504
        
505
        q = ("select serial, node, hash, size, source, mtime, muser, cluster "
505
        q = ("select serial, node, hash, size, source, mtime, muser, uuid, cluster "
506 506
             "from versions "
507 507
             "where serial = ?")
508 508
        self.execute(q, (serial,))
......
653 653
        q = ("select distinct a.key "
654 654
             "from attributes a, versions v, nodes n "
655 655
             "where v.serial = (select max(serial) "
656
                              "from versions "
657
                              "where node = v.node and mtime < ?) "
656
                               "from versions "
657
                               "where node = v.node and mtime < ?) "
658 658
             "and v.cluster != ? "
659 659
             "and v.node in (select node "
660
                           "from nodes "
661
                           "where parent = ?) "
660
                            "from nodes "
661
                            "where parent = ?) "
662 662
             "and a.serial = v.serial "
663 663
             "and a.domain = ? "
664 664
             "and n.node = v.node")
......
728 728
        q = ("select distinct n.path, v.serial "
729 729
             "from attributes a, versions v, nodes n "
730 730
             "where v.serial = (select max(serial) "
731
                              "from versions "
732
                              "where node = v.node and mtime < ?) "
731
                               "from versions "
732
                               "where node = v.node and mtime < ?) "
733 733
             "and v.cluster != ? "
734 734
             "and v.node in (select node "
735
                           "from nodes "
736
                           "where parent = ?) "
735
                            "from nodes "
736
                            "where parent = ?) "
737 737
             "and a.serial = v.serial "
738 738
             "and n.node = v.node "
739 739
             "and n.path > ? and n.path < ?")
......
795 795
            execute(q, args)
796 796
        
797 797
        return matches, prefixes
798
    
799
    def latest_uuid(self, uuid):
800
        """Return a (path, serial) tuple, for the latest version of the given uuid."""
801
        
802
        q = ("select n.path, v.serial "
803
             "from versions v, nodes n "
804
             "where v.serial = (select max(serial) "
805
                               "from versions "
806
                               "where uuid = ?) "
807
             "and n.node = v.node")
808
        self.execute(q, (uuid,))
809
        return self.fetchone()
b/pithos/backends/modular.py
34 34
import sys
35 35
import os
36 36
import time
37
import uuid
37
import uuid as uuidlib
38 38
import logging
39 39
import binascii
40 40

  
......
96 96
        for x in ['READ', 'WRITE']:
97 97
            setattr(self, x, getattr(self.db_module, x))
98 98
        self.node = self.db_module.Node(**params)
99
        for x in ['ROOTNODE', 'SERIAL', 'HASH', 'SIZE', 'MTIME', 'MUSER', 'CLUSTER']:
99
        for x in ['ROOTNODE', 'SERIAL', 'HASH', 'SIZE', 'MTIME', 'MUSER', 'UUID', 'CLUSTER']:
100 100
            setattr(self, x, getattr(self.db_module, x))
101 101
        
102 102
        __import__(block_module)
......
630 630
        return [[x[self.SERIAL], x[self.MTIME]] for x in versions if x[self.CLUSTER] != CLUSTER_DELETED]
631 631
    
632 632
    @backend_method
633
    def get_uuid(self, user, uuid):
634
        """Return the (account, container, name) for the UUID given."""
635
        logger.debug("get_uuid: %s", uuid)
636
        info = self.node.latest_uuid(uuid)
637
        if info is None:
638
            raise NameError
639
        path, serial = info
640
        account, container, name = path.split('/', 2)
641
        self._can_read(user, account, container, name)
642
        return (account, container, name)
643
    
644
    @backend_method
633 645
    def get_public(self, user, public):
634 646
        """Return the (account, container, name) for the public id given."""
635 647
        logger.debug("get_public: %s", public)
636 648
        if public is None or public < ULTIMATE_ANSWER:
637 649
            raise NameError
638 650
        path = self.permissions.public_path(public - ULTIMATE_ANSWER)
651
        if path is None:
652
            raise NameError
639 653
        account, container, name = path.split('/', 2)
640 654
        self._can_read(user, account, container, name)
641 655
        return (account, container, name)
......
669 683
    
670 684
    # Path functions.
671 685
    
672
    def _generate_uuid():
673
        return str(uuid.uuid4())
686
    def _generate_uuid(self):
687
        return str(uuidlib.uuid4())
674 688
    
675 689
    def _put_object_node(self, path, parent, name):
676 690
        path = '/'.join((path, name))
......
756 770
        if size is None:
757 771
            hash = src_hash # This way hash can be set to None.
758 772
            size = src_size
759
        uniq = self._generate_uuid() if is_copy or src_version_id is None else props[self.UUID]
773
        uuid = self._generate_uuid() if is_copy or src_version_id is None else props[self.UUID]
760 774
        
761 775
        if src_version_id is not None:
762 776
            self.node.version_recluster(src_version_id, CLUSTER_HISTORY)
763
        dest_version_id, mtime = self.node.version_create(node, hash, size, src_version_id, user, uniq, cluster)
777
        dest_version_id, mtime = self.node.version_create(node, hash, size, src_version_id, user, uuid, cluster)
764 778
        return src_version_id, dest_version_id
765 779
    
766 780
    def _put_metadata_duplicate(self, src_version_id, dest_version_id, domain, meta, replace=False):

Also available in: Unified diff