Completed
Push — ezp25946-migrate_files_to_othe... ( 341a98...ed9b23 )
by
unknown
11:51
created

MigrateFilesCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 3
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the MigrateFilesCommand class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Bundle\EzPublishIOBundle\Command;
10
11
use eZ\Bundle\EzPublishIOBundle\Migration\MigrationHandlerInterface;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Input\InputOption;
15
use Symfony\Component\Console\Helper\ProgressBar;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Question\ConfirmationQuestion;
18
19
class MigrateFilesCommand extends Command
20
{
21
    /** @var mixed Configuration for metadata handlers */
22
    private $configuredMetadataHandlers;
23
24
    /** @var mixed Configuration for binary data handlers */
25
    private $configuredBinarydataHandlers;
26
27
    /** @var \eZ\Bundle\EzPublishIOBundle\Migration\MigrationHandlerInterface */
28
    private $migrationHandler;
29
30
    public function __construct(
31
        $configuredMetadataHandlers,
32
        $configuredBinarydataHandlers,
33
        MigrationHandlerInterface $migrationHandler
34
    ) {
35
        $this->configuredMetadataHandlers = $configuredMetadataHandlers;
36
        $this->configuredBinarydataHandlers = $configuredBinarydataHandlers;
37
        $this->configuredMetadataHandlers['default'] = [];
38
        $this->configuredBinarydataHandlers['default'] = [];
39
40
        $this->migrationHandler = $migrationHandler;
41
42
        parent::__construct();
43
    }
44
45
    protected function configure()
46
    {
47
        $this
48
            ->setName('ezplatform:io:migrate-files')
49
            ->setDescription('Migrates files from one IO repository to another')
50
            ->addOption('from', null, InputOption::VALUE_REQUIRED, 'Migrate from <from_metadata_handler>,<from_binarydata_handler>')
51
            ->addOption('to', null, InputOption::VALUE_REQUIRED, 'Migrate to <to_metadata_handler>,<to_binarydata_handler>')
52
            ->addOption('list-io-configs', null, InputOption::VALUE_NONE, 'List available IO configurations')
53
            ->addOption('remove-files', null, InputOption::VALUE_NONE, 'Remove source files after copying')
54
            ->addOption('bulk-count', null, InputOption::VALUE_REQUIRED, 'Number of files processed at once', 100)
55
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Execute a dry run')
56
            ->setHelp(
57
                <<<EOT
58
The command <info>%command.name%</info> migrates files from one IO repository
59
to another.
60
61
It can for example be used to migrate local files from the default IO
62
configuration to a new IO configuration, like a clustered setup.
63
64
The <info>--from</info> and <info>--to</info> values must be specified as <info><metadata_handler>,<binarydata_handler></info>.
65
If <info>--from</info> is omitted, the default IO configuration will be used.
66
If <info>--to</info> is omitted, the first non-default IO configuration will be used.
67
68
<fg=red>During the script execution the files should not be modified. To avoid
69
surprises you are advised to create a backup and/or execute a dry run before
70
proceeding with actual update.</>
71
72
Since this script can potentially run for a very long time, to avoid memory
73
exhaustion run it in production environment using <info>--env=prod</info> switch.
74
75
EOT
76
            );
77
    }
78
79
    protected function execute(InputInterface $input, OutputInterface $output)
80
    {
81
        if ($input->getOption('list-io-configs')) {
82
            $this->outputConfiguredHandlers($output);
83
84
            return;
85
        }
86
87
        $bulkCount = (int)$input->getOption('bulk-count');
88
        if ($bulkCount < 1) {
89
            $output->writeln('The value for --bulk-count must be a positive integer.');
90
91
            return;
92
        }
93
94
        $output->writeln($this->getProcessedHelp());
95
96
        $fromHandlers = $input->getOption('from') ? explode(',', $input->getOption('from')) : null;
97
        $toHandlers = $input->getOption('to') ? explode(',', $input->getOption('to')) : null;
98
99
        if (!$fromHandlers) {
100
            $fromHandlers = ['default', 'default'];
101
        }
102
        if (!$toHandlers) {
103
            $toHandlers = [
104
                array_keys($this->configuredMetadataHandlers)[0],
105
                array_keys($this->configuredBinarydataHandlers)[0],
106
            ];
107
        }
108
109
        if (!$this->validateHandlerOptions($fromHandlers, $toHandlers, $output)) {
110
            return;
111
        }
112
113
        $output->writeln([
114
            "Migrating from '$fromHandlers[0],$fromHandlers[1]' to '$toHandlers[0],$toHandlers[1]'",
115
            '',
116
        ]);
117
        $this->migrationHandler->setIODataHandlersByIdentifiers(
118
            $fromHandlers[0],
119
            $fromHandlers[1],
120
            $toHandlers[0],
121
            $toHandlers[1]
122
        );
123
124
        $totalCount = $this->migrationHandler->countFiles();
125
        $output->writeln([
126
            'Total number of files to update: ' . ($totalCount === null ? 'unknown' : $totalCount),
127
            '',
128
        ]);
129
130
        if ($totalCount === 0) {
131
            $output->writeln('Nothing to process.');
132
133
            return;
134
        }
135
136
        $helper = $this->getHelper('question');
137
        $question = new ConfirmationQuestion(
138
            '<question>Are you sure you want to proceed?</question> ',
139
            false
140
        );
141
142
        if (!$helper->ask($input, $output, $question)) {
143
            $output->writeln('Aborting.');
144
145
            return;
146
        }
147
148
        $this->migrateFiles(
149
            $totalCount,
150
            $bulkCount,
151
            $input->getOption('dry-run'),
152
            $output
153
        );
154
    }
155
156
    /**
157
     * Output the configured meta/binary data handlers.
158
     *
159
     * @param OutputInterface $output
160
     */
161
    protected function outputConfiguredHandlers(OutputInterface $output)
162
    {
163
        $output->writeln(
164
            'Configured meta data handlers: ' . implode(', ', array_keys($this->configuredMetadataHandlers))
165
        );
166
        $output->writeln(
167
            'Configured binary data handlers: ' . implode(', ', array_keys($this->configuredBinarydataHandlers))
168
        );
169
    }
170
171
    /**
172
     * Verify that the handler options have been set to meaningful values.
173
     *
174
     * @param mixed $fromHandlers
175
     * @param mixed $toHandlers
176
     * @param OutputInterface $output
177
     * @return bool
178
     */
179
    protected function validateHandlerOptions(
180
        $fromHandlers,
181
        $toHandlers,
182
        OutputInterface $output
183
    ) {
184
        foreach (['From' => $fromHandlers, 'To' => $toHandlers] as $direction => $handlers) {
185
            $lowerDirection = strtolower($direction);
186
            if (count($handlers) !== 2) {
187
                $output->writeln(
188
                    "Enter two comma separated values for the --$lowerDirection option: " .
189
                    "<{$lowerDirection}_metadata_handler>,<{$lowerDirection}_binarydata_handler>"
190
                );
191
192
                return false;
193
            }
194
195
            foreach (['meta' => $handlers[0], 'binary' => $handlers[1]] as $fileDataType => $handler) {
196
                if (!in_array($handler, array_keys(
197
                    $fileDataType === 'meta' ? $this->configuredMetadataHandlers : $this->configuredBinarydataHandlers
198
                ))) {
199
                    $output->writeln("$direction $fileDataType data handler '$handler' is not configured.");
200
                    $this->outputConfiguredHandlers($output);
201
202
                    return false;
203
                }
204
            }
205
        }
206
207
        if ($fromHandlers === $toHandlers) {
208
            $output->writeln('From and to handlers are the same. Nothing to do.');
209
210
            return false;
211
        }
212
213
        return true;
214
    }
215
216
    /**
217
     * Migrate files.
218
     *
219
     * @param int|null $totalFileCount Total count of files, null if unknown
220
     * @param int $bulkCount Number of files to process in each batch
221
     * @param bool $dryRun
222
     * @param OutputInterface $output
223
     */
224
    protected function migrateFiles(
225
        $totalFileCount = null,
226
        $bulkCount,
227
        $dryRun,
228
        OutputInterface $output
229
    ) {
230
        $progress = new ProgressBar($output, $totalFileCount);
231
        if ($totalFileCount) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $totalFileCount of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
232
            $progress->setFormat("%message%\n %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%");
233
        } else {
234
            $progress->setFormat("%message%\n %current% [%bar%] %elapsed:6s% %memory:6s%");
235
        }
236
237
        $output->writeln('');
238
        $progress->setMessage('Starting migration...');
239
        $progress->start();
240
241
        $pass = 0;
242
        $elapsedFileCount = 0;
243
        $timestamp = microtime(true);
244
        $updateFrequency = 1;
245
246
        do {
247
            $metadataList = $this->migrationHandler->loadMetadataList($bulkCount, $pass * $bulkCount);
248
249
            foreach ($metadataList as $metadata) {
250
                if (!$dryRun) {
251
                    $this->migrationHandler->migrateFile($metadata);
252
                }
253
254
                $progress->setMessage('Updated file ' . $metadata->id);
255
                $progress->advance();
256
                ++$elapsedFileCount;
257
258
                // Magic that ensures the progressbar is updated ca. once per second
259
                if (($elapsedFileCount % $updateFrequency) === 0) {
260
                    $newTimestamp = microtime(true);
261
                    if ($newTimestamp - $timestamp > 0.5 && $updateFrequency > 1) {
262
                        $updateFrequency = (int)($updateFrequency / 2);
263
                        $progress->setRedrawFrequency($updateFrequency);
264
                    } else if ($newTimestamp - $timestamp < 0.1 && $updateFrequency < 10000) {
265
                        $updateFrequency = $updateFrequency * 2;
266
                        $progress->setRedrawFrequency($updateFrequency);
267
                    }
268
                    $timestamp = $newTimestamp;
269
                }
270
            }
271
272
            ++$pass;
273
        } while (count($metadataList) > 0);
274
275
        $progress->setMessage('');
276
        $progress->finish();
277
278
        $output->writeln("\n\nFinished processing $elapsedFileCount files.");
279
    }
280
}
281