@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement a user profile image cache

Summary:
Ref T7707. The general form of this can probably be refined somewhat over time as we have more use cases.

I put this cache on the user object itself because we essentially always need this data and it's trivial to invalidate the cache (we can do it implicilty during reads).

Also fix an issue with short, wide images not thumbnailing properly after recent changes.

Test Plan:
- Loaded some pages; saw caches write; saw good pictures.
- Reloaded; saw cache reads; saw good pictures.
- Changed profile picture; saw immediate update.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T7707

Differential Revision: https://secure.phabricator.com/D12826

+143 -55
+13 -29
resources/celerity/map.php
··· 7 7 */ 8 8 return array( 9 9 'names' => array( 10 - 'core.pkg.css' => 'ed3d6355', 10 + 'core.pkg.css' => '7ac320f1', 11 11 'core.pkg.js' => 'ac41c400', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => 'bb338e4b', ··· 33 33 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', 34 34 'rsrc/css/aphront/typeahead.css' => '0e403212', 35 35 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 36 - 'rsrc/css/application/auth/auth.css' => '1e655982', 36 + 'rsrc/css/application/auth/auth.css' => '44975d4b', 37 37 'rsrc/css/application/base/main-menu-view.css' => '663e3810', 38 38 'rsrc/css/application/base/notification-menu.css' => '3c9d8aa1', 39 39 'rsrc/css/application/base/phabricator-application-launch-view.css' => '16ca323f', ··· 47 47 'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2', 48 48 'rsrc/css/application/conpherence/durable-column.css' => '8c43d6ac', 49 49 'rsrc/css/application/conpherence/menu.css' => 'f389e048', 50 - 'rsrc/css/application/conpherence/message-pane.css' => '0e75feef', 51 - 'rsrc/css/application/conpherence/notification.css' => 'd208f806', 50 + 'rsrc/css/application/conpherence/message-pane.css' => '5bb4b76d', 51 + 'rsrc/css/application/conpherence/notification.css' => '919974b6', 52 52 'rsrc/css/application/conpherence/transaction.css' => '42a457f6', 53 53 'rsrc/css/application/conpherence/update.css' => '1099a660', 54 54 'rsrc/css/application/conpherence/widget-pane.css' => '2af42ebe', ··· 135 135 'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27', 136 136 'rsrc/css/phui/phui-form-view.css' => '94ae3032', 137 137 'rsrc/css/phui/phui-form.css' => 'f535f938', 138 - 'rsrc/css/phui/phui-header-view.css' => 'da4586b1', 138 + 'rsrc/css/phui/phui-header-view.css' => '75aaf372', 139 139 'rsrc/css/phui/phui-icon.css' => 'bc766998', 140 140 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 141 141 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 142 142 'rsrc/css/phui/phui-info-view.css' => 'c6f0aef8', 143 143 'rsrc/css/phui/phui-list.css' => '2e25ebfb', 144 144 'rsrc/css/phui/phui-object-box.css' => '7d160002', 145 - 'rsrc/css/phui/phui-object-item-list-view.css' => '9db65899', 145 + 'rsrc/css/phui/phui-object-item-list-view.css' => 'f3a22696', 146 146 'rsrc/css/phui/phui-pinboard-view.css' => 'eaab2b1b', 147 147 'rsrc/css/phui/phui-property-list-view.css' => '5b671934', 148 148 'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b', ··· 150 150 'rsrc/css/phui/phui-status.css' => '888cedb8', 151 151 'rsrc/css/phui/phui-tag-view.css' => '402691cc', 152 152 'rsrc/css/phui/phui-text.css' => 'cf019f54', 153 - 'rsrc/css/phui/phui-timeline-view.css' => 'b0fbc4d7', 153 + 'rsrc/css/phui/phui-timeline-view.css' => 'a85542c8', 154 154 'rsrc/css/phui/phui-workboard-view.css' => '3279cbbf', 155 155 'rsrc/css/phui/phui-workpanel-view.css' => 'e495a5cc', 156 156 'rsrc/css/sprite-gradient.css' => '4bdb98a7', ··· 281 281 'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264', 282 282 'rsrc/image/icon/fatcow/source/tablet.png' => '49396799', 283 283 'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d', 284 - 'rsrc/image/icon/fatcow/thumbnails/default.p100.png' => '7d490b01', 285 - 'rsrc/image/icon/fatcow/thumbnails/default160x120.png' => 'f2e8a2eb', 286 - 'rsrc/image/icon/fatcow/thumbnails/default280x210.png' => '43e8926a', 287 - 'rsrc/image/icon/fatcow/thumbnails/default60x45.png' => '0118abed', 288 - 'rsrc/image/icon/fatcow/thumbnails/image.p100.png' => 'da23cf97', 289 - 'rsrc/image/icon/fatcow/thumbnails/image160x120.png' => '79bb556a', 290 - 'rsrc/image/icon/fatcow/thumbnails/image280x210.png' => '91ae054a', 291 - 'rsrc/image/icon/fatcow/thumbnails/image60x45.png' => 'c5e1685e', 292 - 'rsrc/image/icon/fatcow/thumbnails/pdf.p100.png' => '87d5e065', 293 - 'rsrc/image/icon/fatcow/thumbnails/pdf160x120.png' => 'ac9edbf5', 294 - 'rsrc/image/icon/fatcow/thumbnails/pdf280x210.png' => '1c585653', 295 - 'rsrc/image/icon/fatcow/thumbnails/pdf60x45.png' => 'c0db4143', 296 - 'rsrc/image/icon/fatcow/thumbnails/zip.p100.png' => '6ea5aae4', 297 - 'rsrc/image/icon/fatcow/thumbnails/zip160x120.png' => '75f9cd0f', 298 - 'rsrc/image/icon/fatcow/thumbnails/zip280x210.png' => 'dfda5b8e', 299 - 'rsrc/image/icon/fatcow/thumbnails/zip60x45.png' => 'af11bf3e', 300 284 'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8', 301 285 'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e', 302 286 'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b', ··· 507 491 'aphront-tooltip-css' => '7672b60f', 508 492 'aphront-two-column-view-css' => '16ab3ad2', 509 493 'aphront-typeahead-control-css' => '0e403212', 510 - 'auth-css' => '1e655982', 494 + 'auth-css' => '44975d4b', 511 495 'changeset-view-manager' => '58562350', 512 496 'conduit-api-css' => '7bc725c4', 513 497 'config-options-css' => '7fedf08b', 514 498 'config-welcome-css' => '6abd79be', 515 499 'conpherence-durable-column-view' => '8c43d6ac', 516 500 'conpherence-menu-css' => 'f389e048', 517 - 'conpherence-message-pane-css' => '0e75feef', 518 - 'conpherence-notification-css' => 'd208f806', 501 + 'conpherence-message-pane-css' => '5bb4b76d', 502 + 'conpherence-notification-css' => '919974b6', 519 503 'conpherence-thread-manager' => 'b7342ddb', 520 504 'conpherence-transaction-css' => '42a457f6', 521 505 'conpherence-update-css' => '1099a660', ··· 787 771 'phui-fontkit-css' => 'dd8ddf27', 788 772 'phui-form-css' => 'f535f938', 789 773 'phui-form-view-css' => '94ae3032', 790 - 'phui-header-view-css' => 'da4586b1', 774 + 'phui-header-view-css' => '75aaf372', 791 775 'phui-icon-view-css' => 'bc766998', 792 776 'phui-image-mask-css' => '5a8b09c8', 793 777 'phui-info-panel-css' => '27ea50a1', ··· 795 779 'phui-inline-comment-view-css' => '2174771a', 796 780 'phui-list-view-css' => '2e25ebfb', 797 781 'phui-object-box-css' => '7d160002', 798 - 'phui-object-item-list-view-css' => '9db65899', 782 + 'phui-object-item-list-view-css' => 'f3a22696', 799 783 'phui-pinboard-view-css' => 'eaab2b1b', 800 784 'phui-property-list-view-css' => '5b671934', 801 785 'phui-remarkup-preview-css' => '19ad512b', ··· 803 787 'phui-status-list-view-css' => '888cedb8', 804 788 'phui-tag-view-css' => '402691cc', 805 789 'phui-text-css' => 'cf019f54', 806 - 'phui-timeline-view-css' => 'b0fbc4d7', 790 + 'phui-timeline-view-css' => 'a85542c8', 807 791 'phui-workboard-view-css' => '3279cbbf', 808 792 'phui-workpanel-view-css' => 'e495a5cc', 809 793 'phuix-action-list-view' => 'b5c256b8',
+2
resources/sql/autopatches/20150513.user.cache.1.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.user 2 + ADD profileImageCache VARCHAR(255) COLLATE {$COLLATE_TEXT};
+7 -7
src/applications/files/transform/PhabricatorFileImageTransform.php
··· 141 141 $name = 'default.png'; 142 142 } 143 143 144 - $name = $this->getTransformKey().'-'.$name; 144 + $defaults = array( 145 + 'canCDN' => true, 146 + 'name' => $this->getTransformKey().'-'.$name, 147 + ); 145 148 146 - return PhabricatorFile::newFromFileData( 147 - $data, 148 - array( 149 - 'name' => $name, 150 - 'canCDN' => true, 151 - ) + $this->getFileProperties()); 149 + $properties = $this->getFileProperties() + $defaults; 150 + 151 + return PhabricatorFile::newFromFileData($data, $properties); 152 152 } 153 153 154 154
+3 -2
src/applications/files/transform/PhabricatorFileThumbnailTransform.php
··· 48 48 switch ($this->key) { 49 49 case self::TRANSFORM_PROFILE: 50 50 $properties['profile'] = true; 51 + $properties['name'] = 'profile'; 51 52 break; 52 53 } 53 54 return $properties; ··· 185 186 $scale = $scale_y; 186 187 } 187 188 188 - $copy_x = $dst_x / $scale_x; 189 - $copy_y = $dst_y / $scale_x; 189 + $copy_x = $dst_x / $scale; 190 + $copy_y = $dst_y / $scale; 190 191 191 192 if (!$scale_up) { 192 193 $copy_x = min($src_x, $copy_x);
+46 -17
src/applications/people/query/PhabricatorPeopleQuery.php
··· 148 148 } 149 149 150 150 if ($this->needProfileImage) { 151 - $user_profile_file_phids = mpull($users, 'getProfileImagePHID'); 152 - $user_profile_file_phids = array_filter($user_profile_file_phids); 153 - if ($user_profile_file_phids) { 154 - $files = id(new PhabricatorFileQuery()) 155 - ->setParentQuery($this) 156 - ->setViewer($this->getViewer()) 157 - ->withPHIDs($user_profile_file_phids) 158 - ->execute(); 159 - $files = mpull($files, null, 'getPHID'); 160 - } else { 161 - $files = array(); 151 + $rebuild = array(); 152 + foreach ($users as $user) { 153 + $image_uri = $user->getProfileImageCache(); 154 + if ($image_uri) { 155 + // This user has a valid cache, so we don't need to fetch any 156 + // data or rebuild anything. 157 + 158 + $user->attachProfileImageURI($image_uri); 159 + continue; 160 + } 161 + 162 + // This user's cache is invalid or missing, so we're going to rebuild 163 + // it. 164 + $rebuild[] = $user; 162 165 } 163 - foreach ($users as $user) { 164 - $image_phid = $user->getProfileImagePHID(); 165 - if (isset($files[$image_phid])) { 166 - $profile_image_uri = $files[$image_phid]->getBestURI(); 166 + 167 + if ($rebuild) { 168 + $file_phids = mpull($rebuild, 'getProfileImagePHID'); 169 + $file_phids = array_filter($file_phids); 170 + 171 + if ($file_phids) { 172 + // NOTE: We're using the omnipotent user here because older profile 173 + // images do not have the 'profile' flag, so they may not be visible 174 + // to the executing viewer. At some point, we could migrate to add 175 + // this flag and then use the real viewer, or just use the real 176 + // viewer after enough time has passed to limit the impact of old 177 + // data. The consequence of missing here is that we cache a default 178 + // image when a real image exists. 179 + $files = id(new PhabricatorFileQuery()) 180 + ->setParentQuery($this) 181 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 182 + ->withPHIDs($file_phids) 183 + ->execute(); 184 + $files = mpull($files, null, 'getPHID'); 167 185 } else { 168 - $profile_image_uri = PhabricatorUser::getDefaultProfileImageURI(); 186 + $files = array(); 187 + } 188 + 189 + foreach ($rebuild as $user) { 190 + $image_phid = $user->getProfileImagePHID(); 191 + if (isset($files[$image_phid])) { 192 + $image_uri = $files[$image_phid]->getBestURI(); 193 + } else { 194 + $image_uri = PhabricatorUser::getDefaultProfileImageURI(); 195 + } 196 + 197 + $user->writeProfileImageCache($image_uri); 198 + $user->attachProfileImageURI($image_uri); 169 199 } 170 - $user->attachProfileImageURI($profile_image_uri); 171 200 } 172 201 } 173 202
+72
src/applications/people/storage/PhabricatorUser.php
··· 1 1 <?php 2 2 3 3 /** 4 + * @task image-cache Profile Image Cache 4 5 * @task factors Multi-Factor Authentication 5 6 * @task handles Managing Handles 6 7 */ ··· 24 25 protected $passwordSalt; 25 26 protected $passwordHash; 26 27 protected $profileImagePHID; 28 + protected $profileImageCache; 27 29 protected $timezoneIdentifier = ''; 28 30 29 31 protected $consoleEnabled = 0; ··· 142 144 'isApproved' => 'uint32', 143 145 'accountSecret' => 'bytes64', 144 146 'isEnrolledInMultiFactor' => 'bool', 147 + 'profileImageCache' => 'text255?', 145 148 ), 146 149 self::CONFIG_KEY_SCHEMA => array( 147 150 'key_phid' => null, ··· 159 162 'key_approved' => array( 160 163 'columns' => array('isApproved'), 161 164 ), 165 + ), 166 + self::CONFIG_NO_MUTATE => array( 167 + 'profileImageCache' => true, 162 168 ), 163 169 ) + parent::getConfiguration(); 164 170 } ··· 718 724 */ 719 725 public function getAuthorities() { 720 726 return $this->authorities; 727 + } 728 + 729 + 730 + /* -( Profile Image Cache )------------------------------------------------ */ 731 + 732 + 733 + /** 734 + * Get this user's cached profile image URI. 735 + * 736 + * @return string|null Cached URI, if a URI is cached. 737 + * @task image-cache 738 + */ 739 + public function getProfileImageCache() { 740 + $version = $this->getProfileImageVersion(); 741 + 742 + $parts = explode(',', $this->profileImageCache, 2); 743 + if (count($parts) !== 2) { 744 + return null; 745 + } 746 + 747 + if ($parts[0] !== $version) { 748 + return null; 749 + } 750 + 751 + return $parts[1]; 752 + } 753 + 754 + 755 + /** 756 + * Generate a new cache value for this user's profile image. 757 + * 758 + * @return string New cache value. 759 + * @task image-cache 760 + */ 761 + public function writeProfileImageCache($uri) { 762 + $version = $this->getProfileImageVersion(); 763 + $cache = "{$version},{$uri}"; 764 + 765 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 766 + queryfx( 767 + $this->establishConnection('w'), 768 + 'UPDATE %T SET profileImageCache = %s WHERE id = %d', 769 + $this->getTableName(), 770 + $cache, 771 + $this->getID()); 772 + unset($unguarded); 773 + } 774 + 775 + 776 + /** 777 + * Get a version identifier for a user's profile image. 778 + * 779 + * This version will change if the image changes, or if any of the 780 + * environment configuration which goes into generating a URI changes. 781 + * 782 + * @return string Cache version. 783 + * @task image-cache 784 + */ 785 + private function getProfileImageVersion() { 786 + $parts = array( 787 + PhabricatorEnv::getCDNURI('/'), 788 + PhabricatorEnv::getEnvConfig('cluster.instance'), 789 + $this->getProfileImagePHID(), 790 + ); 791 + $parts = serialize($parts); 792 + return PhabricatorHash::digestForIndex($parts); 721 793 } 722 794 723 795