Skip to content

Commit 25e7b7d

Browse files
committedDec 7, 2013
Implement support for creating and updating fragments from ZIPs
Summary: This implements support for creating and updating fragments from ZIP files. It allows you to upload a ZIP via the Files application, create a fragment from it, and have it recursively imported into Phragment. Updating that folder with another ZIP will recursively create, update and delete files as appropriate. The logic for creating and updating fragments from files has also been centralized into the PhragmentFragment class. Directories are also now supported; a directory fragment is simply a fragment that has no patches; thus a directory fragment can be converted to a file fragment by uploading a first patch for it. Test Plan: Uploaded ZIP files through the interface and saw all of the fragments get created and updated as expected. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley CC: Korvin, epriestley, aran Maniphest Tasks: T4205 Differential Revision: https://secure.phabricator.com/D7729
1 parent ccd4ae5 commit 25e7b7d

File tree

8 files changed

+270
-55
lines changed

8 files changed

+270
-55
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE {$NAMESPACE}_phragment.phragment_fragment
2+
MODIFY latestVersionPHID VARCHAR(64) NULL;

‎src/applications/phragment/controller/PhragmentBrowseController.php

+12-8
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,18 @@ public function processRequest() {
5757
$item = id(new PHUIObjectItemView());
5858
$item->setHeader($fragment->getName());
5959
$item->setHref($this->getApplicationURI('/browse/'.$fragment->getPath()));
60-
$item->addAttribute(pht(
61-
'Last Updated %s',
62-
phabricator_datetime(
63-
$fragment->getLatestVersion()->getDateCreated(),
64-
$viewer)));
65-
$item->addAttribute(pht(
66-
'Latest Version %s',
67-
$fragment->getLatestVersion()->getSequence()));
60+
if (!$fragment->isDirectory()) {
61+
$item->addAttribute(pht(
62+
'Last Updated %s',
63+
phabricator_datetime(
64+
$fragment->getLatestVersion()->getDateCreated(),
65+
$viewer)));
66+
$item->addAttribute(pht(
67+
'Latest Version %s',
68+
$fragment->getLatestVersion()->getSequence()));
69+
} else {
70+
$item->addAttribute('Directory');
71+
}
6872
$list->addItem($item);
6973
}
7074

‎src/applications/phragment/controller/PhragmentController.php

+36-15
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,16 @@ protected function createCurrentFragmentView($fragment, $is_history_view) {
6666

6767
$this->loadHandles($phids);
6868

69-
$file = id(new PhabricatorFileQuery())
70-
->setViewer($viewer)
71-
->withPHIDs(array($fragment->getLatestVersion()->getFilePHID()))
72-
->executeOne();
69+
$file = null;
7370
$file_uri = null;
74-
if ($file !== null) {
75-
$file_uri = $file->getBestURI();
71+
if (!$fragment->isDirectory()) {
72+
$file = id(new PhabricatorFileQuery())
73+
->setViewer($viewer)
74+
->withPHIDs(array($fragment->getLatestVersion()->getFilePHID()))
75+
->executeOne();
76+
if ($file !== null) {
77+
$file_uri = $file->getBestURI();
78+
}
7679
}
7780

7881
$header = id(new PHUIHeaderView())
@@ -96,12 +99,21 @@ protected function createCurrentFragmentView($fragment, $is_history_view) {
9699
->setHref($this->getApplicationURI("zip/".$fragment->getPath()))
97100
->setDisabled(false) // TODO: Policy
98101
->setIcon('zip'));
99-
$actions->addAction(
100-
id(new PhabricatorActionView())
101-
->setName(pht('Update Fragment'))
102-
->setHref($this->getApplicationURI("update/".$fragment->getPath()))
103-
->setDisabled(false) // TODO: Policy
104-
->setIcon('edit'));
102+
if (!$fragment->isDirectory()) {
103+
$actions->addAction(
104+
id(new PhabricatorActionView())
105+
->setName(pht('Update Fragment'))
106+
->setHref($this->getApplicationURI("update/".$fragment->getPath()))
107+
->setDisabled(false) // TODO: Policy
108+
->setIcon('edit'));
109+
} else {
110+
$actions->addAction(
111+
id(new PhabricatorActionView())
112+
->setName(pht('Convert to File'))
113+
->setHref($this->getApplicationURI("update/".$fragment->getPath()))
114+
->setDisabled(false) // TODO: Policy
115+
->setIcon('edit'));
116+
}
105117
if ($is_history_view) {
106118
$actions->addAction(
107119
id(new PhabricatorActionView())
@@ -121,9 +133,18 @@ protected function createCurrentFragmentView($fragment, $is_history_view) {
121133
->setObject($fragment)
122134
->setActionList($actions);
123135

124-
$properties->addProperty(
125-
pht('Latest Version'),
126-
$this->renderHandlesForPHIDs(array($fragment->getLatestVersionPHID())));
136+
if (!$fragment->isDirectory()) {
137+
$properties->addProperty(
138+
pht('Type'),
139+
pht('File'));
140+
$properties->addProperty(
141+
pht('Latest Version'),
142+
$this->renderHandlesForPHIDs(array($fragment->getLatestVersionPHID())));
143+
} else {
144+
$properties->addProperty(
145+
pht('Type'),
146+
pht('Directory'));
147+
}
127148

128149
return id(new PHUIObjectBoxView())
129150
->setHeader($header)

‎src/applications/phragment/controller/PhragmentCreateController.php

+6-15
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,12 @@ public function processRequest() {
5454
$depth = $parent->getDepth() + 1;
5555
}
5656

57-
$version = id(new PhragmentFragmentVersion());
58-
$version->setSequence(0);
59-
$version->setFragmentPHID(''); // Can't set this yet...
60-
$version->setFilePHID($file->getPHID());
61-
$version->save();
62-
63-
$fragment->setPath(trim($parent_path.'/'.$v_name, '/'));
64-
$fragment->setDepth($depth);
65-
$fragment->setLatestVersionPHID($version->getPHID());
66-
$fragment->setViewPolicy($v_viewpolicy);
67-
$fragment->setEditPolicy($v_editpolicy);
68-
$fragment->save();
69-
70-
$version->setFragmentPHID($fragment->getPHID());
71-
$version->save();
57+
PhragmentFragment::createFromFile(
58+
$viewer,
59+
$file,
60+
trim($parent_path.'/'.$v_name, '/'),
61+
$v_viewpolicy,
62+
$v_editpolicy);
7263

7364
return id(new AphrontRedirectResponse())
7465
->setURI('/phragment/browse/'.trim($parent_path.'/'.$v_name, '/'));

‎src/applications/phragment/controller/PhragmentUpdateController.php

+8-16
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,14 @@ public function processRequest() {
3131
}
3232

3333
if (!count($errors)) {
34-
$existing = id(new PhragmentFragmentVersionQuery())
35-
->setViewer($viewer)
36-
->withFragmentPHIDs(array($fragment->getPHID()))
37-
->execute();
38-
$sequence = count($existing);
39-
40-
$fragment->openTransaction();
41-
$version = id(new PhragmentFragmentVersion());
42-
$version->setSequence($sequence);
43-
$version->setFragmentPHID($fragment->getPHID());
44-
$version->setFilePHID($file->getPHID());
45-
$version->save();
46-
47-
$fragment->setLatestVersionPHID($version->getPHID());
48-
$fragment->save();
49-
$fragment->saveTransaction();
34+
// If the file is a ZIP archive (has application/zip mimetype)
35+
// then we extract the zip and apply versions for each of the
36+
// individual fragments, creating and deleting files as needed.
37+
if ($file->getMimeType() === "application/zip") {
38+
$fragment->updateFromZIP($viewer, $file);
39+
} else {
40+
$fragment->updateFromFile($viewer, $file);
41+
}
5042

5143
return id(new AphrontRedirectResponse())
5244
->setURI('/phragment/browse/'.$fragment->getPath());

‎src/applications/phragment/query/PhragmentFragmentQuery.php

-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ protected function didFilterPage(array $page) {
115115
foreach ($page as $key => $fragment) {
116116
$version_phid = $fragment->getLatestVersionPHID();
117117
if (empty($versions[$version_phid])) {
118-
unset($page[$key]);
119118
continue;
120119
}
121120
$fragment->attachLatestVersion($versions[$version_phid]);

‎src/applications/phragment/storage/PhragmentFragment.php

+202
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,216 @@ public function attachFile(PhabricatorFile $file) {
3838
return $this->file = $file;
3939
}
4040

41+
public function isDirectory() {
42+
return $this->latestVersionPHID === null;
43+
}
44+
4145
public function getLatestVersion() {
46+
if ($this->latestVersionPHID === null) {
47+
return null;
48+
}
4249
return $this->assertAttached($this->latestVersion);
4350
}
4451

4552
public function attachLatestVersion(PhragmentFragmentVersion $version) {
4653
return $this->latestVersion = $version;
4754
}
4855

56+
57+
/* -( Updating ) --------------------------------------------------------- */
58+
59+
60+
/**
61+
* Create a new fragment from a file.
62+
*/
63+
public static function createFromFile(
64+
PhabricatorUser $viewer,
65+
PhabricatorFile $file = null,
66+
$path,
67+
$view_policy,
68+
$edit_policy) {
69+
70+
$fragment = id(new PhragmentFragment());
71+
$fragment->setPath($path);
72+
$fragment->setDepth(count(explode('/', $path)));
73+
$fragment->setLatestVersionPHID(null);
74+
$fragment->setViewPolicy($view_policy);
75+
$fragment->setEditPolicy($edit_policy);
76+
$fragment->save();
77+
78+
// Directory fragments have no versions associated with them, so we
79+
// just return the fragment at this point.
80+
if ($file === null) {
81+
return $fragment;
82+
}
83+
84+
if ($file->getMimeType() === "application/zip") {
85+
$fragment->updateFromZIP($viewer, $file);
86+
} else {
87+
$fragment->updateFromFile($viewer, $file);
88+
}
89+
90+
return $fragment;
91+
}
92+
93+
94+
/**
95+
* Set the specified file as the next version for the fragment.
96+
*/
97+
public function updateFromFile(
98+
PhabricatorUser $viewer,
99+
PhabricatorFile $file) {
100+
101+
$existing = id(new PhragmentFragmentVersionQuery())
102+
->setViewer($viewer)
103+
->withFragmentPHIDs(array($this->getPHID()))
104+
->execute();
105+
$sequence = count($existing);
106+
107+
$this->openTransaction();
108+
$version = id(new PhragmentFragmentVersion());
109+
$version->setSequence($sequence);
110+
$version->setFragmentPHID($this->getPHID());
111+
$version->setFilePHID($file->getPHID());
112+
$version->save();
113+
114+
$this->setLatestVersionPHID($version->getPHID());
115+
$this->save();
116+
$this->saveTransaction();
117+
}
118+
119+
/**
120+
* Apply the specified ZIP archive onto the fragment, removing
121+
* and creating fragments as needed.
122+
*/
123+
public function updateFromZIP(
124+
PhabricatorUser $viewer,
125+
PhabricatorFile $file) {
126+
127+
if ($file->getMimeType() !== "application/zip") {
128+
throw new Exception("File must have mimetype 'application/zip'");
129+
}
130+
131+
// First apply the ZIP as normal.
132+
$this->updateFromFile($viewer, $file);
133+
134+
// Ensure we have ZIP support.
135+
$zip = null;
136+
try {
137+
$zip = new ZipArchive();
138+
} catch (Exception $e) {
139+
// The server doesn't have php5-zip, so we can't do recursive updates.
140+
return;
141+
}
142+
143+
$temp = new TempFile();
144+
Filesystem::writeFile($temp, $file->loadFileData());
145+
if (!$zip->open($temp)) {
146+
throw new Exception("Unable to open ZIP");
147+
}
148+
149+
// Get all of the paths and their data from the ZIP.
150+
$mappings = array();
151+
for ($i = 0; $i < $zip->numFiles; $i++) {
152+
$path = trim($zip->getNameIndex($i), '/');
153+
$stream = $zip->getStream($path);
154+
$data = null;
155+
// If the stream is false, then it is a directory entry. We leave
156+
// $data set to null for directories so we know not to create a
157+
// version entry for them.
158+
if ($stream !== false) {
159+
$data = stream_get_contents($stream);
160+
fclose($stream);
161+
}
162+
$mappings[$path] = $data;
163+
}
164+
165+
// Adjust the paths relative to this fragment so we can look existing
166+
// fragments up in the DB.
167+
$base_path = $this->getPath();
168+
$paths = array();
169+
foreach ($mappings as $p => $data) {
170+
$paths[] = $base_path.'/'.$p;
171+
}
172+
173+
// FIXME: What happens when a child exists, but the current user
174+
// can't see it. We're going to create a new child with the exact
175+
// same path and then bad things will happen.
176+
$children = id(new PhragmentFragmentQuery())
177+
->setViewer($viewer)
178+
->needLatestVersion(true)
179+
->withPaths($paths)
180+
->execute();
181+
$children = mpull($children, null, 'getPath');
182+
183+
// Iterate over the existing fragments.
184+
foreach ($children as $full_path => $child) {
185+
$path = substr($full_path, strlen($base_path) + 1);
186+
if (array_key_exists($path, $mappings)) {
187+
if ($child->isDirectory() && $mappings[$path] === null) {
188+
// Don't create a version entry for a directory
189+
// (unless it's been converted into a file).
190+
continue;
191+
}
192+
193+
// The file is being updated.
194+
$file = PhabricatorFile::newFromFileData(
195+
$mappings[$path],
196+
array('name' => basename($path)));
197+
$child->updateFromFile($viewer, $file);
198+
} else {
199+
// The file is being deleted.
200+
$child->deleteFile($viewer);
201+
}
202+
}
203+
204+
// Iterate over the mappings to find new files.
205+
foreach ($mappings as $path => $data) {
206+
if (!array_key_exists($base_path.'/'.$path, $children)) {
207+
// The file is being created. If the data is null,
208+
// then this is explicitly a directory being created.
209+
$file = null;
210+
if ($mappings[$path] !== null) {
211+
$file = PhabricatorFile::newFromFileData(
212+
$mappings[$path],
213+
array('name' => basename($path)));
214+
}
215+
PhragmentFragment::createFromFile(
216+
$viewer,
217+
$file,
218+
$base_path.'/'.$path,
219+
$this->getViewPolicy(),
220+
$this->getEditPolicy());
221+
}
222+
}
223+
}
224+
225+
/**
226+
* Delete the contents of the specified fragment.
227+
*/
228+
public function deleteFile(PhabricatorUser $viewer) {
229+
$existing = id(new PhragmentFragmentVersionQuery())
230+
->setViewer($viewer)
231+
->withFragmentPHIDs(array($this->getPHID()))
232+
->execute();
233+
$sequence = count($existing);
234+
235+
$this->openTransaction();
236+
$version = id(new PhragmentFragmentVersion());
237+
$version->setSequence($sequence);
238+
$version->setFragmentPHID($this->getPHID());
239+
$version->setFilePHID(null);
240+
$version->save();
241+
242+
$this->setLatestVersionPHID($version->getPHID());
243+
$this->save();
244+
$this->saveTransaction();
245+
}
246+
247+
248+
/* -( Policy Interface )--------------------------------------------------- */
249+
250+
49251
public function getCapabilities() {
50252
return array(
51253
PhabricatorPolicyCapability::CAN_VIEW,

‎src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php

+4
Original file line numberDiff line numberDiff line change
@@ -1820,6 +1820,10 @@ public function getPatches() {
18201820
'type' => 'sql',
18211821
'name' => $this->getPatchPath('20131206.phragment.sql'),
18221822
),
1823+
'20131206.phragmentnull.sql' => array(
1824+
'type' => 'sql',
1825+
'name' => $this->getPatchPath('20131206.phragmentnull.sql'),
1826+
),
18231827
);
18241828
}
18251829
}

0 commit comments

Comments
 (0)
Failed to load comments.