1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Archivr\IndexMerger; |
4
|
|
|
|
5
|
|
|
use Archivr\ConflictHandler\ConflictHandlerInterface; |
6
|
|
|
use Archivr\ConflictHandler\PanickingConflictHandler; |
7
|
|
|
use Archivr\Index; |
8
|
|
|
use Archivr\IndexObject; |
9
|
|
|
|
10
|
|
|
class StandardIndexMerger implements IndexMergerInterface |
11
|
|
|
{ |
12
|
|
|
/** |
13
|
|
|
* @var ConflictHandlerInterface |
14
|
|
|
*/ |
15
|
|
|
protected $conflictHandler; |
16
|
|
|
|
17
|
|
|
public function setConflictHandler(ConflictHandlerInterface $conflictHandler = null): IndexMergerInterface |
18
|
|
|
{ |
19
|
|
|
$this->conflictHandler = $conflictHandler; |
20
|
|
|
|
21
|
|
|
return $this; |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
public function getConflictHandler(): ConflictHandlerInterface |
25
|
|
|
{ |
26
|
|
|
if ($this->conflictHandler === null) |
27
|
|
|
{ |
28
|
|
|
$this->setConflictHandler(new PanickingConflictHandler()); |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
return $this->conflictHandler; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
public function merge(Index $remoteIndex, Index $localIndex, Index $lastLocalIndex = null): Index |
35
|
|
|
{ |
36
|
|
|
$mergedIndex = new Index(); |
37
|
|
|
|
38
|
|
|
if ($lastLocalIndex === null) |
39
|
|
|
{ |
40
|
|
|
// lets make our life easier |
41
|
|
|
$lastLocalIndex = new Index(new \DateTime('@0')); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
foreach ($localIndex as $localObject) |
45
|
|
|
{ |
46
|
|
|
/** @var IndexObject $localObject */ |
47
|
|
|
|
48
|
|
|
$remoteObject = $remoteIndex->getObjectByPath($localObject->getRelativePath()); |
49
|
|
|
$lastLocalObject = $lastLocalIndex->getObjectByPath($localObject->getRelativePath()); |
50
|
|
|
|
51
|
|
|
|
52
|
|
|
// compare existing to known object |
53
|
|
|
if ($lastLocalObject) |
54
|
|
|
{ |
55
|
|
|
$localObjectModified = false; |
56
|
|
|
$localObjectModified = $localObjectModified || ($localObject->getType() !== $lastLocalObject->getType()); |
57
|
|
|
$localObjectModified = $localObjectModified || ($localObject->getMtime() !== $lastLocalObject->getMtime()); |
58
|
|
|
$localObjectModified = $localObjectModified || ($localObject->getCtime() !== $lastLocalObject->getCtime()); |
59
|
|
|
$localObjectModified = $localObjectModified || ($localObject->getMode() !== $lastLocalObject->getMode()); |
60
|
|
|
$localObjectModified = $localObjectModified || ($localObject->getSize() !== $lastLocalObject->getSize()); |
61
|
|
|
$localObjectModified = $localObjectModified || ($localObject->getLinkTarget() !== $lastLocalObject->getLinkTarget()); |
62
|
|
|
|
63
|
|
|
// remote object has been modified if it does not equal the object on its last synchronization |
64
|
|
|
$remoteObjectModified = !$lastLocalObject->equals($remoteObject); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
// object has been created |
68
|
|
|
else |
69
|
|
|
{ |
70
|
|
|
$localObjectModified = true; |
71
|
|
|
|
72
|
|
|
// object has been created since last synchronization |
73
|
|
|
$remoteObjectModified = $remoteObject !== null; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
|
77
|
|
|
// conflict if both the local and the remote object has been changed |
78
|
|
View Code Duplication |
if ($localObjectModified && $remoteObjectModified) |
|
|
|
|
79
|
|
|
{ |
80
|
|
|
$this->conflict($mergedIndex, $remoteObject, $localObject, $lastLocalObject); |
|
|
|
|
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
// add the remote object if only it has been modified |
84
|
|
|
elseif ($remoteObjectModified) |
85
|
|
|
{ |
86
|
|
|
$mergedIndex->addObject($remoteObject); |
|
|
|
|
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
// add the local object otherwise if only it or none has been modified |
90
|
|
|
else |
91
|
|
|
{ |
92
|
|
|
$mergedIndex->addObject($localObject); |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
foreach ($remoteIndex as $remoteObject) |
97
|
|
|
{ |
98
|
|
|
/** @var IndexObject $remoteObject */ |
99
|
|
|
|
100
|
|
|
$localObject = $localIndex->getObjectByPath($remoteObject->getRelativePath()); |
101
|
|
|
$lastLocalObject = $lastLocalIndex->getObjectByPath($remoteObject->getRelativePath()); |
102
|
|
|
|
103
|
|
|
// only consider objects not existing locally as those are already considered by the first loop |
104
|
|
|
if (!$localObject) |
105
|
|
|
{ |
106
|
|
|
if ($lastLocalObject) |
107
|
|
|
{ |
108
|
|
|
// local object has been deleted |
109
|
|
|
$localObjectModified = true; |
110
|
|
|
|
111
|
|
|
// compare remote object to object state at last sync |
112
|
|
|
$remoteObjectModified = !$remoteObject->equals($lastLocalObject); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
else |
116
|
|
|
{ |
117
|
|
|
// object already didn't exist locally at the last sync |
118
|
|
|
$localObjectModified = false; |
119
|
|
|
|
120
|
|
|
// another client added the remote object |
121
|
|
|
$remoteObjectModified = true; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
|
125
|
|
|
// conflict if both the local and the remote object has been changed |
126
|
|
View Code Duplication |
if ($localObjectModified && $remoteObjectModified) |
|
|
|
|
127
|
|
|
{ |
128
|
|
|
$this->conflict($mergedIndex, $remoteObject, $localObject, $lastLocalObject); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
// another client added the remote object |
132
|
|
|
elseif (!$lastLocalObject) |
133
|
|
|
{ |
134
|
|
|
$mergedIndex->addObject($remoteObject); |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
return $mergedIndex; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
protected function conflict(Index $mergedIndex, IndexObject $remoteObject, IndexObject $localObject = null, IndexObject $lastLocalObject = null): void |
143
|
|
|
{ |
144
|
|
|
$solution = $this->getConflictHandler()->handleConflict($remoteObject, $localObject, $lastLocalObject); |
145
|
|
|
|
146
|
|
|
switch ($solution) |
147
|
|
|
{ |
148
|
|
|
case ConflictHandlerInterface::USE_LOCAL: |
|
|
|
|
149
|
|
|
|
150
|
|
|
if ($localObject) |
151
|
|
|
{ |
152
|
|
|
$mergedIndex->addObject($localObject); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
break; |
156
|
|
|
|
157
|
|
|
case ConflictHandlerInterface::USE_REMOTE: |
|
|
|
|
158
|
|
|
|
159
|
|
|
if ($remoteObject) |
160
|
|
|
{ |
161
|
|
|
$mergedIndex->addObject($remoteObject); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
break; |
165
|
|
|
|
166
|
|
|
default: |
|
|
|
|
167
|
|
|
|
168
|
|
|
throw new \LogicException(); |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
} |
172
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.