[go: up one dir, main page]

File: sync.py

package info (click to toggle)
kiwi 10.2.33-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,528 kB
  • sloc: python: 67,299; sh: 3,980; xml: 3,379; ansic: 391; makefile: 354
file content (144 lines) | stat: -rw-r--r-- 4,979 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# Copyright (c) 2015 SUSE Linux GmbH.  All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
#
import os
import errno
import logging
from stat import ST_MODE
from typing import List

# project
from kiwi.command import Command

log = logging.getLogger('kiwi')


class DataSync:
    """
    **Sync data from a source directory to a target directory**
    """
    def __init__(self, source_dir: str, target_dir: str) -> None:
        """
        Create a new DataSync instance and initialize
        sync source and target

        :param str source_dir: source directory path name
        :param str target_dir: target directory path name
        """
        self.source_dir = source_dir
        self.target_dir = target_dir

    def sync_data(
        self, options: List[str] = [], exclude: List[str] = [],
        force_trailing_slash: bool = False
    ) -> None:
        """
        Sync data from source to target using the rsync protocol

        :param list options: rsync options
        :param list exclude: file patterns to exclude
        :param bool force_trailing_slash: add '/' to source_dir if not present

        A speciality of the rsync tool is that it behaves differently
        if the given source_dir ends with a '/' or not. If it ends
        with a slash the data structure below will be synced to the
        target_dir. If it does not end with a slash the source_dir
        and its contents are synced to the target_dir. For example

        .. code:: bash

            source
              └── some_data

            1. $ rsync -a source target

            target
              └── source
                    └── some_data

            2. $ rsync -a source/ target

            target
              └── some_data

        The parameter force_trailing_slash can be used to make
        sure rsync behaves like shown in the second case. If
        set to true a '/' is appended to the given source_dir
        if not already present
        """
        if force_trailing_slash and not self.source_dir.endswith(os.sep):
            self.source_dir += os.sep
        target_entry_permissions = None
        exclude_options = []
        rsync_options = []
        if options:
            rsync_options = options
        if not self.target_supports_extended_attributes():
            warn_me = False
            if '--xattrs' in rsync_options:
                rsync_options.remove('--xattrs')
                warn_me = True
            if '--acls' in rsync_options:
                rsync_options.remove('--acls')
                warn_me = True
            if warn_me:
                log.warning(
                    'Extended attributes not supported for target: %s',
                    self.target_dir
                )
        if exclude:
            for item in exclude:
                exclude_options.append('--exclude')
                exclude_options.append(
                    '/' + item
                )
        if os.path.exists(self.target_dir):
            target_entry_permissions = os.stat(self.target_dir)[ST_MODE]
        Command.run(
            ['rsync'] + rsync_options + exclude_options + [
                self.source_dir, self.target_dir
            ]
        )
        if target_entry_permissions:
            # rsync applies the permissions of the source directory
            # also to the target directory which is unwanted because
            # only permissions of the files and directories from the
            # source directory and its contents should be transfered
            # but not from the source directory itself. Therefore
            # the permission bits of the target directory before the
            # sync are applied back after sync to ensure they have
            # not changed
            os.chmod(self.target_dir, target_entry_permissions)

    def target_supports_extended_attributes(self) -> bool:
        """
        Check if the target directory supports extended filesystem
        attributes

        :return: True or False

        :rtype: bool
        """
        try:
            os.getxattr(self.target_dir, 'user.mime_type')
        except OSError as e:
            log.debug(
                f'Check for extended attributes on {self.target_dir} said: {e}'
            )
            if e.errno == errno.ENOTSUP:
                return False
        return True