Completed
Push — master ( 77f170...166d1e )
by Arne
03:02
created

StandardOperationCollectionBuilder   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 11

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 28
lcom 0
cbo 11
dl 0
loc 144
rs 10
c 1
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
F buildOperationCollection() 0 141 28
1
<?php
2
3
namespace Archivr\OperationCollectionBuilder;
4
5
use Archivr\Exception\Exception;
6
use Archivr\Index;
7
use Archivr\IndexObject;
8
use Archivr\Operation\ChmodOperation;
9
use Archivr\Operation\DownloadOperation;
10
use Archivr\Operation\MkdirOperation;
11
use Archivr\Operation\SymlinkOperation;
12
use Archivr\Operation\TouchOperation;
13
use Archivr\Operation\UnlinkOperation;
14
use Archivr\Operation\UploadOperation;
15
use Archivr\OperationCollection;
16
17
class StandardOperationCollectionBuilder implements OperationCollectionBuilderInterface
18
{
19
    public function buildOperationCollection(Index $mergedIndex, Index $localIndex, Index $remoteIndex = null): OperationCollection
20
    {
21
        $uploadStreamFilters = [
22
            'zlib.deflate' => []
23
        ];
24
        $downloadStreamFilters = [
25
            'zlib.inflate' => []
26
        ];
27
28
29
        $operationCollection = new OperationCollection();
30
31
        // mtimes to be set for directories are collected and applied afterwards as they get modified by synchronization operations as well
32
        $directoryMtimes = [];
33
34
        // set of modified paths that can be populated and is later used to add parent directory touch()es
35
        $modifiedPaths = [];
36
37
        // relies on the directory tree structure being traversed in pre-order (or at least a directory appears before its content)
38
        foreach ($mergedIndex as $mergedIndexObject)
39
        {
40
            /** @var IndexObject $mergedIndexObject */
41
42
            $localObject = $localIndex->getObjectByPath($mergedIndexObject->getRelativePath());
43
            $remoteObject = $remoteIndex ? $remoteIndex->getObjectByPath($mergedIndexObject->getRelativePath()) : null;
44
45
            // unlink to-be-overridden local path with different type
46
            if ($localObject !== null && $localObject->getType() !== $mergedIndexObject->getType())
47
            {
48
                $operationCollection->addOperation(new UnlinkOperation($mergedIndexObject->getRelativePath()));
49
50
                $modifiedPaths[] = $mergedIndexObject->getRelativePath();
51
            }
52
53
54
            if ($mergedIndexObject->isDirectory())
55
            {
56
                if ($localObject === null || !$localObject->isDirectory())
57
                {
58
                    $operationCollection->addOperation(new MkdirOperation($mergedIndexObject->getRelativePath(), $mergedIndexObject->getMode()));
59
60
                    $directoryMtimes[$mergedIndexObject->getRelativePath()] = $mergedIndexObject->getMtime();
61
                }
62
63
                if ($localObject !== null && $localObject->isDirectory())
64
                {
65
                    if ($localObject->getMtime() !== $mergedIndexObject->getMtime())
66
                    {
67
                        // fix wrong mtime
68
                        $directoryMtimes[$mergedIndexObject->getRelativePath()] = $mergedIndexObject->getMtime();
69
                    }
70
                }
71
            }
72
73
            elseif ($mergedIndexObject->isFile())
74
            {
75
                // local file did not exist, hasn't been a file before or is outdated
76
                $doDownloadFile = $localObject === null || !$localObject->isFile() || $localObject->getMtime() < $mergedIndexObject->getMtime();
77
78
                // file has to be restored as it does not equal the local version
79
                $doDownloadFile |= $localObject !== null && $mergedIndexObject->getBlobId() !== $localObject->getBlobId();
80
81
                if ($doDownloadFile)
82
                {
83
                    $operationCollection->addOperation(new DownloadOperation($mergedIndexObject->getRelativePath(), $mergedIndexObject->getBlobId(), $downloadStreamFilters));
84
                    $operationCollection->addOperation(new TouchOperation($mergedIndexObject->getRelativePath(), $mergedIndexObject->getMtime()));
85
                    $operationCollection->addOperation(new ChmodOperation($mergedIndexObject->getRelativePath(), $mergedIndexObject->getMode()));
86
87
                    $modifiedPaths[] = $mergedIndexObject->getRelativePath();
88
                }
89
90
                // local file got created or updated
91
                elseif ($remoteObject === null || $mergedIndexObject->getBlobId() === null)
92
                {
93
                    // generate blob id
94
                    // todo: we might want to have some mechanism to prevent overriding existing file in case of collision
95
                    $newBlobId = $mergedIndex->generateNewBlobId();
96
97
                    $mergedIndexObject->setBlobId($newBlobId);
98
99
                    $operationCollection->addOperation(new UploadOperation($mergedIndexObject->getRelativePath(), $mergedIndexObject->getBlobId(), $uploadStreamFilters));
100
                }
101
            }
102
103
            elseif ($mergedIndexObject->isLink())
104
            {
105
                if ($localObject !== null && $localObject->getLinkTarget() !== $mergedIndexObject->getLinkTarget())
106
                {
107
                    $operationCollection->addOperation(new UnlinkOperation($mergedIndexObject->getRelativePath()));
108
                    $operationCollection->addOperation(new SymlinkOperation($mergedIndexObject->getRelativePath(), $mergedIndexObject->getLinkTarget(), $mergedIndexObject->getMode()));
109
110
                    $modifiedPaths[] = $mergedIndexObject->getRelativePath();
111
                }
112
            }
113
114
            else
115
            {
116
                // unknown/invalid object type
117
                throw new Exception();
118
            }
119
120
121
            if ($localObject !== null && $localObject->getMode() !== $mergedIndexObject->getMode())
122
            {
123
                $operationCollection->addOperation(new ChmodOperation($mergedIndexObject->getRelativePath(), $mergedIndexObject->getMode()));
124
            }
125
        }
126
127
        // remove superfluous local files
128
        foreach ($localIndex as $localObject)
129
        {
130
            /** @var IndexObject $localObject */
131
132
            if ($mergedIndex->getObjectByPath($localObject->getRelativePath()) === null)
133
            {
134
                $operationCollection->addOperation(new UnlinkOperation($localObject->getRelativePath()));
135
136
                $modifiedPaths[] = $localObject->getRelativePath();
137
            }
138
        }
139
140
        // add modified paths to directory mtimes to be set
141
        foreach ($modifiedPaths as $modifiedPath)
142
        {
143
            if (($dir = dirname($modifiedPath)) !== '.')
144
            {
145
                $dirObject = $mergedIndex->getObjectByPath($dir);
146
147
                $directoryMtimes[$dirObject->getRelativePath()] = $dirObject->getMtime();
148
            }
149
        }
150
151
        // set directory mtimes after all other modifications have been performed
152
        krsort($directoryMtimes);
153
        foreach ($directoryMtimes as $relativePath => $mtime)
154
        {
155
            $operationCollection->addOperation(new TouchOperation($relativePath, $mtime));
156
        }
157
158
        return $operationCollection;
159
    }
160
}
161