Issues (23)

backuppc_clone/helper/BackupClone.py (1 issue)

1
import os
2
import shutil
3
4
from backuppc_clone.Config import Config
5
from backuppc_clone.DataLayer import DataLayer
6
from backuppc_clone.ProgressBar import ProgressBar
7
from backuppc_clone.helper.BackupScanner import BackupScanner
8
from backuppc_clone.misc import sizeof_fmt
9
from backuppc_clone.style.BackupPcCloneStyle import BackupPcCloneStyle
10
11
12 View Code Duplication
class BackupClone:
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
13
    """
14
    Clones a backup of a host
15
    """
16
17
    # ------------------------------------------------------------------------------------------------------------------
18
    def __init__(self, io: BackupPcCloneStyle):
19
        """
20
        Object constructor.
21
22
        @param BackupPcCloneStyle io: The output style.
23
        """
24
25
        self.__io: BackupPcCloneStyle = io
26
        """
27
        The output style.
28
        """
29
30
        self.__host: str = ''
31
        """
32
        The host of the backup.
33
        """
34
35
        self.__backup_no: int = 0
36
        """
37
        The number of the backup.
38
        """
39
40
    # ------------------------------------------------------------------------------------------------------------------
41
    def __scan_host_backup(self, csv_filename: str) -> None:
42
        """
43
        Scans the backup of a host.
44
45
        @param str csv_filename: The name of the CSV file.
46
        """
47
        self.__io.section('Original backup')
48
49
        scanner = BackupScanner(self.__io)
50
        scanner.scan_directory(self.__host, self.__backup_no, csv_filename)
51
52
        self.__io.writeln('')
53
        self.__io.writeln(' Files found:       {}'.format(scanner.file_count))
54
        self.__io.writeln(' Directories found: {}'.format(scanner.dir_count))
55
        self.__io.writeln('')
56
57
    # ------------------------------------------------------------------------------------------------------------------
58
    def __import_host_scan_csv(self, csv_filename: str) -> None:
59
        """
60
        Imports to CSV file with entries of the original pool into the SQLite database.
61
62
        @param str csv_filename: The name of the CSV file.
63
        """
64
        self.__io.log_very_verbose(' Importing <fso>{}</fso>'.format(csv_filename))
65
66
        hst_id = DataLayer.instance.get_host_id(self.__host)
67
        bck_id = DataLayer.instance.get_bck_id(hst_id, int(self.__backup_no))
68
69
        DataLayer.instance.backup_empty(bck_id)
70
        DataLayer.instance.import_csv('BKC_BACKUP_TREE',
71
                                      ['bbt_seq', 'bbt_inode_original', 'bbt_dir', 'bbt_name'],
72
                                      csv_filename,
73
                                      False,
74
                                      {'bck_id': bck_id})
75
76
    # ------------------------------------------------------------------------------------------------------------------
77
    def __import_pre_scan_csv(self, csv_filename: str) -> None:
78
        """
79
        Imports to CSV file with entries of the original pool into the SQLite database.
80
81
        @param str csv_filename: The name of the CSV file.
82
        """
83
        self.__io.section('Using pre-scan')
84
85
        self.__import_host_scan_csv(csv_filename)
86
87
        hst_id = DataLayer.instance.get_host_id(self.__host)
88
        bck_id = DataLayer.instance.get_bck_id(hst_id, int(self.__backup_no))
89
90
        stats = DataLayer.instance.backup_get_stats(bck_id)
91
92
        self.__io.writeln(' Files found:       {}'.format(stats['#files']))
93
        self.__io.writeln(' Directories found: {}'.format(stats['#dirs']))
94
        self.__io.writeln('')
95
96
    # ------------------------------------------------------------------------------------------------------------------
97
    def __copy_pool_file(self, dir_name: str, file_name: str, bpl_inode_original: int) -> int:
98
        """
99
        Copies a pool file from the Original pool to the clone pool. Returns the size eof the file.
100
101
        @param str dir_name: The directory name relative to the top dir.
102
        @param str file_name: The file name.
103
        @param int bpl_inode_original: The inode of the original pool file.
104
105
        :rtype: int
106
        """
107
        original_path = os.path.join(Config.instance.top_dir_original, dir_name, file_name)
108
        clone_dir = os.path.join(Config.instance.top_dir_clone, dir_name)
109
        clone_path = os.path.join(clone_dir, file_name)
110
111
        self.__io.log_very_verbose('Coping <fso>{}</fso> to <fso>{}</fso>'.format(original_path, clone_dir))
112
113
        stats_original = os.stat(original_path)
114
        # BackupPC 3.x renames pool files with hash collisions.
115
        if stats_original.st_ino != bpl_inode_original:
116
            raise FileNotFoundError("Filename '{}' and inode {} do not match".format(original_path, bpl_inode_original))
117
118
        if not os.path.exists(clone_dir):
119
            os.makedirs(clone_dir)
120
121
        shutil.copyfile(original_path, clone_path)
122
123
        stats_clone = os.stat(clone_path)
124
        os.chmod(clone_path, stats_original.st_mode)
125
        os.utime(clone_path, (stats_original.st_mtime, stats_original.st_mtime))
126
127
        DataLayer.instance.pool_update_by_inode_original(stats_original.st_ino,
128
                                                         stats_clone.st_ino,
129
                                                         stats_original.st_size,
130
                                                         stats_original.st_mtime)
131
132
        return stats_original.st_size
133
134
    # ------------------------------------------------------------------------------------------------------------------
135
    def __update_clone_pool(self) -> None:
136
        """
137
        Copies required pool files from the original pool to the clone pool.
138
        """
139
        self.__io.section('Clone pool')
140
        self.__io.writeln(' Adding files ...')
141
        self.__io.writeln('')
142
143
        hst_id = DataLayer.instance.get_host_id(self.__host)
144
        bck_id = DataLayer.instance.get_bck_id(hst_id, self.__backup_no)
145
146
        file_count = DataLayer.instance.backup_prepare_required_clone_pool_files(bck_id)
147
        progress = ProgressBar(self.__io.output, file_count)
148
149
        total_size = 0
150
        file_count = 0
151
        for rows in DataLayer.instance.backup_yield_required_clone_pool_files():
152
            for row in rows:
153
                total_size += self.__copy_pool_file(row['bpl_dir'], row['bpl_name'], row['bpl_inode_original'])
154
                file_count += 1
155
                progress.advance()
156
157
        progress.finish()
158
159
        self.__io.writeln('')
160
        self.__io.writeln(' Number of files copied: {}'.format(file_count))
161
        self.__io.writeln(' Total bytes copied    : {} ({}B) '.format(sizeof_fmt(total_size), total_size))
162
        self.__io.writeln('')
163
164
    # ------------------------------------------------------------------------------------------------------------------
165
    def __clone_backup(self) -> None:
166
        """
167
        Clones the backup.
168
        """
169
        self.__io.section('Clone backup')
170
        self.__io.writeln(' Populating ...')
171
        self.__io.writeln('')
172
173
        hst_id = DataLayer.instance.get_host_id(self.__host)
174
        bck_id = DataLayer.instance.get_bck_id(hst_id, int(self.__backup_no))
175
        DataLayer.instance.backup_set_in_progress(bck_id, 1)
176
177
        backup_dir_clone = Config.instance.backup_dir_clone(self.__host, self.__backup_no)
178
        if os.path.exists(backup_dir_clone):
179
            shutil.rmtree(backup_dir_clone)
180
        os.makedirs(backup_dir_clone)
181
182
        backup_dir_original = Config.instance.backup_dir_original(self.__host, self.__backup_no)
183
        top_dir_clone = Config.instance.top_dir_clone
184
185
        file_count = DataLayer.instance.backup_prepare_tree(bck_id)
186
        progress = ProgressBar(self.__io.output, file_count)
187
188
        file_count = 0
189
        link_count = 0
190
        dir_count = 0
191
        for rows in DataLayer.instance.backup_yield_tree():
192
            for row in rows:
193
                if row['bbt_dir'] is None:
194
                    row['bbt_dir'] = ''
195
196
                target_clone = os.path.join(backup_dir_clone, row['bbt_dir'], row['bbt_name'])
197
198
                if row['bpl_inode_original']:
199
                    # Entry is a file linked to the pool.
200
                    source_clone = os.path.join(top_dir_clone, row['bpl_dir'], row['bpl_name'])
201
                    self.__io.log_very_verbose(
202
                            'Linking to <fso>{}</fso> from <fso>{}</fso>'.format(source_clone, target_clone))
203
                    os.link(source_clone, target_clone)
204
                    link_count += 1
205
206
                elif row['bbt_inode_original']:
207
                    # Entry is a file not linked to the pool.
208
                    source_original = os.path.join(backup_dir_original, row['bbt_dir'], row['bbt_name'])
209
                    self.__io.log_very_verbose('Copying <fso>{}</fso> to <fso>{}</fso>'.format(source_original,
210
                                                                                               target_clone))
211
                    shutil.copy2(source_original, target_clone)
212
                    file_count += 1
213
                else:
214
                    # Entry is a directory
215
                    os.mkdir(target_clone)
216
                    dir_count += 1
217
218
                progress.advance()
219
220
        progress.finish()
221
222
        DataLayer.instance.backup_set_in_progress(bck_id, 0)
223
224
        self.__io.writeln('')
225
        self.__io.writeln(' Number of files copied       : {}'.format(file_count))
226
        self.__io.writeln(' Number of hardlinks created  : {}'.format(link_count))
227
        self.__io.writeln(' Number of directories created: {}'.format(dir_count))
228
        self.__io.writeln('')
229
230
    # ------------------------------------------------------------------------------------------------------------------
231
    def clone_backup(self, host: str, backup_no: int) -> None:
232
        """
233
        Clones a backup of a host.
234
        """
235
        self.__host = host
236
        self.__backup_no = backup_no
237
238
        backup_dir_original = Config.instance.backup_dir_original(host, backup_no)
239
        pre_scan_csv_filename = os.path.join(backup_dir_original, 'backuppc-clone.csv')
240
        if os.path.isfile(pre_scan_csv_filename):
241
            self.__import_pre_scan_csv(pre_scan_csv_filename)
242
        else:
243
            csv_filename = os.path.join(Config.instance.tmp_dir_clone, 'backup-{}-{}.csv'.format(host, backup_no))
244
            self.__scan_host_backup(csv_filename)
245
            self.__import_host_scan_csv(csv_filename)
246
247
        self.__update_clone_pool()
248
        self.__clone_backup()
249
250
# ----------------------------------------------------------------------------------------------------------------------
251