~lmirror/lmirror/master

« back to all changes in this revision

Viewing changes to l_mirror/journals.py

  • Committer: Robert Collins
  • Date: 2014-09-14 05:03:18 UTC
  • Revision ID: git-v1:aeeff6ea24c32a63a3470da2d956c928c355200d
Fix converged files and streamed replacements.

When files were replaced and streaming was in use a error would be
thrown from the logging code if the file was ignored - which happens
when the content has converged with that already on disk (e.g. because
an earlier mirror run was interrupted).

Secondly, when files had converged, we would end up deleting the file
rather than leaving it alone, due to a bug in replay() that would
affect all users, not just streaming.

Show diffs side-by-side

added added

removed removed

Lines of Context:
759
759
        return self.sourcedir.get(self.path)
760
760
 
761
761
    def ignore_file(self):
 
762
        if type(self.content) is tuple:
 
763
            content = self.content[1]
 
764
        else:
 
765
            content = self.content
762
766
        self.ui.output_log(
763
 
            4, __name__, 'Ignoring %s %r' % (self.content.kind, self.path))
 
767
            4, __name__, 'Ignoring %s %r' % (content.kind, self.path))
764
768
 
765
769
 
766
770
class StreamedAction(Action):
782
786
        return BufferedFile(self.generator, content.length, self.ui)
783
787
 
784
788
    def ignore_file(self):
 
789
        if type(self.content) is tuple:
 
790
            content = self.content[1]
 
791
        else:
 
792
            content = self.content
785
793
        self.ui.output_log(
786
 
            4, __name__, 'Ignoring %s %r' % (self.content.kind, self.path))
787
 
        self.get_file().close()
 
794
            4, __name__, 'Ignoring %s %r' % (content.kind, self.path))
 
795
        if self.type != 'del':
 
796
            self.get_file().close()
788
797
 
789
798
 
790
799
class BufferedFile(object):
859
868
        self.buffered_bytes = []
860
869
        self.ui = ui
861
870
 
862
 
    def parse_kind_data(self, tokens):
863
 
        pos = 2
 
871
    def parse_kind_data(self, tokens, pos):
864
872
        kind = tokens[pos]
865
873
        pos += 1
866
874
        if kind == 'file':
883
891
            # TODO: make this more efficient: but wait till its shown to be a
884
892
            # key issue to address.
885
893
            some_bytes = self._next_bytes(4096)
 
894
            if not some_bytes:
 
895
                return
886
896
            tokens = some_bytes.split('\x00')
887
897
            path = tokens[0]
888
898
            action = tokens[1]
889
899
            kind = tokens[2]
890
900
            if action in ('new', 'del'):
891
 
                kind_data, pos = self.parse_kind_data(tokens)
 
901
                kind_data, pos = self.parse_kind_data(tokens, 2)
892
902
            elif action == 'replace':
893
 
                kind_data1, pos = self.parse_kind_data(tokens)
894
 
                kind_data2, pos = self.parse_kind_data(tokens)
 
903
                kind_data1, pos = self.parse_kind_data(tokens, 2)
 
904
                kind_data2, pos = self.parse_kind_data(tokens, pos)
895
905
                kind_data = kind_data1, kind_data2
896
906
            else:
897
907
                raise ValueError('unknown action %r' % action)
927
937
            content = self._stream.read(65536)
928
938
 
929
939
 
 
940
class CancellableDelete:
 
941
    """Group a number of actions and allow them to all be cancelled at once."""
 
942
 
 
943
    def __init__(self, content, contentdir, path, ui):
 
944
        self.cancelled = False
 
945
        self.content = content
 
946
        self.contentdir = contentdir
 
947
        self.path = path
 
948
        self.ui = ui
 
949
 
 
950
    def delete(self):
 
951
        # TODO: we may want to warn or perhaps have a strict mode here.
 
952
        # e.g. handle already deleted things. This should become clear
 
953
        # when recovery mode is done.
 
954
        if self.cancelled:
 
955
            return
 
956
        self.ui.output_log(4, __name__, 'Deleting %s %r' %
 
957
            (self.content.kind, self.path))
 
958
        try:
 
959
            if self.content.kind != 'dir':
 
960
                self.contentdir.delete(self.path)
 
961
            else:
 
962
                self.contentdir.rmdir(self.path)
 
963
        except errors.NoSuchFile:
 
964
            # Already gone, ignore it.
 
965
            pass
 
966
 
 
967
 
930
968
class TransportReplay(object):
931
969
    """Replay a journal reading content from a transport.
932
970
 
978
1016
                    if action == 'replace':
979
1017
                        # TODO: (again, to do with replacing files with dirs:)
980
1018
                        #       do not delay creating dirs needed for files
981
 
                        #       below them.
982
 
                        to_rename.append(self.put_with_check(path, content[1],
983
 
                            action_obj))
984
 
                        to_delete.append((path, content[0]))
 
1019
                        #       below them, or create the files in the temp
 
1020
                        #       dir.
 
1021
                        cancellable = CancellableDelete(
 
1022
                            content[0], self.contentdir, path, self.ui)
 
1023
                        to_rename.append(
 
1024
                            self.put_with_check(path, content[1], action_obj,
 
1025
                                cancellable))
 
1026
                        to_delete.append(cancellable)
985
1027
                    if action == 'del':
986
 
                        to_delete.append((path, content))
987
 
                for path, content in to_delete:
 
1028
                        cancellable = CancellableDelete(
 
1029
                            content, self.contentdir, path, self.ui)
 
1030
                        to_delete.append(cancellable)
 
1031
                for cancellable in to_delete:
988
1032
                    # Second pass on the group to handle deletes as late as possible
989
 
                    # TODO: we may want to warn or perhaps have a strict mode here.
990
 
                    # e.g. handle already deleted things. This should become clear
991
 
                    # when recovery mode is done.
992
 
                    self.ui.output_log(4, __name__, 'Deleting %s %r' %
993
 
                        (content.kind, path))
994
 
                    try:
995
 
                        if content.kind != 'dir':
996
 
                            self.contentdir.delete(path)
997
 
                        else:
998
 
                            self.contentdir.rmdir(path)
999
 
                    except errors.NoSuchFile:
1000
 
                        # Already gone, ignore it.
1001
 
                        pass
 
1033
                    cancellable.delete()
1002
1034
            finally:
1003
1035
                for doit in to_rename:
1004
1036
                    doit()
1061
1093
            else:
1062
1094
                raise
1063
1095
 
1064
 
    def put_with_check(self, path, content, action):
 
1096
    def put_with_check(self, path, content, action, cancellable=None):
1065
1097
        """Put a_file at path checking that as received it matches content.
1066
1098
 
1067
1099
        :param path: A relpath.
1068
1100
        :param content: A content description of a file.
1069
1101
        :param action: An action object which can supply file content.
 
1102
        :param cancellable: A Cancellable object to use when the content is
 
1103
            already present locally.
1070
1104
        :return: A callable that will execute the rename-into-place - all the
1071
1105
            IO has been done before returning.
1072
1106
        """
1083
1117
        try:
1084
1118
            if self.check_file(path, content):
1085
1119
                action.ignore_file()
 
1120
                if cancellable:
 
1121
                    cancellable.cancelled = True
1086
1122
                return lambda:None
1087
1123
        except (ValueError, IOError):
1088
1124
            # If we can't read the file for some reason, we obviously need to