Completed
Push — master ( 9003e7...f2c4d3 )
by ReliQ
04:59
created

Publisher::publishVersion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
c 0
b 0
f 0
rs 9.8666
cc 2
nc 2
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ReliQArts\Docweaver\Services\Product;
6
7
use ReliQArts\Docweaver\Contracts\Exception;
8
use ReliQArts\Docweaver\Contracts\Filesystem;
9
use ReliQArts\Docweaver\Contracts\Logger;
10
use ReliQArts\Docweaver\Contracts\Product\Publisher as PublisherContract;
11
use ReliQArts\Docweaver\Contracts\VCSCommandRunner;
12
use ReliQArts\Docweaver\Exceptions\Product\InvalidAssetDirectory;
13
use ReliQArts\Docweaver\Exceptions\Product\PublicationFailed;
14
use ReliQArts\Docweaver\Models\Product;
15
use ReliQArts\Docweaver\Services\Publisher as BasePublisher;
16
use ReliQArts\Docweaver\VO\Result;
17
use Symfony\Component\Process\Exception\ProcessFailedException;
18
19
/**
20
 * Publishes and updates product versions.
21
 */
22
final class Publisher extends BasePublisher implements PublisherContract
23
{
24
    /**
25
     * @var VCSCommandRunner
26
     */
27
    private $vcsCommandRunner;
28
29
    /**
30
     * Publisher constructor.
31
     *
32
     * @param Filesystem       $filesystem
33
     * @param Logger           $logger
34
     * @param VCSCommandRunner $vcsCommandRunner
35
     */
36
    public function __construct(Filesystem $filesystem, Logger $logger, VCSCommandRunner $vcsCommandRunner)
37
    {
38
        parent::__construct($filesystem, $logger);
39
40
        $this->vcsCommandRunner = $vcsCommandRunner;
41
    }
42
43
    /**
44
     * @param Product $product
45
     * @param string  $source
46
     *
47
     * @throws Exception
48
     *
49
     * @return Result
50
     */
51
    public function publish(Product $product, string $source): Result
52
    {
53
        $result = new Result();
54
        $versions = [Product::VERSION_MASTER];
55
        $versionsPublished = [];
56
        $versionsUpdated = [];
57
        $productDirectory = $product->getDirectory();
58
        $masterDirectory = sprintf('%s/master', $productDirectory);
59
60
        $this->setExecutionStartTime();
61
62
        if (!$this->readyResourceDirectory($productDirectory)) {
63
            return $result->setError(sprintf('Product directory %s is not writable.', $productDirectory))
64
                ->setData((object)['executionTime' => $this->getExecutionTime()]);
65
        }
66
67
        if (!$this->filesystem->isDirectory($masterDirectory)) {
68
            $this->publishVersion($product, $source, Product::VERSION_MASTER);
69
            $versionsPublished[] = Product::VERSION_MASTER;
70
        } else {
71
            $this->updateVersion($product, Product::VERSION_MASTER);
72
            $versionsUpdated[] = Product::VERSION_MASTER;
73
        }
74
75
        $tagResult = $this->publishTags($product, $source);
76
        $tagData = $tagResult->getData();
77
        $versions = array_merge($versions, $tagData->tags ?? []);
78
        $versionsPublished = array_merge($versionsPublished, $tagData->tagsPublished ?? []);
79
80
        if ($result->isSuccess()) {
81
            $result = $result->setMessage(sprintf('%s was successfully published.', $product->getName()))
82
                ->setData((object)[
83
                    'versions' => $versions,
84
                    'versionsPublished' => $versionsPublished,
85
                    'versionsUpdated' => $versionsUpdated,
86
                    'executionTime' => $this->getExecutionTime(),
87
                ]);
88
        }
89
90
        return $result;
91
    }
92
93
    /**
94
     * @param Product $product
95
     *
96
     * @return Result
97
     */
98
    public function update(Product $product): Result
99
    {
100
        $result = new Result();
101
        $versions = $product->getVersions();
102
        $versionsUpdated = [];
103
104
        foreach (array_keys($versions) as $version) {
105
            try {
106
                $this->updateVersion($product, $version);
107
                $versionsUpdated[] = $version;
108
            } catch (Exception $exception) {
109
                // expected when version is a tag
110
                // TODO: enhancement; determine whether version is tag in advance
111
                $this->logger->info($exception->getMessage());
112
            }
113
        }
114
115
        if ($result->isSuccess()) {
116
            $result = $result->setMessage(sprintf('%s was successfully updated.', $product->getName()))
117
                ->setData((object)[
118
                    'versions' => $versions,
119
                    'versionsUpdated' => $versionsUpdated,
120
                ]);
121
        }
122
123
        return $result;
124
    }
125
126
    /**
127
     * @param Product $product
128
     * @param string  $source
129
     * @param string  $version
130
     *
131
     * @throws Exception
132
     *
133
     * @return bool
134
     */
135
    private function publishVersion(Product $product, string $source, string $version): bool
136
    {
137
        try {
138
            $this->vcsCommandRunner->clone($source, $version, $product->getDirectory());
139
        } catch (ProcessFailedException $e) {
140
            throw PublicationFailed::forProductVersion($product, $version);
141
        }
142
143
        $this->publishProductAssets($product, $version);
144
145
        return true;
146
    }
147
148
    /**
149
     * @param Product $product
150
     * @param string  $version
151
     *
152
     * @throws Exception
153
     *
154
     * @return bool
155
     */
156
    private function updateVersion(Product $product, string $version): bool
157
    {
158
        try {
159
            $this->vcsCommandRunner->pull(sprintf('%s/%s', $product->getDirectory(), $version));
160
        } catch (ProcessFailedException $e) {
161
            throw new PublicationFailed(
162
                sprintf(
163
                    'Failed to update version `%s` of product `%s`. It may be a tag.',
164
                    $version,
165
                    $product->getName()
166
                )
167
            );
168
        }
169
170
        $this->publishProductAssets($product, $version);
171
172
        return true;
173
    }
174
175
    /**
176
     * @param Product $product
177
     * @param string  $source
178
     *
179
     * @return Result
180
     */
181
    private function publishTags(Product $product, string $source): Result
182
    {
183
        $result = new Result();
184
        $masterDirectory = sprintf('%s/%s', $product->getDirectory(), Product::VERSION_MASTER);
185
        $tagsPublished = [];
186
187
        try {
188
            $tags = $this->vcsCommandRunner->getTags($masterDirectory);
189
190
            foreach ($tags as $tag) {
191
                $tagDirectory = sprintf('%s/%s', $product->getDirectory(), $tag);
192
193
                if (!$this->filesystem->isDirectory($tagDirectory)) {
194
                    $this->publishVersion($product, $source, $tag);
195
                    $result = $result->addMessage(sprintf('Successfully published tag `%s`.', $tag));
196
                    $tagsPublished[] = $tag;
197
                } else {
198
                    $message = sprintf('Version `%s` already exists.', $tag);
199
                    $result = $result->addMessage($message);
200
                }
201
            }
202
        } catch (Exception $e) {
203
            $errorMessage = $e->getMessage();
204
205
            return $result->setMessage($errorMessage)->setError($errorMessage);
206
        }
207
208
        return $result->setData((object)[
209
            'tags' => $tags,
210
            'tagsPublished' => $tagsPublished,
211
        ]);
212
    }
213
214
    /**
215
     * @param Product $product
216
     * @param string  $version
217
     */
218
    private function publishProductAssets(Product $product, string $version): void
219
    {
220
        try {
221
            $product->publishAssets($version);
222
        } catch (Exception $exception) {
223
            if ($exception instanceof InvalidAssetDirectory) {
224
                $this->logger->info($exception->getMessage());
225
            } else {
226
                $this->logger->error($exception->getMessage());
227
            }
228
        }
229
    }
230
}
231