Passed
Push — master ( 425a8c...545c74 )
by Andreas
11:48
created

blobdir::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 2
c 1
b 1
f 0
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
1
<?php
2
/**
3
 * @package midcom.console
4
 * @author CONTENT CONTROL http://www.contentcontrol-berlin.de/
5
 * @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/
6
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
7
 */
8
9
namespace midcom\console\command\cleanup;
10
11
use Symfony\Component\Console\Command\Command;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use midcom_db_attachment;
16
use Symfony\Component\Console\Attribute\AsCommand;
17
use midgard\portable\storage\connection;
18
use Doctrine\ORM\AbstractQuery;
19
use midcom_services_auth;
20
21
/**
22
 * Cleanup the blobdir
23
 * Search for corrupted (0 bytes) files
24
 *
25
 * @package midcom.console
26
 */
27
#[AsCommand(
28
    name: 'midcom:cleanup:blobdir',
29
    description: 'Cleanup the blobdir',
30
    aliases: ['blobdircleanup']
31
)]
32
class blobdir extends Command
33
{
34
    private int $_file_counter = 0;
35
36
    private string $_dir = "";
37
38
    private bool $dry = false;
39
40
    private array $findings = [
41
        'corrupted' => [],
42
        'orphaned' => [],
43
        'orphaned_attachments' => []
44
    ];
45
46
    private midcom_services_auth $auth;
47
48
    public function __construct(midcom_services_auth $auth)
49
    {
50
        $this->auth = $auth;
51
        parent::__construct();
52
    }
53
54
    protected function configure()
55
    {
56
        $this->addOption('dry', 'd', InputOption::VALUE_NONE, 'If set, files and attachments will not be deleted');
57
    }
58
59
    public function check_dir(string $outerDir)
60
    {
61
        $outerDir = rtrim($outerDir, "/");
62
        $dirs = array_diff(scandir($outerDir), [".", ".."]);
63
        foreach ($dirs as $d) {
64
            if (is_dir($outerDir . "/" . $d)) {
65
                $this->check_dir($outerDir . "/" . $d);
66
            } else {
67
                // got something
68
                $file = $outerDir . "/" . $d;
69
                if (filesize($file) == 0) {
70
                    $this->findings['corrupted'][] = $file;
71
                } else {
72
                    $attachment = $this->get_attachment($file);
73
                    if (!$attachment) {
74
                        $this->findings['orphaned'][] = $file;
75
                    } elseif (!$this->get_attachment_parent($attachment)) {
76
                        $this->findings['orphaned_attachments'][] = $attachment;
77
                    }
78
                }
79
                $this->_file_counter++;
80
            }
81
        }
82
    }
83
84
    private function _determine_location(string $path) : string
85
    {
86
        return ltrim(str_replace($this->_dir, "", $path), "/");
87
    }
88
89
    private function get_attachment_parent(midcom_db_attachment $attachment) : bool
90
    {
91
        $type = connection::get_em()
92
            ->createQuery('SELECT r.typename from midgard_repligard r WHERE r.guid = ?1')
93
            ->setParameter(1, $attachment->parentguid)
94
            ->getOneOrNullResult(AbstractQuery::HYDRATE_SINGLE_SCALAR);
95
96
        if (!$type) {
97
            return false;
98
        }
99
100
        $dba_type = \midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($type);
101
102
        $qb = \midcom::get()->dbfactory->new_query_builder($dba_type);
0 ignored issues
show
Bug introduced by
It seems like $dba_type can also be of type null; however, parameter $classname of midcom_helper__dbfactory::new_query_builder() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

102
        $qb = \midcom::get()->dbfactory->new_query_builder(/** @scrutinizer ignore-type */ $dba_type);
Loading history...
103
        $qb->include_deleted();
104
        $qb->add_constraint('guid', '=', $attachment->parentguid);
105
        return $qb->count() > 0;
106
    }
107
108
    private function get_attachment(string $file) : ?midcom_db_attachment
109
    {
110
        $location = $this->_determine_location($file);
111
        // get attachments
112
        $qb = midcom_db_attachment::new_query_builder();
113
        $qb->add_constraint("location", "=", $location);
114
        try {
115
            $attachments = $qb->execute();
116
        } catch (\Exception) {
117
            return null;
118
        }
119
        if (empty($attachments)) {
120
            return null;
121
        }
122
        if (count($attachments) > 1) {
123
            throw new \midcom_error('Multiple attachments share location ' . $location);
124
        }
125
126
        return $attachments[0];
127
    }
128
129
    private function cleanup_corrupted(OutputInterface $output, array $files)
130
    {
131
        $i = 0;
132
        foreach ($files as $file) {
133
            $i++;
134
            // cleanup file
135
            $output->writeln($i . ") " . $file);
136
            $this->cleanup_file($output, $file);
137
138
            if ($attachment = $this->get_attachment($file)) {
139
                $this->purge_attachment($output, $attachment);
140
            }
141
        }
142
    }
143
144
    private function purge_attachment(OutputInterface $output, midcom_db_attachment $attachment)
145
    {
146
        if (!$this->dry) {
147
            $stat = $attachment->purge();
148
            if (!$stat || $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
149
                $output->writeln(($stat) ? "<info>Purge OK</info>" : "<comment>Purge FAILED, reason: " . \midcom_connection::get_error_string() . "</comment>");
150
            }
151
        }
152
    }
153
154
    private function cleanup_file(OutputInterface $output, string $file)
155
    {
156
        if (!$this->dry) {
157
            $stat = unlink($file);
158
            if (!$stat || $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
159
                $output->writeln(($stat) ? "<info>Cleanup OK</info>" : "<comment>Cleanup FAILED</comment>");
160
            }
161
        }
162
    }
163
164
    protected function execute(InputInterface $input, OutputInterface $output) : int
165
    {
166
        $dir = \midgard_connection::get_instance()->config->blobdir;
167
        if (!is_dir($dir)) {
168
            $output->writeln("<comment>Unable to detect blobdir</comment>");
169
            return Command::FAILURE;
170
        }
171
        $this->_dir = $dir;
172
        $this->dry = $input->getOption("dry");
173
        if ($this->dry) {
174
            $output->writeln("<comment>Running in dry mode!</comment>");
175
        }
176
177
        $output->writeln("Start scanning dir: <comment>" . $dir . "</comment>");
178
        if (!$this->auth->request_sudo('midcom.core')) {
179
            $output->writeln("<comment>Unable to get sudo</comment>");
180
            return Command::FAILURE;
181
        }
182
183
        $this->check_dir($dir);
184
185
        $output->writeln("Scanned <info>" . $this->_file_counter . "</info> files");
186
        $output->writeln("Found <info>" . count($this->findings['corrupted']) . "</info> corrupted files");
187
        $output->writeln("Found <info>" . count($this->findings['orphaned']) . "</info> orphaned files");
188
        $output->writeln("Found <info>" . count($this->findings['orphaned_attachments']) . "</info> orphaned attachments");
189
190
        $this->cleanup_corrupted($output, $this->findings['corrupted']);
191
192
        foreach ($this->findings['orphaned'] as $file) {
193
            $this->cleanup_file($output, $file);
194
        }
195
196
        foreach ($this->findings['orphaned_attachments'] as $attachment) {
197
            $this->purge_attachment($output, $attachment);
198
        }
199
200
        $this->auth->drop_sudo();
201
        $output->writeln("<comment>Done</comment>");
202
        return Command::SUCCESS;
203
    }
204
}
205