diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'ed3d6355', + 'core.pkg.css' => '7ac320f1', 'core.pkg.js' => 'ac41c400', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', @@ -33,7 +33,7 @@ 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', 'rsrc/css/aphront/typeahead.css' => '0e403212', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', - 'rsrc/css/application/auth/auth.css' => '1e655982', + 'rsrc/css/application/auth/auth.css' => '44975d4b', 'rsrc/css/application/base/main-menu-view.css' => '663e3810', 'rsrc/css/application/base/notification-menu.css' => '3c9d8aa1', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '16ca323f', @@ -47,8 +47,8 @@ 'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2', 'rsrc/css/application/conpherence/durable-column.css' => '8c43d6ac', 'rsrc/css/application/conpherence/menu.css' => 'f389e048', - 'rsrc/css/application/conpherence/message-pane.css' => '0e75feef', - 'rsrc/css/application/conpherence/notification.css' => 'd208f806', + 'rsrc/css/application/conpherence/message-pane.css' => '5bb4b76d', + 'rsrc/css/application/conpherence/notification.css' => '919974b6', 'rsrc/css/application/conpherence/transaction.css' => '42a457f6', 'rsrc/css/application/conpherence/update.css' => '1099a660', 'rsrc/css/application/conpherence/widget-pane.css' => '2af42ebe', @@ -135,14 +135,14 @@ 'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27', 'rsrc/css/phui/phui-form-view.css' => '94ae3032', 'rsrc/css/phui/phui-form.css' => 'f535f938', - 'rsrc/css/phui/phui-header-view.css' => 'da4586b1', + 'rsrc/css/phui/phui-header-view.css' => '75aaf372', 'rsrc/css/phui/phui-icon.css' => 'bc766998', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => 'c6f0aef8', 'rsrc/css/phui/phui-list.css' => '2e25ebfb', 'rsrc/css/phui/phui-object-box.css' => '7d160002', - 'rsrc/css/phui/phui-object-item-list-view.css' => '9db65899', + 'rsrc/css/phui/phui-object-item-list-view.css' => 'f3a22696', 'rsrc/css/phui/phui-pinboard-view.css' => 'eaab2b1b', 'rsrc/css/phui/phui-property-list-view.css' => '5b671934', 'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b', @@ -150,7 +150,7 @@ 'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-tag-view.css' => '402691cc', 'rsrc/css/phui/phui-text.css' => 'cf019f54', - 'rsrc/css/phui/phui-timeline-view.css' => 'b0fbc4d7', + 'rsrc/css/phui/phui-timeline-view.css' => 'a85542c8', 'rsrc/css/phui/phui-workboard-view.css' => '3279cbbf', 'rsrc/css/phui/phui-workpanel-view.css' => 'e495a5cc', 'rsrc/css/sprite-gradient.css' => '4bdb98a7', @@ -281,22 +281,6 @@ 'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264', 'rsrc/image/icon/fatcow/source/tablet.png' => '49396799', 'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d', - 'rsrc/image/icon/fatcow/thumbnails/default.p100.png' => '7d490b01', - 'rsrc/image/icon/fatcow/thumbnails/default160x120.png' => 'f2e8a2eb', - 'rsrc/image/icon/fatcow/thumbnails/default280x210.png' => '43e8926a', - 'rsrc/image/icon/fatcow/thumbnails/default60x45.png' => '0118abed', - 'rsrc/image/icon/fatcow/thumbnails/image.p100.png' => 'da23cf97', - 'rsrc/image/icon/fatcow/thumbnails/image160x120.png' => '79bb556a', - 'rsrc/image/icon/fatcow/thumbnails/image280x210.png' => '91ae054a', - 'rsrc/image/icon/fatcow/thumbnails/image60x45.png' => 'c5e1685e', - 'rsrc/image/icon/fatcow/thumbnails/pdf.p100.png' => '87d5e065', - 'rsrc/image/icon/fatcow/thumbnails/pdf160x120.png' => 'ac9edbf5', - 'rsrc/image/icon/fatcow/thumbnails/pdf280x210.png' => '1c585653', - 'rsrc/image/icon/fatcow/thumbnails/pdf60x45.png' => 'c0db4143', - 'rsrc/image/icon/fatcow/thumbnails/zip.p100.png' => '6ea5aae4', - 'rsrc/image/icon/fatcow/thumbnails/zip160x120.png' => '75f9cd0f', - 'rsrc/image/icon/fatcow/thumbnails/zip280x210.png' => 'dfda5b8e', - 'rsrc/image/icon/fatcow/thumbnails/zip60x45.png' => 'af11bf3e', 'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8', 'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e', 'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b', @@ -507,15 +491,15 @@ 'aphront-tooltip-css' => '7672b60f', 'aphront-two-column-view-css' => '16ab3ad2', 'aphront-typeahead-control-css' => '0e403212', - 'auth-css' => '1e655982', + 'auth-css' => '44975d4b', 'changeset-view-manager' => '58562350', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '7fedf08b', 'config-welcome-css' => '6abd79be', 'conpherence-durable-column-view' => '8c43d6ac', 'conpherence-menu-css' => 'f389e048', - 'conpherence-message-pane-css' => '0e75feef', - 'conpherence-notification-css' => 'd208f806', + 'conpherence-message-pane-css' => '5bb4b76d', + 'conpherence-notification-css' => '919974b6', 'conpherence-thread-manager' => 'b7342ddb', 'conpherence-transaction-css' => '42a457f6', 'conpherence-update-css' => '1099a660', @@ -787,7 +771,7 @@ 'phui-fontkit-css' => 'dd8ddf27', 'phui-form-css' => 'f535f938', 'phui-form-view-css' => '94ae3032', - 'phui-header-view-css' => 'da4586b1', + 'phui-header-view-css' => '75aaf372', 'phui-icon-view-css' => 'bc766998', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', @@ -795,7 +779,7 @@ 'phui-inline-comment-view-css' => '2174771a', 'phui-list-view-css' => '2e25ebfb', 'phui-object-box-css' => '7d160002', - 'phui-object-item-list-view-css' => '9db65899', + 'phui-object-item-list-view-css' => 'f3a22696', 'phui-pinboard-view-css' => 'eaab2b1b', 'phui-property-list-view-css' => '5b671934', 'phui-remarkup-preview-css' => '19ad512b', @@ -803,7 +787,7 @@ 'phui-status-list-view-css' => '888cedb8', 'phui-tag-view-css' => '402691cc', 'phui-text-css' => 'cf019f54', - 'phui-timeline-view-css' => 'b0fbc4d7', + 'phui-timeline-view-css' => 'a85542c8', 'phui-workboard-view-css' => '3279cbbf', 'phui-workpanel-view-css' => 'e495a5cc', 'phuix-action-list-view' => 'b5c256b8', diff --git a/resources/sql/autopatches/20150513.user.cache.1.sql b/resources/sql/autopatches/20150513.user.cache.1.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150513.user.cache.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_user.user + ADD profileImageCache VARCHAR(255) COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/files/transform/PhabricatorFileImageTransform.php b/src/applications/files/transform/PhabricatorFileImageTransform.php --- a/src/applications/files/transform/PhabricatorFileImageTransform.php +++ b/src/applications/files/transform/PhabricatorFileImageTransform.php @@ -141,14 +141,14 @@ $name = 'default.png'; } - $name = $this->getTransformKey().'-'.$name; - - return PhabricatorFile::newFromFileData( - $data, - array( - 'name' => $name, - 'canCDN' => true, - ) + $this->getFileProperties()); + $defaults = array( + 'canCDN' => true, + 'name' => $this->getTransformKey().'-'.$name, + ); + + $properties = $this->getFileProperties() + $defaults; + + return PhabricatorFile::newFromFileData($data, $properties); } diff --git a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php --- a/src/applications/files/transform/PhabricatorFileThumbnailTransform.php +++ b/src/applications/files/transform/PhabricatorFileThumbnailTransform.php @@ -48,6 +48,7 @@ switch ($this->key) { case self::TRANSFORM_PROFILE: $properties['profile'] = true; + $properties['name'] = 'profile'; break; } return $properties; @@ -185,8 +186,8 @@ $scale = $scale_y; } - $copy_x = $dst_x / $scale_x; - $copy_y = $dst_y / $scale_x; + $copy_x = $dst_x / $scale; + $copy_y = $dst_y / $scale; if (!$scale_up) { $copy_x = min($src_x, $copy_x); diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -148,26 +148,55 @@ } if ($this->needProfileImage) { - $user_profile_file_phids = mpull($users, 'getProfileImagePHID'); - $user_profile_file_phids = array_filter($user_profile_file_phids); - if ($user_profile_file_phids) { - $files = id(new PhabricatorFileQuery()) - ->setParentQuery($this) - ->setViewer($this->getViewer()) - ->withPHIDs($user_profile_file_phids) - ->execute(); - $files = mpull($files, null, 'getPHID'); - } else { - $files = array(); - } + $rebuild = array(); foreach ($users as $user) { - $image_phid = $user->getProfileImagePHID(); - if (isset($files[$image_phid])) { - $profile_image_uri = $files[$image_phid]->getBestURI(); + $image_uri = $user->getProfileImageCache(); + if ($image_uri) { + // This user has a valid cache, so we don't need to fetch any + // data or rebuild anything. + + $user->attachProfileImageURI($image_uri); + continue; + } + + // This user's cache is invalid or missing, so we're going to rebuild + // it. + $rebuild[] = $user; + } + + if ($rebuild) { + $file_phids = mpull($rebuild, 'getProfileImagePHID'); + $file_phids = array_filter($file_phids); + + if ($file_phids) { + // NOTE: We're using the omnipotent user here because older profile + // images do not have the 'profile' flag, so they may not be visible + // to the executing viewer. At some point, we could migrate to add + // this flag and then use the real viewer, or just use the real + // viewer after enough time has passed to limit the impact of old + // data. The consequence of missing here is that we cache a default + // image when a real image exists. + $files = id(new PhabricatorFileQuery()) + ->setParentQuery($this) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); } else { - $profile_image_uri = PhabricatorUser::getDefaultProfileImageURI(); + $files = array(); + } + + foreach ($rebuild as $user) { + $image_phid = $user->getProfileImagePHID(); + if (isset($files[$image_phid])) { + $image_uri = $files[$image_phid]->getBestURI(); + } else { + $image_uri = PhabricatorUser::getDefaultProfileImageURI(); + } + + $user->writeProfileImageCache($image_uri); + $user->attachProfileImageURI($image_uri); } - $user->attachProfileImageURI($profile_image_uri); } } diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -1,6 +1,7 @@ 'uint32', 'accountSecret' => 'bytes64', 'isEnrolledInMultiFactor' => 'bool', + 'profileImageCache' => 'text255?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -160,6 +163,9 @@ 'columns' => array('isApproved'), ), ), + self::CONFIG_NO_MUTATE => array( + 'profileImageCache' => true, + ), ) + parent::getConfiguration(); } @@ -721,6 +727,72 @@ } +/* -( Profile Image Cache )------------------------------------------------ */ + + + /** + * Get this user's cached profile image URI. + * + * @return string|null Cached URI, if a URI is cached. + * @task image-cache + */ + public function getProfileImageCache() { + $version = $this->getProfileImageVersion(); + + $parts = explode(',', $this->profileImageCache, 2); + if (count($parts) !== 2) { + return null; + } + + if ($parts[0] !== $version) { + return null; + } + + return $parts[1]; + } + + + /** + * Generate a new cache value for this user's profile image. + * + * @return string New cache value. + * @task image-cache + */ + public function writeProfileImageCache($uri) { + $version = $this->getProfileImageVersion(); + $cache = "{$version},{$uri}"; + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + queryfx( + $this->establishConnection('w'), + 'UPDATE %T SET profileImageCache = %s WHERE id = %d', + $this->getTableName(), + $cache, + $this->getID()); + unset($unguarded); + } + + + /** + * Get a version identifier for a user's profile image. + * + * This version will change if the image changes, or if any of the + * environment configuration which goes into generating a URI changes. + * + * @return string Cache version. + * @task image-cache + */ + private function getProfileImageVersion() { + $parts = array( + PhabricatorEnv::getCDNURI('/'), + PhabricatorEnv::getEnvConfig('cluster.instance'), + $this->getProfileImagePHID(), + ); + $parts = serialize($parts); + return PhabricatorHash::digestForIndex($parts); + } + + /* -( Multi-Factor Authentication )---------------------------------------- */