@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.

Reduce the cost of generating default user profile images

Summary:
See PHI413. You can pre-generate these with `bin/people profileimage --all`, but they're needlessly expensive to generate.

Streamline the workflow and cache some of the cacheable parts to reduce the generation cost.

Test Plan:
- Ran `bin/people profileimage --all` and saw cost drop from {nav 15.801s > 4.839s}.
- Set `defaultProfileImagePHID` to `NULL` in `phabricator_user.user` and purged caches with `bin/cache purge --all`.
- Loaded user directory.
- Saw default images regenerate relatively quickly.

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

+111 -197
-2
src/__phutil_library_map__.php
··· 3044 3044 'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php', 3045 3045 'PhabricatorFilesBuiltinFile' => 'applications/files/builtin/PhabricatorFilesBuiltinFile.php', 3046 3046 'PhabricatorFilesComposeAvatarBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php', 3047 - 'PhabricatorFilesComposeAvatarExample' => 'applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php', 3048 3047 'PhabricatorFilesComposeIconBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php', 3049 3048 'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php', 3050 3049 'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php', ··· 8628 8627 'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel', 8629 8628 'PhabricatorFilesBuiltinFile' => 'Phobject', 8630 8629 'PhabricatorFilesComposeAvatarBuiltinFile' => 'PhabricatorFilesBuiltinFile', 8631 - 'PhabricatorFilesComposeAvatarExample' => 'PhabricatorUIExample', 8632 8630 'PhabricatorFilesComposeIconBuiltinFile' => 'PhabricatorFilesBuiltinFile', 8633 8631 'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions', 8634 8632 'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow',
+5 -1
src/applications/files/PhabricatorImageTransformer.php
··· 333 333 return null; 334 334 } 335 335 336 + // NOTE: Empirically, the highest compression level (9) seems to take 337 + // up to twice as long as the default compression level (6) but produce 338 + // only slightly smaller files (10% on avatars, 3% on screenshots). 339 + 336 340 ob_start(); 337 - $result = imagepng($image, null, 9); 341 + $result = imagepng($image, null, 6); 338 342 $output = ob_get_clean(); 339 343 340 344 if (!$result) {
+94 -78
src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php
··· 7 7 private $color; 8 8 private $border; 9 9 10 + private $maps = array(); 11 + 10 12 const VERSION = 'v1'; 11 13 14 + public function updateUser(PhabricatorUser $user) { 15 + $username = $user->getUsername(); 16 + 17 + $image_map = $this->getMap('image'); 18 + $initial = phutil_utf8_strtoupper(substr($username, 0, 1)); 19 + $pack = $this->pickMap('pack', $username); 20 + $icon = "alphanumeric/{$pack}/{$initial}.png"; 21 + if (!isset($image_map[$icon])) { 22 + $icon = "alphanumeric/{$pack}/_default.png"; 23 + } 24 + 25 + $border = $this->pickMap('border', $username); 26 + $color = $this->pickMap('color', $username); 27 + 28 + $data = $this->composeImage($color, $icon, $border); 29 + $name = $this->getImageDisplayName($color, $icon, $border); 30 + 31 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 32 + 33 + $file = PhabricatorFile::newFromFileData( 34 + $data, 35 + array( 36 + 'name' => $name, 37 + 'profile' => true, 38 + 'canCDN' => true, 39 + )); 40 + 41 + $user 42 + ->setDefaultProfileImagePHID($file->getPHID()) 43 + ->setDefaultProfileImageVersion(self::VERSION) 44 + ->saveWithoutIndex(); 45 + 46 + unset($unguarded); 47 + 48 + return $file; 49 + } 50 + 51 + private function getMap($map_key) { 52 + if (!isset($this->maps[$map_key])) { 53 + switch ($map_key) { 54 + case 'pack': 55 + $map = $this->newPackMap(); 56 + break; 57 + case 'image': 58 + $map = $this->newImageMap(); 59 + break; 60 + case 'color': 61 + $map = $this->newColorMap(); 62 + break; 63 + case 'border': 64 + $map = $this->newBorderMap(); 65 + break; 66 + default: 67 + throw new Exception(pht('Unknown map "%s".', $map_key)); 68 + } 69 + $this->maps[$map_key] = $map; 70 + } 71 + 72 + return $this->maps[$map_key]; 73 + } 74 + 75 + private function pickMap($map_key, $username) { 76 + $map = $this->getMap($map_key); 77 + $seed = $username.'_'.$map_key; 78 + $key = PhabricatorHash::digestToRange($seed, 0, count($map) - 1); 79 + return $map[$key]; 80 + } 81 + 82 + 12 83 public function setIcon($icon) { 13 84 $this->icon = $icon; 14 85 return $this; ··· 46 117 } 47 118 48 119 public function getBuiltinDisplayName() { 49 - $icon = $this->getIcon(); 50 - $color = $this->getColor(); 51 - $border = implode(',', $this->getBorder()); 120 + return $this->getImageDisplayName( 121 + $this->getIcon(), 122 + $this->getColor(), 123 + $this->getBorder()); 124 + } 125 + 126 + private function getImageDisplayName($icon, $color, $border) { 127 + $border = implode(',', $border); 52 128 return "{$icon}-{$color}-{$border}.png"; 53 129 } 54 130 55 131 public function loadBuiltinFileData() { 56 132 return $this->composeImage( 57 - $this->getColor(), $this->getIcon(), $this->getBorder()); 133 + $this->getColor(), 134 + $this->getIcon(), 135 + $this->getBorder()); 58 136 } 59 137 60 138 private function composeImage($color, $image, $border) { ··· 68 146 69 147 $color_const = hexdec(trim($color, '#')); 70 148 $true_border = self::rgba2gd($border); 71 - $image_map = self::getImageMap(); 149 + $image_map = $this->getMap('image'); 72 150 $data = Filesystem::readFile($image_map[$image]); 73 151 74 152 $img = imagecreatefromstring($data); ··· 111 189 $b = $rgba[2]; 112 190 $a = $rgba[3]; 113 191 $a = (1 - $a) * 255; 114 - return ($a << 24) | ($r << 16) | ($g << 8) | $b; 192 + return ($a << 24) | ($r << 16) | ($g << 8) | $b; 115 193 } 116 194 117 - public static function getImageMap() { 195 + private function newImageMap() { 118 196 $root = dirname(phutil_get_library_root('phabricator')); 119 197 $root = $root.'/resources/builtin/alphanumeric/'; 120 198 ··· 131 209 return $map; 132 210 } 133 211 134 - public function getUniqueProfileImage($username) { 135 - $pack_map = $this->getImagePackMap(); 136 - $image_map = $this->getImageMap(); 137 - $color_map = $this->getColorMap(); 138 - $border_map = $this->getBorderMap(); 139 - $file = phutil_utf8_strtoupper(substr($username, 0, 1)); 140 - 141 - $pack_count = count($pack_map); 142 - $color_count = count($color_map); 143 - $border_count = count($border_map); 144 - 145 - $pack_seed = $username.'_pack'; 146 - $color_seed = $username.'_color'; 147 - $border_seed = $username.'_border'; 148 - 149 - $pack_key = 150 - PhabricatorHash::digestToRange($pack_seed, 0, $pack_count - 1); 151 - $color_key = 152 - PhabricatorHash::digestToRange($color_seed, 0, $color_count - 1); 153 - $border_key = 154 - PhabricatorHash::digestToRange($border_seed, 0, $border_count - 1); 155 - 156 - $pack = $pack_map[$pack_key]; 157 - $icon = 'alphanumeric/'.$pack.'/'.$file.'.png'; 158 - $color = $color_map[$color_key]; 159 - $border = $border_map[$border_key]; 160 - 161 - if (!isset($image_map[$icon])) { 162 - $icon = 'alphanumeric/'.$pack.'/_default.png'; 163 - } 164 - 165 - return array('color' => $color, 'icon' => $icon, 'border' => $border); 166 - } 167 - 168 - public function getUserProfileImageFile($username) { 169 - $unique = $this->getUniqueProfileImage($username); 170 - 171 - $composer = id(new self()) 172 - ->setIcon($unique['icon']) 173 - ->setColor($unique['color']) 174 - ->setBorder($unique['border']); 175 - 176 - $data = $composer->loadBuiltinFileData(); 177 - 178 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 179 - $file = PhabricatorFile::newFromFileData( 180 - $data, 181 - array( 182 - 'name' => $composer->getBuiltinDisplayName(), 183 - 'profile' => true, 184 - 'canCDN' => true, 185 - )); 186 - unset($unguarded); 187 - 188 - return $file; 189 - } 190 - 191 - public static function getImagePackMap() { 212 + private function newPackMap() { 192 213 $root = dirname(phutil_get_library_root('phabricator')); 193 214 $root = $root.'/resources/builtin/alphanumeric/'; 194 215 ··· 196 217 ->withType('d') 197 218 ->withFollowSymlinks(false) 198 219 ->find(); 220 + $map = array_values($map); 199 221 200 - return array_values($map); 222 + return $map; 201 223 } 202 224 203 - public static function getBorderMap() { 204 - 205 - $map = array( 225 + private function newBorderMap() { 226 + return array( 206 227 array(0, 0, 0, 0), 207 228 array(0, 0, 0, 0.3), 208 229 array(255, 255, 255, 0.4), 209 230 array(255, 255, 255, 0.7), 210 231 ); 232 + } 211 233 212 - return $map; 213 - } 234 + private function newColorMap() { 235 + // Via: http://tools.medialab.sciences-po.fr/iwanthue/ 214 236 215 - public static function getColorMap() { 216 - // 217 - // Generated Colors 218 - // http://tools.medialab.sciences-po.fr/iwanthue/ 219 - // 220 - $map = array( 237 + return array( 221 238 '#335862', 222 239 '#2d5192', 223 240 '#3c5da0', ··· 447 464 '#335862', 448 465 '#335862', 449 466 ); 450 - return $map; 451 467 } 452 468 453 469 }
+4 -15
src/applications/people/cache/PhabricatorUserProfileImageCacheType.php
··· 45 45 $generate_users[] = $user; 46 46 } 47 47 48 - // Generate Files for anyone without a default 49 - foreach ($generate_users as $generate_user) { 50 - $generate_user_phid = $generate_user->getPHID(); 51 - $generate_username = $generate_user->getUsername(); 52 - $generate_version = PhabricatorFilesComposeAvatarBuiltinFile::VERSION; 53 - $generate_file = id(new PhabricatorFilesComposeAvatarBuiltinFile()) 54 - ->getUserProfileImageFile($generate_username); 55 - 56 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 57 - $generate_user->setDefaultProfileImagePHID($generate_file->getPHID()); 58 - $generate_user->setDefaultProfileImageVersion($generate_version); 59 - $generate_user->save(); 60 - unset($unguarded); 61 - 62 - $file_phids[$generate_user_phid] = $generate_file->getPHID(); 48 + $generator = new PhabricatorFilesComposeAvatarBuiltinFile(); 49 + foreach ($generate_users as $user) { 50 + $file = $generator->updateUser($user); 51 + $file_phids[$user->getPHID()] = $file->getPHID(); 63 52 } 64 53 65 54 if ($file_phids) {
+3 -5
src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php
··· 51 51 } 52 52 53 53 $version = PhabricatorFilesComposeAvatarBuiltinFile::VERSION; 54 + $generator = new PhabricatorFilesComposeAvatarBuiltinFile(); 54 55 55 56 foreach ($iterator as $user) { 56 57 $username = $user->getUsername(); ··· 63 64 } 64 65 65 66 if ($default_phid == null || $is_force || $generate) { 66 - $file = id(new PhabricatorFilesComposeAvatarBuiltinFile()) 67 - ->getUserProfileImageFile($username); 68 - $user->setDefaultProfileImagePHID($file->getPHID()); 69 - $user->setDefaultProfileImageVersion($version); 70 - $user->save(); 71 67 $console->writeOut( 72 68 "%s\n", 73 69 pht( 74 70 'Generating profile image for "%s".', 75 71 $username)); 72 + 73 + $generator->updateUser($user); 76 74 } else { 77 75 $console->writeOut( 78 76 "%s\n",
+5 -1
src/applications/people/storage/PhabricatorUser.php
··· 267 267 return !($this->getPHID() === null); 268 268 } 269 269 270 + public function saveWithoutIndex() { 271 + return parent::save(); 272 + } 273 + 270 274 public function save() { 271 275 if (!$this->getConduitCertificate()) { 272 276 $this->setConduitCertificate($this->generateConduitCertificate()); ··· 276 280 $this->setAccountSecret(Filesystem::readRandomCharacters(64)); 277 281 } 278 282 279 - $result = parent::save(); 283 + $result = $this->saveWithoutIndex(); 280 284 281 285 if ($this->profile) { 282 286 $this->profile->save();
-95
src/applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php
··· 1 - <?php 2 - 3 - final class PhabricatorFilesComposeAvatarExample extends PhabricatorUIExample { 4 - 5 - public function getName() { 6 - return pht('Avatars'); 7 - } 8 - 9 - public function getDescription() { 10 - return pht('Tests various color palettes and sizes.'); 11 - } 12 - 13 - public function getCategory() { 14 - return pht('Technical'); 15 - } 16 - 17 - public function renderExample() { 18 - $request = $this->getRequest(); 19 - $viewer = $request->getUser(); 20 - 21 - $colors = PhabricatorFilesComposeAvatarBuiltinFile::getColorMap(); 22 - $packs = PhabricatorFilesComposeAvatarBuiltinFile::getImagePackMap(); 23 - $builtins = PhabricatorFilesComposeAvatarBuiltinFile::getImageMap(); 24 - $borders = PhabricatorFilesComposeAvatarBuiltinFile::getBorderMap(); 25 - 26 - $images = array(); 27 - foreach ($builtins as $builtin => $raw_file) { 28 - $file = PhabricatorFile::loadBuiltin($viewer, $builtin); 29 - $images[] = $file->getBestURI(); 30 - } 31 - 32 - $content = array(); 33 - shuffle($colors); 34 - foreach ($colors as $color) { 35 - shuffle($borders); 36 - $color_const = hexdec(trim($color, '#')); 37 - $border = head($borders); 38 - $border_color = implode(', ', $border); 39 - 40 - $styles = array(); 41 - $styles[] = 'background-color: '.$color.';'; 42 - $styles[] = 'display: inline-block;'; 43 - $styles[] = 'height: 42px;'; 44 - $styles[] = 'width: 42px;'; 45 - $styles[] = 'border-radius: 3px;'; 46 - $styles[] = 'border: 4px solid rgba('.$border_color.');'; 47 - 48 - shuffle($images); 49 - $png = head($images); 50 - 51 - $image = phutil_tag( 52 - 'img', 53 - array( 54 - 'src' => $png, 55 - 'height' => 42, 56 - 'width' => 42, 57 - )); 58 - 59 - $tag = phutil_tag( 60 - 'div', 61 - array( 62 - 'style' => implode(' ', $styles), 63 - ), 64 - $image); 65 - 66 - $content[] = phutil_tag( 67 - 'div', 68 - array( 69 - 'class' => 'mlr mlb', 70 - 'style' => 'float: left;', 71 - ), 72 - $tag); 73 - } 74 - 75 - $count = new PhutilNumber( 76 - count($colors) * count($builtins) * count($borders)); 77 - 78 - $infoview = id(new PHUIInfoView()) 79 - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) 80 - ->appendChild(pht('This installation can generate %s unique '. 81 - 'avatars. You can add additional image packs in '. 82 - 'resources/builtins/alphanumeric/.', $count)); 83 - 84 - $info = phutil_tag_div('pmb', $infoview); 85 - $view = phutil_tag_div('ml', $content); 86 - 87 - return phutil_tag( 88 - 'div', 89 - array(), 90 - array( 91 - $info, 92 - $view, 93 - )); 94 - } 95 - }