2011-01-23 03:33:00 +01:00
|
|
|
<?php
|
|
|
|
|
Move ALL files to serve from the alternate file domain, not just files without
"Content-Disposition: attachment"
Summary:
We currently serve some files off the primary domain (with "Content-Disposition:
attachment" + a CSRF check) and some files off the alternate domain (without
either).
This is not sufficient, because some UAs (like the iPad) ignore
"Content-Disposition: attachment". So there's an attack that goes like this:
- Alice uploads xss.html
- Alice says to Bob "hey download this file on your iPad"
- Bob clicks "Download" on Phabricator on his iPad, gets XSS'd.
NOTE: This removes the CSRF check for downloading files. The check is nice to
have but only raises the barrier to entry slightly. Between iPad / sniffing /
flash bytecode attacks, single-domain installs are simply insecure. We could
restore the check at some point in conjunction with a derived authentication
cookie (i.e., a mini-session-token which is only useful for downloading files),
but that's a lot of complexity to drop all at once.
(Because files are now authenticated only by knowing the PHID and secret key,
this also fixes the "no profile pictures in public feed while logged out"
issue.)
Test Plan: Viewed, info'd, and downloaded files
Reviewers: btrahan, arice, alok
Reviewed By: arice
CC: aran, epriestley
Maniphest Tasks: T843
Differential Revision: https://secure.phabricator.com/D1608
2012-02-14 23:52:27 +01:00
|
|
|
final class PhabricatorFileInfoController extends PhabricatorFileController {
|
2011-01-23 03:33:00 +01:00
|
|
|
|
|
|
|
private $phid;
|
2014-08-20 22:18:21 +02:00
|
|
|
private $id;
|
|
|
|
|
|
|
|
public function shouldAllowPublic() {
|
|
|
|
return true;
|
|
|
|
}
|
2011-01-23 03:33:00 +01:00
|
|
|
|
|
|
|
public function willProcessRequest(array $data) {
|
2014-08-20 22:18:21 +02:00
|
|
|
$this->phid = idx($data, 'phid');
|
|
|
|
$this->id = idx($data, 'id');
|
2011-01-23 03:33:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function processRequest() {
|
Use phabricator_ time functions in more places
Summary:
Replace some more date() calls with locale-aware calls.
Also, at least on my system, the DateTimeZone / DateTime stuff didn't actually
work and always rendered in UTC. Fixed that.
Test Plan:
Viewed daemon console, differential revisions, files, and maniphest timestamps
in multiple timezones.
Reviewed By: toulouse
Reviewers: toulouse, fratrik, jungejason, aran, tuomaspelkonen
CC: aran, toulouse
Differential Revision: 530
2011-06-26 18:22:52 +02:00
|
|
|
$request = $this->getRequest();
|
|
|
|
$user = $request->getUser();
|
|
|
|
|
2014-08-20 22:18:21 +02:00
|
|
|
if ($this->phid) {
|
|
|
|
$file = id(new PhabricatorFileQuery())
|
|
|
|
->setViewer($user)
|
|
|
|
->withPHIDs(array($this->phid))
|
|
|
|
->executeOne();
|
|
|
|
|
|
|
|
if (!$file) {
|
|
|
|
return new Aphront404Response();
|
|
|
|
}
|
|
|
|
return id(new AphrontRedirectResponse())->setURI($file->getInfoURI());
|
|
|
|
}
|
2012-12-17 01:33:24 +01:00
|
|
|
$file = id(new PhabricatorFileQuery())
|
|
|
|
->setViewer($user)
|
2014-08-20 22:18:21 +02:00
|
|
|
->withIDs(array($this->id))
|
2012-12-17 01:33:24 +01:00
|
|
|
->executeOne();
|
2011-01-23 03:33:00 +01:00
|
|
|
if (!$file) {
|
|
|
|
return new Aphront404Response();
|
|
|
|
}
|
2011-02-22 18:22:57 +01:00
|
|
|
|
2012-12-17 01:33:24 +01:00
|
|
|
$phid = $file->getPHID();
|
2013-09-05 22:11:02 +02:00
|
|
|
|
2013-09-17 18:12:37 +02:00
|
|
|
$header = id(new PHUIHeaderView())
|
2013-12-30 20:27:02 +01:00
|
|
|
->setUser($user)
|
|
|
|
->setPolicyObject($file)
|
2012-12-17 01:33:24 +01:00
|
|
|
->setHeader($file->getName());
|
|
|
|
|
2013-02-20 22:33:47 +01:00
|
|
|
$ttl = $file->getTTL();
|
|
|
|
if ($ttl !== null) {
|
2014-01-14 23:09:52 +01:00
|
|
|
$ttl_tag = id(new PHUITagView())
|
2015-03-13 19:30:24 +01:00
|
|
|
->setType(PHUITagView::TYPE_STATE)
|
|
|
|
->setBackgroundColor(PHUITagView::COLOR_YELLOW)
|
2014-06-09 20:36:49 +02:00
|
|
|
->setName(pht('Temporary'));
|
2013-02-20 22:33:47 +01:00
|
|
|
$header->addTag($ttl_tag);
|
|
|
|
}
|
|
|
|
|
2015-03-13 19:30:24 +01:00
|
|
|
$partial = $file->getIsPartial();
|
|
|
|
if ($partial) {
|
|
|
|
$partial_tag = id(new PHUITagView())
|
|
|
|
->setType(PHUITagView::TYPE_STATE)
|
|
|
|
->setBackgroundColor(PHUITagView::COLOR_ORANGE)
|
|
|
|
->setName(pht('Partial Upload'));
|
|
|
|
$header->addTag($partial_tag);
|
|
|
|
}
|
|
|
|
|
2012-12-17 01:33:24 +01:00
|
|
|
$actions = $this->buildActionView($file);
|
2014-12-03 22:16:15 +01:00
|
|
|
$timeline = $this->buildTransactionView($file);
|
2013-04-10 22:08:36 +02:00
|
|
|
$crumbs = $this->buildApplicationCrumbs();
|
2013-12-19 02:47:34 +01:00
|
|
|
$crumbs->addTextCrumb(
|
|
|
|
'F'.$file->getID(),
|
|
|
|
$this->getApplicationURI("/info/{$phid}/"));
|
2013-04-10 22:08:36 +02:00
|
|
|
|
2013-09-29 00:55:38 +02:00
|
|
|
$object_box = id(new PHUIObjectBoxView())
|
2013-10-11 16:53:56 +02:00
|
|
|
->setHeader($header);
|
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
$this->buildPropertyViews($object_box, $file, $actions);
|
2013-09-29 00:55:38 +02:00
|
|
|
|
2012-12-17 01:33:24 +01:00
|
|
|
return $this->buildApplicationPage(
|
|
|
|
array(
|
|
|
|
$crumbs,
|
2013-09-29 00:55:38 +02:00
|
|
|
$object_box,
|
2014-10-07 15:01:04 +02:00
|
|
|
$timeline,
|
2012-12-17 01:33:24 +01:00
|
|
|
),
|
|
|
|
array(
|
|
|
|
'title' => $file->getName(),
|
2013-09-05 22:11:02 +02:00
|
|
|
'pageObjects' => array($file->getPHID()),
|
2012-12-17 01:33:24 +01:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2014-12-03 22:16:15 +01:00
|
|
|
private function buildTransactionView(PhabricatorFile $file) {
|
2013-09-05 22:11:02 +02:00
|
|
|
$user = $this->getRequest()->getUser();
|
|
|
|
|
2014-12-03 22:16:15 +01:00
|
|
|
$timeline = $this->buildTransactionTimeline(
|
|
|
|
$file,
|
|
|
|
new PhabricatorFileTransactionQuery());
|
2013-09-05 22:11:02 +02:00
|
|
|
|
|
|
|
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
|
|
|
|
2013-11-22 01:09:04 +01:00
|
|
|
$add_comment_header = $is_serious
|
|
|
|
? pht('Add Comment')
|
|
|
|
: pht('Question File Integrity');
|
2013-09-05 22:11:02 +02:00
|
|
|
|
|
|
|
$draft = PhabricatorDraft::newFromUserAndKey($user, $file->getPHID());
|
|
|
|
|
|
|
|
$add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
|
|
|
|
->setUser($user)
|
|
|
|
->setObjectPHID($file->getPHID())
|
|
|
|
->setDraft($draft)
|
2013-11-22 01:09:04 +01:00
|
|
|
->setHeaderText($add_comment_header)
|
2013-09-05 22:11:02 +02:00
|
|
|
->setAction($this->getApplicationURI('/comment/'.$file->getID().'/'))
|
2014-04-19 02:51:46 +02:00
|
|
|
->setSubmitButtonName(pht('Add Comment'));
|
2013-09-05 22:11:02 +02:00
|
|
|
|
|
|
|
return array(
|
|
|
|
$timeline,
|
2014-10-07 15:01:04 +02:00
|
|
|
$add_comment_form,
|
|
|
|
);
|
2013-09-05 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
2012-12-17 01:33:24 +01:00
|
|
|
private function buildActionView(PhabricatorFile $file) {
|
|
|
|
$request = $this->getRequest();
|
2014-08-02 23:45:50 +02:00
|
|
|
$viewer = $request->getUser();
|
2011-07-08 06:17:00 +02:00
|
|
|
|
2012-12-17 01:33:24 +01:00
|
|
|
$id = $file->getID();
|
2011-02-22 18:22:57 +01:00
|
|
|
|
2014-08-02 23:45:50 +02:00
|
|
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
|
|
|
$viewer,
|
|
|
|
$file,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT);
|
|
|
|
|
2012-12-17 01:33:24 +01:00
|
|
|
$view = id(new PhabricatorActionListView())
|
2014-08-02 23:45:50 +02:00
|
|
|
->setUser($viewer)
|
2013-07-12 20:39:47 +02:00
|
|
|
->setObjectURI($this->getRequest()->getRequestURI())
|
2012-12-17 01:33:24 +01:00
|
|
|
->setObject($file);
|
2012-01-16 22:26:44 +01:00
|
|
|
|
2015-03-13 19:30:24 +01:00
|
|
|
$can_download = !$file->getIsPartial();
|
|
|
|
|
2011-02-22 18:19:14 +01:00
|
|
|
if ($file->isViewableInBrowser()) {
|
2012-12-17 01:33:24 +01:00
|
|
|
$view->addAction(
|
|
|
|
id(new PhabricatorActionView())
|
|
|
|
->setName(pht('View File'))
|
2014-05-13 16:45:39 +02:00
|
|
|
->setIcon('fa-file-o')
|
2015-03-13 19:30:24 +01:00
|
|
|
->setHref($file->getViewURI())
|
|
|
|
->setDisabled(!$can_download)
|
|
|
|
->setWorkflow(!$can_download));
|
2011-02-22 18:19:14 +01:00
|
|
|
} else {
|
2012-12-17 01:33:24 +01:00
|
|
|
$view->addAction(
|
|
|
|
id(new PhabricatorActionView())
|
2014-08-02 23:45:50 +02:00
|
|
|
->setUser($viewer)
|
2015-03-13 19:30:24 +01:00
|
|
|
->setRenderAsForm($can_download)
|
|
|
|
->setDownload($can_download)
|
2012-12-17 01:33:24 +01:00
|
|
|
->setName(pht('Download File'))
|
2014-05-13 16:45:39 +02:00
|
|
|
->setIcon('fa-download')
|
2015-03-13 19:30:24 +01:00
|
|
|
->setHref($file->getViewURI())
|
|
|
|
->setDisabled(!$can_download)
|
|
|
|
->setWorkflow(!$can_download));
|
2012-01-16 22:26:44 +01:00
|
|
|
}
|
|
|
|
|
2014-08-02 23:45:50 +02:00
|
|
|
$view->addAction(
|
|
|
|
id(new PhabricatorActionView())
|
|
|
|
->setName(pht('Edit File'))
|
|
|
|
->setIcon('fa-pencil')
|
|
|
|
->setHref($this->getApplicationURI("/edit/{$id}/"))
|
|
|
|
->setWorkflow(!$can_edit)
|
|
|
|
->setDisabled(!$can_edit));
|
|
|
|
|
2012-12-17 01:33:24 +01:00
|
|
|
$view->addAction(
|
|
|
|
id(new PhabricatorActionView())
|
|
|
|
->setName(pht('Delete File'))
|
2014-05-13 16:45:39 +02:00
|
|
|
->setIcon('fa-times')
|
2012-12-17 01:33:24 +01:00
|
|
|
->setHref($this->getApplicationURI("/delete/{$id}/"))
|
2014-08-02 23:45:50 +02:00
|
|
|
->setWorkflow(true)
|
|
|
|
->setDisabled(!$can_edit));
|
2012-12-17 01:33:24 +01:00
|
|
|
|
|
|
|
return $view;
|
|
|
|
}
|
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
private function buildPropertyViews(
|
|
|
|
PHUIObjectBoxView $box,
|
2013-10-11 16:53:56 +02:00
|
|
|
PhabricatorFile $file,
|
|
|
|
PhabricatorActionListView $actions) {
|
2012-12-17 01:33:24 +01:00
|
|
|
$request = $this->getRequest();
|
|
|
|
$user = $request->getUser();
|
|
|
|
|
2013-10-11 16:53:56 +02:00
|
|
|
$properties = id(new PHUIPropertyListView());
|
|
|
|
$properties->setActionList($actions);
|
2013-10-19 21:08:06 +02:00
|
|
|
$box->addPropertyList($properties, pht('Details'));
|
2012-12-17 01:33:24 +01:00
|
|
|
|
|
|
|
if ($file->getAuthorPHID()) {
|
2013-10-11 16:53:56 +02:00
|
|
|
$properties->addProperty(
|
2012-12-17 01:33:24 +01:00
|
|
|
pht('Author'),
|
2015-03-30 15:35:32 +02:00
|
|
|
$user->renderHandle($file->getAuthorPHID()));
|
2011-02-22 18:19:14 +01:00
|
|
|
}
|
2011-07-30 01:01:59 +02:00
|
|
|
|
2013-10-11 16:53:56 +02:00
|
|
|
$properties->addProperty(
|
2012-12-17 01:33:24 +01:00
|
|
|
pht('Created'),
|
|
|
|
phabricator_datetime($file->getDateCreated(), $user));
|
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
|
|
|
|
$finfo = id(new PHUIPropertyListView());
|
|
|
|
$box->addPropertyList($finfo, pht('File Info'));
|
|
|
|
|
|
|
|
$finfo->addProperty(
|
2012-12-17 01:33:24 +01:00
|
|
|
pht('Size'),
|
2014-07-13 04:03:17 +02:00
|
|
|
phutil_format_bytes($file->getByteSize()));
|
2012-12-17 01:33:24 +01:00
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
$finfo->addProperty(
|
2012-12-17 01:33:24 +01:00
|
|
|
pht('Mime Type'),
|
2013-01-29 20:01:47 +01:00
|
|
|
$file->getMimeType());
|
2012-12-17 01:33:24 +01:00
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
$width = $file->getImageWidth();
|
|
|
|
if ($width) {
|
|
|
|
$finfo->addProperty(
|
|
|
|
pht('Width'),
|
|
|
|
pht('%s px', new PhutilNumber($width)));
|
|
|
|
}
|
|
|
|
|
|
|
|
$height = $file->getImageHeight();
|
|
|
|
if ($height) {
|
|
|
|
$finfo->addProperty(
|
|
|
|
pht('Height'),
|
|
|
|
pht('%s px', new PhutilNumber($height)));
|
|
|
|
}
|
|
|
|
|
2014-08-14 21:13:26 +02:00
|
|
|
$is_image = $file->isViewableImage();
|
|
|
|
if ($is_image) {
|
|
|
|
$image_string = pht('Yes');
|
|
|
|
$cache_string = $file->getCanCDN() ? pht('Yes') : pht('No');
|
|
|
|
} else {
|
|
|
|
$image_string = pht('No');
|
|
|
|
$cache_string = pht('Not Applicable');
|
|
|
|
}
|
|
|
|
|
|
|
|
$finfo->addProperty(pht('Viewable Image'), $image_string);
|
|
|
|
$finfo->addProperty(pht('Cacheable'), $cache_string);
|
2013-10-19 21:08:06 +02:00
|
|
|
|
2015-03-01 21:12:38 +01:00
|
|
|
$builtin = $file->getBuiltinName();
|
|
|
|
if ($builtin === null) {
|
|
|
|
$builtin_string = pht('No');
|
|
|
|
} else {
|
|
|
|
$builtin_string = $builtin;
|
|
|
|
}
|
|
|
|
|
|
|
|
$finfo->addProperty(pht('Builtin'), $builtin_string);
|
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
$storage_properties = new PHUIPropertyListView();
|
|
|
|
$box->addPropertyList($storage_properties, pht('Storage'));
|
|
|
|
|
|
|
|
$storage_properties->addProperty(
|
2012-12-17 01:33:24 +01:00
|
|
|
pht('Engine'),
|
2013-01-29 20:01:47 +01:00
|
|
|
$file->getStorageEngine());
|
2012-12-17 01:33:24 +01:00
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
$storage_properties->addProperty(
|
2012-12-17 01:33:24 +01:00
|
|
|
pht('Format'),
|
2013-01-29 20:01:47 +01:00
|
|
|
$file->getStorageFormat());
|
2012-12-17 01:33:24 +01:00
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
$storage_properties->addProperty(
|
2012-12-17 01:33:24 +01:00
|
|
|
pht('Handle'),
|
2013-01-29 20:01:47 +01:00
|
|
|
$file->getStorageHandle());
|
2012-12-17 01:33:24 +01:00
|
|
|
|
2013-01-07 18:43:35 +01:00
|
|
|
|
2013-10-01 17:43:34 +02:00
|
|
|
$phids = $file->getObjectPHIDs();
|
|
|
|
if ($phids) {
|
2013-10-11 16:53:56 +02:00
|
|
|
$attached = new PHUIPropertyListView();
|
2013-10-19 21:08:06 +02:00
|
|
|
$box->addPropertyList($attached, pht('Attached'));
|
|
|
|
|
2013-10-11 16:53:56 +02:00
|
|
|
$attached->addProperty(
|
2013-10-01 17:43:34 +02:00
|
|
|
pht('Attached To'),
|
2015-03-30 15:35:32 +02:00
|
|
|
$user->renderHandleList($phids));
|
2013-10-01 17:43:34 +02:00
|
|
|
}
|
|
|
|
|
2012-12-17 01:33:24 +01:00
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
if ($file->isViewableImage()) {
|
2013-01-18 03:39:02 +01:00
|
|
|
$image = phutil_tag(
|
2012-12-17 01:33:24 +01:00
|
|
|
'img',
|
2012-01-16 15:54:08 +01:00
|
|
|
array(
|
2012-12-17 01:33:24 +01:00
|
|
|
'src' => $file->getViewURI(),
|
2013-10-11 16:53:56 +02:00
|
|
|
'class' => 'phui-property-list-image',
|
2012-01-16 15:54:08 +01:00
|
|
|
));
|
|
|
|
|
2013-01-18 09:32:58 +01:00
|
|
|
$linked_image = phutil_tag(
|
2012-12-17 01:33:24 +01:00
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => $file->getViewURI(),
|
|
|
|
),
|
|
|
|
$image);
|
|
|
|
|
2013-10-11 16:53:56 +02:00
|
|
|
$media = id(new PHUIPropertyListView())
|
|
|
|
->addImageContent($linked_image);
|
2013-10-19 21:08:06 +02:00
|
|
|
|
|
|
|
$box->addPropertyList($media);
|
2013-09-27 19:51:25 +02:00
|
|
|
} else if ($file->isAudio()) {
|
|
|
|
$audio = phutil_tag(
|
|
|
|
'audio',
|
|
|
|
array(
|
|
|
|
'controls' => 'controls',
|
2013-10-11 16:53:56 +02:00
|
|
|
'class' => 'phui-property-list-audio',
|
2013-09-27 19:51:25 +02:00
|
|
|
),
|
|
|
|
phutil_tag(
|
|
|
|
'source',
|
|
|
|
array(
|
|
|
|
'src' => $file->getViewURI(),
|
|
|
|
'type' => $file->getMimeType(),
|
|
|
|
)));
|
2013-10-11 16:53:56 +02:00
|
|
|
$media = id(new PHUIPropertyListView())
|
|
|
|
->addImageContent($audio);
|
2011-05-22 23:40:51 +02:00
|
|
|
|
2013-10-19 21:08:06 +02:00
|
|
|
$box->addPropertyList($media);
|
|
|
|
}
|
Add a chunking storage engine for files
Summary:
Ref T7149. This isn't complete and isn't active yet, but does basically work. I'll shore it up in the next few diffs.
The new workflow goes like this:
> Client, file.allocate(): I'd like to upload a file with length L, metadata M, and hash H.
Then the server returns `upload` (a boolean) and `filePHID` (a PHID). These mean:
| upload | filePHID | means |
|---|---|---|
| false | false | Server can't accept file.
| false | true | File data already known, file created from hash.
| true | false | Just upload normally.
| true | true | Query chunks to start or resume a chunked upload.
All but the last case are uninteresting and work like exising uploads with `file.uploadhash` (which we can eventually deprecate).
In the last case:
> Client, file.querychunks(): Give me a list of chunks that I should upload.
This returns all the chunks for the file. Chunks have a start byte, an end byte, and a "complete" flag to indicate that the server already has the data.
Then, the client fills in chunks by sending them:
> Client, file.uploadchunk(): Here is the data for one chunk.
This stuff doesn't work yet or has some caveats:
- I haven't tested resume much.
- Files need an "isPartial()" flag for partial uploads, and the UI needs to respect it.
- The JS client needs to become chunk-aware.
- Chunk size is set crazy low to make testing easier.
- Some debugging flags that I'll remove soon-ish.
- Downloading works, but still streams the whole file into memory.
- This storage engine is disabled by default (hardcoded as a unit test engine) because it's still sketchy.
- Need some code to remove the "isParital" flag when the last chunk is uploaded.
- Maybe do checksumming on chunks.
Test Plan:
- Hacked up `arc upload` (see next diff) to be chunk-aware and uploaded a readme in 18 32-byte chunks. Then downloaded it. Got the same file back that I uploaded.
- File UI now shows some basic chunk info for chunked files:
{F336434}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: joshuaspence, epriestley
Maniphest Tasks: T7149
Differential Revision: https://secure.phabricator.com/D12060
2015-03-13 19:30:02 +01:00
|
|
|
|
|
|
|
$engine = null;
|
|
|
|
try {
|
|
|
|
$engine = $file->instantiateStorageEngine();
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
// Don't bother raising this anywhere for now.
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($engine) {
|
|
|
|
if ($engine->isChunkEngine()) {
|
|
|
|
$chunkinfo = new PHUIPropertyListView();
|
|
|
|
$box->addPropertyList($chunkinfo, pht('Chunks'));
|
|
|
|
|
|
|
|
$chunks = id(new PhabricatorFileChunkQuery())
|
|
|
|
->setViewer($user)
|
|
|
|
->withChunkHandles(array($file->getStorageHandle()))
|
|
|
|
->execute();
|
|
|
|
$chunks = msort($chunks, 'getByteStart');
|
|
|
|
|
|
|
|
$rows = array();
|
|
|
|
$completed = array();
|
|
|
|
foreach ($chunks as $chunk) {
|
|
|
|
$is_complete = $chunk->getDataFilePHID();
|
|
|
|
|
|
|
|
$rows[] = array(
|
|
|
|
$chunk->getByteStart(),
|
|
|
|
$chunk->getByteEnd(),
|
|
|
|
($is_complete ? pht('Yes') : pht('No')),
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($is_complete) {
|
|
|
|
$completed[] = $chunk;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$table = id(new AphrontTableView($rows))
|
|
|
|
->setHeaders(
|
|
|
|
array(
|
|
|
|
pht('Offset'),
|
|
|
|
pht('End'),
|
|
|
|
pht('Complete'),
|
|
|
|
))
|
|
|
|
->setColumnClasses(
|
|
|
|
array(
|
|
|
|
'',
|
|
|
|
'',
|
|
|
|
'wide',
|
|
|
|
));
|
|
|
|
|
|
|
|
$chunkinfo->addProperty(
|
|
|
|
pht('Total Chunks'),
|
|
|
|
count($chunks));
|
|
|
|
|
|
|
|
$chunkinfo->addProperty(
|
|
|
|
pht('Completed Chunks'),
|
|
|
|
count($completed));
|
|
|
|
|
|
|
|
$chunkinfo->addRawContent($table);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-23 03:33:00 +01:00
|
|
|
}
|
2012-12-17 01:33:24 +01:00
|
|
|
|
2011-01-23 03:33:00 +01:00
|
|
|
}
|