Skip to content

Instantly share code, notes, and snippets.

@andrewmackrodt
Last active March 3, 2020 17:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewmackrodt/8040695 to your computer and use it in GitHub Desktop.
Save andrewmackrodt/8040695 to your computer and use it in GitHub Desktop.
Download, extract and symlink the latest version of PhpStorm on Linux.
#!/usr/bin/env php
<?php
/**
* $extractPath must be writable by the user account used to launch this script
*/
$extractPath = '/opt/.phpstorm';
$versionPage = 'https://data.services.jetbrains.com/products/releases?code=PS&latest=true&type=release&_=' . time();
#region Functions
// get the page text - uses a cached copy if retrieved within the last 5 minutes
$getPage = function ($url) {
$file = sys_get_temp_dir() . '/phpstorm-' . dechex(crc32($url)) . '.html';
$splFileInfo = new \SplFileInfo($file);
$modified = $splFileInfo->isFile() ? $splFileInfo->getMTime() : 0;
if (time() - $modified > 300) {
file_put_contents($file, file_get_contents($url));
}
return file_get_contents($file);
};
$getInstalledVersion = function () use ($extractPath) {
$version = null;
$file = "$extractPath/default/build.txt";
if (file_exists($file)) {
$version = preg_replace('/^PS-/', '', trim(file_get_contents($file)));
}
return $version;
};
$getJson = function ($url) use ($getPage) {
if (($json = json_decode($getPage($url), true)) === false) {
fwrite(STDERR, "Failed to decode JSON\n");
exit(1);
}
return $json;
};
$getJsonProperty = function ($url, $property) use ($getJson) {
$json = $getJson($url);
foreach (explode('.', $property) as $key) {
if (!array_key_exists($key, $json)) {
fwrite(STDERR, "Invalid JSON property\n");
exit(1);
}
$json = $json[$key];
}
return $json;
};
$getLatestVersion = function () use ($getJsonProperty, $versionPage) {
return $getJsonProperty($versionPage, 'PS.0.build');
};
$getDownloadLink = function () use ($getJsonProperty, $versionPage) {
return $getJsonProperty($versionPage, 'PS.0.downloads.linux.link');
};
$downloadFile = function ($url, $destination, $callback = null, &$info = array()) {
$hnd = fopen($destination, 'wb');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FILE, $hnd);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
if ($callback) {
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $bytes) use ($callback, $hnd) {
return call_user_func($callback, $ch, $bytes, $hnd);
});
}
curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
fclose($hnd);
if ($info['http_code'] === 200) {
$filesize = filesize($destination);
} else {
if (file_exists($destination)) {
unlink($destination);
}
$filesize = -1;
}
return $filesize;
};
$downloadFileWithProgress = function (
$url,
$destination,
$progressCallback = null,
$interval = 100,
&$info = array()) use ($downloadFile) {
if (function_exists('pcntl_fork')) {
$key = ftok(__FILE__, 't');
$shm = @shm_attach($key, 102400, 0640);
} else {
$key = null;
$shm = null;
}
if ($shm) {
shm_put_var($shm, 0, null); // waiting for the child to finish
shm_put_var($shm, 1, -1); // the filesize of the remote target
shm_put_var($shm, 2, -1); // the number or bytes written
$pid = pcntl_fork();
if ($pid === 0) { // child fork
$pid = posix_getpid();
try {
// perform a HEAD request to get the size of the remote file
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
$info = curl_getinfo($ch);
$contentLength = $info['download_content_length'];
shm_put_var($shm, 1, $contentLength);
// start the download
$written = -1;
$downloadFile($url, $destination, function ($ch, $bytes, $hnd) use ($shm, &$written) {
$length = fwrite($hnd, $bytes);
$written = $written ? $written + $length : $length;
shm_put_var($shm, 2, $written);
return $length;
}, $info);
shm_put_var($shm, 2, $written);
} catch (Exception $e) {
print_r($e);
}
shm_put_var($shm, 0, $info); // child finished
posix_kill($pid, SIGKILL); // kill the child
}
$last = -1;
while (($info = shm_get_var($shm, 0)) === null) {
if ($progressCallback) {
$total = shm_get_var($shm, 1);
$written = shm_get_var($shm, 2);
if ($written !== $last) {
call_user_func($progressCallback, $written, $total, $destination);
$last = $written;
}
}
usleep($interval * 1000);
}
$total = shm_get_var($shm, 1);
$written = shm_get_var($shm, 2);
shm_remove($shm);
if ($progressCallback) {
call_user_func($progressCallback, $written, $total, $destination);
}
} else {
// synchronous fallback if pcntl is not available or unable to allocate shared memory
$written = $downloadFile($url, $destination, null, $info);
}
return $written;
};
$extract = function ($archive, $destination) {
$p = new PharData($archive);
$success = $p->extractTo($destination);
return $success;
};
$do = function ($function, $args, $text, $error) {
fwrite(STDOUT, "$text\n");
if (!call_user_func_array($function, $args)) {
fwrite(STDERR, "$error\n");
exit(1);
}
};
#endregion
$installedVersion = $getInstalledVersion() ?: 'n/a';
fwrite(STDOUT, "Detecting installed version: $installedVersion\n");
fwrite(STDOUT, "Detecting latest version: ");
$latestVersion = $getLatestVersion();
fwrite(STDOUT, "$latestVersion\n");
if (version_compare($latestVersion, $installedVersion) !== 1) {
fwrite(STDOUT, "No action required\n");
exit(0);
}
fwrite(STDOUT, "Update required\n");
$downloadName = "PhpStorm-$latestVersion.tar.gz";
$downloadPath = getcwd() . "/$downloadName";
if (!file_exists($downloadPath) || filesize($downloadPath) === 0) {
fwrite(STDOUT, "Finding the download link: ");
$downloadLink = $getDownloadLink();
fwrite(STDOUT, "$downloadLink\n");
fwrite(STDOUT, "Downloading target: '$downloadPath'\n");
// download the file and display a progress indicator
$info = array();
$downloadFileWithProgress($downloadLink, $downloadPath, function ($written, $total, $path) {
$pct = number_format(100 * ($written / $total), 2);
$written = number_format($written / 1024, 0);
$total = number_format($total / 1024, 0);
fwrite(STDOUT, "\r$written/$total kB [$pct%]");
}, 100, $info);
$bytes = number_format($info['size_download'] / 1024, 0);
$time = number_format($info['total_time'], 2);
fwrite(STDOUT, "\rDownloaded $bytes kB in $time seconds\n");
} else {
fwrite(STDOUT, "The file '$downloadName' exists skipping download\n");
}
$temp = "$extractPath/" . substr($downloadName, 0, -7);
$dest = "$extractPath/$latestVersion";
$link = "$extractPath/default";
foreach (array($dest, $temp) as $path) {
if (is_dir($path) || !is_writable($extractPath)) {
fwrite(STDERR, "Cannot create folder '$path'\n");
exit(1);
}
}
$do($extract, array($downloadPath, $extractPath),
"Extracting archive to '$temp'",
"Failed to extract the archive");
$do('rename', array($temp, $dest),
"Renaming '$temp' -> '$dest'",
"Failed to rename the directory");
if (is_link($link)) {
fwrite(STDOUT, "Removing existing symlink '$link'\n");
unlink($link);
}
$do('symlink', array($dest, $link),
"Creating symlink '$link' -> '$dest'",
"Failed to create the symlink");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment