mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-26 23:40:57 +01:00
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
This commit is contained in:
parent
1f40e50f7e
commit
14fe941c34
7 changed files with 111 additions and 197 deletions
|
@ -3044,7 +3044,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php',
|
||||
'PhabricatorFilesBuiltinFile' => 'applications/files/builtin/PhabricatorFilesBuiltinFile.php',
|
||||
'PhabricatorFilesComposeAvatarBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php',
|
||||
'PhabricatorFilesComposeAvatarExample' => 'applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php',
|
||||
'PhabricatorFilesComposeIconBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php',
|
||||
'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php',
|
||||
'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php',
|
||||
|
@ -8628,7 +8627,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel',
|
||||
'PhabricatorFilesBuiltinFile' => 'Phobject',
|
||||
'PhabricatorFilesComposeAvatarBuiltinFile' => 'PhabricatorFilesBuiltinFile',
|
||||
'PhabricatorFilesComposeAvatarExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorFilesComposeIconBuiltinFile' => 'PhabricatorFilesBuiltinFile',
|
||||
'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow',
|
||||
|
|
|
@ -333,8 +333,12 @@ final class PhabricatorImageTransformer extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
// NOTE: Empirically, the highest compression level (9) seems to take
|
||||
// up to twice as long as the default compression level (6) but produce
|
||||
// only slightly smaller files (10% on avatars, 3% on screenshots).
|
||||
|
||||
ob_start();
|
||||
$result = imagepng($image, null, 9);
|
||||
$result = imagepng($image, null, 6);
|
||||
$output = ob_get_clean();
|
||||
|
||||
if (!$result) {
|
||||
|
|
|
@ -7,8 +7,79 @@ final class PhabricatorFilesComposeAvatarBuiltinFile
|
|||
private $color;
|
||||
private $border;
|
||||
|
||||
private $maps = array();
|
||||
|
||||
const VERSION = 'v1';
|
||||
|
||||
public function updateUser(PhabricatorUser $user) {
|
||||
$username = $user->getUsername();
|
||||
|
||||
$image_map = $this->getMap('image');
|
||||
$initial = phutil_utf8_strtoupper(substr($username, 0, 1));
|
||||
$pack = $this->pickMap('pack', $username);
|
||||
$icon = "alphanumeric/{$pack}/{$initial}.png";
|
||||
if (!isset($image_map[$icon])) {
|
||||
$icon = "alphanumeric/{$pack}/_default.png";
|
||||
}
|
||||
|
||||
$border = $this->pickMap('border', $username);
|
||||
$color = $this->pickMap('color', $username);
|
||||
|
||||
$data = $this->composeImage($color, $icon, $border);
|
||||
$name = $this->getImageDisplayName($color, $icon, $border);
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => $name,
|
||||
'profile' => true,
|
||||
'canCDN' => true,
|
||||
));
|
||||
|
||||
$user
|
||||
->setDefaultProfileImagePHID($file->getPHID())
|
||||
->setDefaultProfileImageVersion(self::VERSION)
|
||||
->saveWithoutIndex();
|
||||
|
||||
unset($unguarded);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function getMap($map_key) {
|
||||
if (!isset($this->maps[$map_key])) {
|
||||
switch ($map_key) {
|
||||
case 'pack':
|
||||
$map = $this->newPackMap();
|
||||
break;
|
||||
case 'image':
|
||||
$map = $this->newImageMap();
|
||||
break;
|
||||
case 'color':
|
||||
$map = $this->newColorMap();
|
||||
break;
|
||||
case 'border':
|
||||
$map = $this->newBorderMap();
|
||||
break;
|
||||
default:
|
||||
throw new Exception(pht('Unknown map "%s".', $map_key));
|
||||
}
|
||||
$this->maps[$map_key] = $map;
|
||||
}
|
||||
|
||||
return $this->maps[$map_key];
|
||||
}
|
||||
|
||||
private function pickMap($map_key, $username) {
|
||||
$map = $this->getMap($map_key);
|
||||
$seed = $username.'_'.$map_key;
|
||||
$key = PhabricatorHash::digestToRange($seed, 0, count($map) - 1);
|
||||
return $map[$key];
|
||||
}
|
||||
|
||||
|
||||
public function setIcon($icon) {
|
||||
$this->icon = $icon;
|
||||
return $this;
|
||||
|
@ -46,15 +117,22 @@ final class PhabricatorFilesComposeAvatarBuiltinFile
|
|||
}
|
||||
|
||||
public function getBuiltinDisplayName() {
|
||||
$icon = $this->getIcon();
|
||||
$color = $this->getColor();
|
||||
$border = implode(',', $this->getBorder());
|
||||
return $this->getImageDisplayName(
|
||||
$this->getIcon(),
|
||||
$this->getColor(),
|
||||
$this->getBorder());
|
||||
}
|
||||
|
||||
private function getImageDisplayName($icon, $color, $border) {
|
||||
$border = implode(',', $border);
|
||||
return "{$icon}-{$color}-{$border}.png";
|
||||
}
|
||||
|
||||
public function loadBuiltinFileData() {
|
||||
return $this->composeImage(
|
||||
$this->getColor(), $this->getIcon(), $this->getBorder());
|
||||
$this->getColor(),
|
||||
$this->getIcon(),
|
||||
$this->getBorder());
|
||||
}
|
||||
|
||||
private function composeImage($color, $image, $border) {
|
||||
|
@ -68,7 +146,7 @@ final class PhabricatorFilesComposeAvatarBuiltinFile
|
|||
|
||||
$color_const = hexdec(trim($color, '#'));
|
||||
$true_border = self::rgba2gd($border);
|
||||
$image_map = self::getImageMap();
|
||||
$image_map = $this->getMap('image');
|
||||
$data = Filesystem::readFile($image_map[$image]);
|
||||
|
||||
$img = imagecreatefromstring($data);
|
||||
|
@ -111,10 +189,10 @@ final class PhabricatorFilesComposeAvatarBuiltinFile
|
|||
$b = $rgba[2];
|
||||
$a = $rgba[3];
|
||||
$a = (1 - $a) * 255;
|
||||
return ($a << 24) | ($r << 16) | ($g << 8) | $b;
|
||||
return ($a << 24) | ($r << 16) | ($g << 8) | $b;
|
||||
}
|
||||
|
||||
public static function getImageMap() {
|
||||
private function newImageMap() {
|
||||
$root = dirname(phutil_get_library_root('phabricator'));
|
||||
$root = $root.'/resources/builtin/alphanumeric/';
|
||||
|
||||
|
@ -131,64 +209,7 @@ final class PhabricatorFilesComposeAvatarBuiltinFile
|
|||
return $map;
|
||||
}
|
||||
|
||||
public function getUniqueProfileImage($username) {
|
||||
$pack_map = $this->getImagePackMap();
|
||||
$image_map = $this->getImageMap();
|
||||
$color_map = $this->getColorMap();
|
||||
$border_map = $this->getBorderMap();
|
||||
$file = phutil_utf8_strtoupper(substr($username, 0, 1));
|
||||
|
||||
$pack_count = count($pack_map);
|
||||
$color_count = count($color_map);
|
||||
$border_count = count($border_map);
|
||||
|
||||
$pack_seed = $username.'_pack';
|
||||
$color_seed = $username.'_color';
|
||||
$border_seed = $username.'_border';
|
||||
|
||||
$pack_key =
|
||||
PhabricatorHash::digestToRange($pack_seed, 0, $pack_count - 1);
|
||||
$color_key =
|
||||
PhabricatorHash::digestToRange($color_seed, 0, $color_count - 1);
|
||||
$border_key =
|
||||
PhabricatorHash::digestToRange($border_seed, 0, $border_count - 1);
|
||||
|
||||
$pack = $pack_map[$pack_key];
|
||||
$icon = 'alphanumeric/'.$pack.'/'.$file.'.png';
|
||||
$color = $color_map[$color_key];
|
||||
$border = $border_map[$border_key];
|
||||
|
||||
if (!isset($image_map[$icon])) {
|
||||
$icon = 'alphanumeric/'.$pack.'/_default.png';
|
||||
}
|
||||
|
||||
return array('color' => $color, 'icon' => $icon, 'border' => $border);
|
||||
}
|
||||
|
||||
public function getUserProfileImageFile($username) {
|
||||
$unique = $this->getUniqueProfileImage($username);
|
||||
|
||||
$composer = id(new self())
|
||||
->setIcon($unique['icon'])
|
||||
->setColor($unique['color'])
|
||||
->setBorder($unique['border']);
|
||||
|
||||
$data = $composer->loadBuiltinFileData();
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$file = PhabricatorFile::newFromFileData(
|
||||
$data,
|
||||
array(
|
||||
'name' => $composer->getBuiltinDisplayName(),
|
||||
'profile' => true,
|
||||
'canCDN' => true,
|
||||
));
|
||||
unset($unguarded);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
public static function getImagePackMap() {
|
||||
private function newPackMap() {
|
||||
$root = dirname(phutil_get_library_root('phabricator'));
|
||||
$root = $root.'/resources/builtin/alphanumeric/';
|
||||
|
||||
|
@ -196,28 +217,24 @@ final class PhabricatorFilesComposeAvatarBuiltinFile
|
|||
->withType('d')
|
||||
->withFollowSymlinks(false)
|
||||
->find();
|
||||
$map = array_values($map);
|
||||
|
||||
return array_values($map);
|
||||
return $map;
|
||||
}
|
||||
|
||||
public static function getBorderMap() {
|
||||
|
||||
$map = array(
|
||||
private function newBorderMap() {
|
||||
return array(
|
||||
array(0, 0, 0, 0),
|
||||
array(0, 0, 0, 0.3),
|
||||
array(255, 255, 255, 0.4),
|
||||
array(255, 255, 255, 0.7),
|
||||
);
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
public static function getColorMap() {
|
||||
//
|
||||
// Generated Colors
|
||||
// http://tools.medialab.sciences-po.fr/iwanthue/
|
||||
//
|
||||
$map = array(
|
||||
private function newColorMap() {
|
||||
// Via: http://tools.medialab.sciences-po.fr/iwanthue/
|
||||
|
||||
return array(
|
||||
'#335862',
|
||||
'#2d5192',
|
||||
'#3c5da0',
|
||||
|
@ -447,7 +464,6 @@ final class PhabricatorFilesComposeAvatarBuiltinFile
|
|||
'#335862',
|
||||
'#335862',
|
||||
);
|
||||
return $map;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,21 +45,10 @@ final class PhabricatorUserProfileImageCacheType
|
|||
$generate_users[] = $user;
|
||||
}
|
||||
|
||||
// Generate Files for anyone without a default
|
||||
foreach ($generate_users as $generate_user) {
|
||||
$generate_user_phid = $generate_user->getPHID();
|
||||
$generate_username = $generate_user->getUsername();
|
||||
$generate_version = PhabricatorFilesComposeAvatarBuiltinFile::VERSION;
|
||||
$generate_file = id(new PhabricatorFilesComposeAvatarBuiltinFile())
|
||||
->getUserProfileImageFile($generate_username);
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$generate_user->setDefaultProfileImagePHID($generate_file->getPHID());
|
||||
$generate_user->setDefaultProfileImageVersion($generate_version);
|
||||
$generate_user->save();
|
||||
unset($unguarded);
|
||||
|
||||
$file_phids[$generate_user_phid] = $generate_file->getPHID();
|
||||
$generator = new PhabricatorFilesComposeAvatarBuiltinFile();
|
||||
foreach ($generate_users as $user) {
|
||||
$file = $generator->updateUser($user);
|
||||
$file_phids[$user->getPHID()] = $file->getPHID();
|
||||
}
|
||||
|
||||
if ($file_phids) {
|
||||
|
|
|
@ -51,6 +51,7 @@ final class PhabricatorPeopleProfileImageWorkflow
|
|||
}
|
||||
|
||||
$version = PhabricatorFilesComposeAvatarBuiltinFile::VERSION;
|
||||
$generator = new PhabricatorFilesComposeAvatarBuiltinFile();
|
||||
|
||||
foreach ($iterator as $user) {
|
||||
$username = $user->getUsername();
|
||||
|
@ -63,16 +64,13 @@ final class PhabricatorPeopleProfileImageWorkflow
|
|||
}
|
||||
|
||||
if ($default_phid == null || $is_force || $generate) {
|
||||
$file = id(new PhabricatorFilesComposeAvatarBuiltinFile())
|
||||
->getUserProfileImageFile($username);
|
||||
$user->setDefaultProfileImagePHID($file->getPHID());
|
||||
$user->setDefaultProfileImageVersion($version);
|
||||
$user->save();
|
||||
$console->writeOut(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Generating profile image for "%s".',
|
||||
$username));
|
||||
|
||||
$generator->updateUser($user);
|
||||
} else {
|
||||
$console->writeOut(
|
||||
"%s\n",
|
||||
|
|
|
@ -267,6 +267,10 @@ final class PhabricatorUser
|
|||
return !($this->getPHID() === null);
|
||||
}
|
||||
|
||||
public function saveWithoutIndex() {
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
public function save() {
|
||||
if (!$this->getConduitCertificate()) {
|
||||
$this->setConduitCertificate($this->generateConduitCertificate());
|
||||
|
@ -276,7 +280,7 @@ final class PhabricatorUser
|
|||
$this->setAccountSecret(Filesystem::readRandomCharacters(64));
|
||||
}
|
||||
|
||||
$result = parent::save();
|
||||
$result = $this->saveWithoutIndex();
|
||||
|
||||
if ($this->profile) {
|
||||
$this->profile->save();
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFilesComposeAvatarExample extends PhabricatorUIExample {
|
||||
|
||||
public function getName() {
|
||||
return pht('Avatars');
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return pht('Tests various color palettes and sizes.');
|
||||
}
|
||||
|
||||
public function getCategory() {
|
||||
return pht('Technical');
|
||||
}
|
||||
|
||||
public function renderExample() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$colors = PhabricatorFilesComposeAvatarBuiltinFile::getColorMap();
|
||||
$packs = PhabricatorFilesComposeAvatarBuiltinFile::getImagePackMap();
|
||||
$builtins = PhabricatorFilesComposeAvatarBuiltinFile::getImageMap();
|
||||
$borders = PhabricatorFilesComposeAvatarBuiltinFile::getBorderMap();
|
||||
|
||||
$images = array();
|
||||
foreach ($builtins as $builtin => $raw_file) {
|
||||
$file = PhabricatorFile::loadBuiltin($viewer, $builtin);
|
||||
$images[] = $file->getBestURI();
|
||||
}
|
||||
|
||||
$content = array();
|
||||
shuffle($colors);
|
||||
foreach ($colors as $color) {
|
||||
shuffle($borders);
|
||||
$color_const = hexdec(trim($color, '#'));
|
||||
$border = head($borders);
|
||||
$border_color = implode(', ', $border);
|
||||
|
||||
$styles = array();
|
||||
$styles[] = 'background-color: '.$color.';';
|
||||
$styles[] = 'display: inline-block;';
|
||||
$styles[] = 'height: 42px;';
|
||||
$styles[] = 'width: 42px;';
|
||||
$styles[] = 'border-radius: 3px;';
|
||||
$styles[] = 'border: 4px solid rgba('.$border_color.');';
|
||||
|
||||
shuffle($images);
|
||||
$png = head($images);
|
||||
|
||||
$image = phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $png,
|
||||
'height' => 42,
|
||||
'width' => 42,
|
||||
));
|
||||
|
||||
$tag = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'style' => implode(' ', $styles),
|
||||
),
|
||||
$image);
|
||||
|
||||
$content[] = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'mlr mlb',
|
||||
'style' => 'float: left;',
|
||||
),
|
||||
$tag);
|
||||
}
|
||||
|
||||
$count = new PhutilNumber(
|
||||
count($colors) * count($builtins) * count($borders));
|
||||
|
||||
$infoview = id(new PHUIInfoView())
|
||||
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
|
||||
->appendChild(pht('This installation can generate %s unique '.
|
||||
'avatars. You can add additional image packs in '.
|
||||
'resources/builtins/alphanumeric/.', $count));
|
||||
|
||||
$info = phutil_tag_div('pmb', $infoview);
|
||||
$view = phutil_tag_div('ml', $content);
|
||||
|
||||
return phutil_tag(
|
||||
'div',
|
||||
array(),
|
||||
array(
|
||||
$info,
|
||||
$view,
|
||||
));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue