Statistics
| Branch: | Tag: | Revision:

root / image_creator / rsync.py @ 121f3bc0

History | View | Annotate | Download (4.6 kB)

1
# -*- coding: utf-8 -*-
2
#
3
# Copyright 2012 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

    
36
"""This module provides an interface for the rsync utility"""
37

    
38
import subprocess
39
import time
40
import signal
41

    
42
from image_creator.util import FatalError
43

    
44

    
45
class Rsync:
46
    """Wrapper class for the rsync command"""
47

    
48
    def __init__(self, output):
49
        """Create an instance """
50
        self._out = output
51
        self._exclude = []
52
        self._options = ['-v']
53

    
54
    def archive(self):
55
        """Enable the archive option"""
56
        self._options.append('-a')
57
        return self
58

    
59
    def xattrs(self):
60
        """Preserve extended attributes"""
61
        self._options.append('-X')
62
        return self
63

    
64
    def hard_links(self):
65
        """Preserve hard links"""
66
        self._options.append('-H')
67
        return self
68

    
69
    def acls(self):
70
        """Preserve ACLs"""
71
        self._options.append('-A')
72
        return self
73

    
74
    def sparse(self):
75
        """Handle sparse files efficiently"""
76
        self._options.append('-S')
77
        return self
78

    
79
    def exclude(self, pattern):
80
        """Add an exclude pattern"""
81
        self._exclude.append(pattern)
82
        return self
83

    
84
    def reset(self):
85
        """Reset all rsync options"""
86
        self._exclude = []
87
        self._options = ['-v']
88

    
89
    def run(self, src, dest, slabel='source', dlabel='destination'):
90
        """Run the actual command"""
91
        cmd = []
92
        cmd.append('rsync')
93
        cmd.extend(self._options)
94
        for i in self._exclude:
95
            cmd.extend(['--exclude', i])
96

    
97
        self._out.output("Calculating total number of %s files ..." % slabel,
98
                         False)
99

    
100
        # If you don't specify a destination, rsync will list the source files.
101
        dry_run = subprocess.Popen(cmd + [src], shell=False,
102
                                   stdout=subprocess.PIPE, bufsize=0)
103
        try:
104
            total = 0
105
            for _ in iter(dry_run.stdout.readline, b''):
106
                total += 1
107
        finally:
108
            dry_run.communicate()
109
            if dry_run.returncode != 0:
110
                raise FatalError("rsync failed")
111

    
112
        self._out.success("%d" % total)
113

    
114
        progress = self._out.Progress(total, "Copying files to %s" % dlabel)
115
        run = subprocess.Popen(cmd + [src, dest], shell=False,
116
                               stdout=subprocess.PIPE, bufsize=0)
117
        try:
118
            t = time.time()
119
            i = 0
120
            for _ in iter(run.stdout.readline, b''):
121
                i += 1
122
                current = time.time()
123
                if current - t > 0.1:
124
                    t = current
125
                    progress.goto(i)
126

    
127
            progress.success('done')
128

    
129
        finally:
130
            def handler(signum, frame):
131
                run.terminate()
132
                time.sleep(1)
133
                run.poll()
134
                if run.returncode is None:
135
                    run.kill()
136
                run.wait()
137

    
138
            signal.signal(signal.SIGALRM, handler)
139
            signal.alarm(2)
140
            run.communicate()
141
            signal.alarm(0)
142
            if run.returncode != 0:
143
                raise FatalError("rsync failed")
144

    
145
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :