|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Command Controller
|
|
|
|
*/
|
|
|
|
require_once __DIR__ . '/../../../lib/DirScanner.php';
|
|
|
|
require_once __DIR__ . '/../../../plugins/Parsedown.php';
|
|
|
|
require_once __DIR__ . '/../../../plugins/Common.php';
|
|
|
|
|
|
|
|
Class CommandController extends Controller {
|
|
|
|
protected $logPrefix = '[MainBot]';
|
|
|
|
protected $scanedDirCacheKey = 'MainBotScanedDirs';
|
|
|
|
protected $dateIndexCacheKey = 'MainBotDateIndex'; //索引数据的key单独缓存,缓存key为此{cacheKey}_keys
|
|
|
|
protected $noOriginalCtimeFilesCacheKey = 'MainBotNoOriginalCtimeFiles';
|
|
|
|
protected $allFilesCacheKey = 'MainBotAllFiles';
|
|
|
|
|
|
|
|
public function actionIndex() {
|
|
|
|
$commands = <<<eof
|
|
|
|
Actions:
|
|
|
|
- config [do=get&key=theme] //获取或修改系统配置
|
|
|
|
- mainBot //扫描机器人程序
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
php command.php action parameters
|
|
|
|
|
|
|
|
|
|
|
|
eof;
|
|
|
|
echo $commands;
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
//查看、修改/runtime/custom_config.json里的内容
|
|
|
|
public function actionConfig() {
|
|
|
|
$themeName = FSC::$app['config']['theme'];
|
|
|
|
|
|
|
|
$code = 1;
|
|
|
|
$data = '';
|
|
|
|
|
|
|
|
//修改配置文件
|
|
|
|
$param_do = $this->get('do', 'set'); //支持:set, get, all, del
|
|
|
|
$param_key = $this->get('key', '');
|
|
|
|
$param_value = $this->get('val', '');
|
|
|
|
|
|
|
|
if ($param_do == 'set' && empty($param_value)) {
|
|
|
|
throw new Exception("缺少val参数!", 403);
|
|
|
|
}else if (in_array($param_do, array('set', 'get', 'del')) && empty($param_key)) {
|
|
|
|
throw new Exception("缺少key参数!", 403);
|
|
|
|
}
|
|
|
|
|
|
|
|
//val数据格式转换
|
|
|
|
if ($param_value === 'false') {
|
|
|
|
$param_value = false;
|
|
|
|
}else if ($param_value === 'true') {
|
|
|
|
$param_value = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$config_file = __DIR__ . "/../../../runtime/custom_config.json";
|
|
|
|
if (file_exists($config_file)) {
|
|
|
|
$content = file_get_contents($config_file);
|
|
|
|
$configs = @json_decode($content, true);
|
|
|
|
if (empty($configs)) {
|
|
|
|
$config_file_template = __DIR__ . "/../../../conf/custom_config_{$themeName}.json";
|
|
|
|
$content = file_get_contents($config_file_template);
|
|
|
|
$configs = @json_decode($content, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!empty($configs)) {
|
|
|
|
switch($param_do) {
|
|
|
|
case 'set':
|
|
|
|
$configs[$param_key] = $param_value;
|
|
|
|
file_put_contents($config_file, json_encode($configs, JSON_PRETTY_PRINT));
|
|
|
|
$data = $configs;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'get':
|
|
|
|
$data = !empty($configs[$param_key]) ? $configs[$param_key] : '';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'del':
|
|
|
|
unset($configs[$param_key]);
|
|
|
|
file_put_contents($config_file, json_encode($configs, JSON_PRETTY_PRINT));
|
|
|
|
$data = $configs;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'all':
|
|
|
|
default:
|
|
|
|
$data = $configs;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$res = compact('code', 'data');
|
|
|
|
|
|
|
|
echo "命令参数:\n";
|
|
|
|
print_r($this->get());
|
|
|
|
echo "\n";
|
|
|
|
echo "命令执行结果:\n";
|
|
|
|
print_r($res);
|
|
|
|
echo "\n\n";
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//服务器端机器人程序
|
|
|
|
/**
|
|
|
|
* 扫描照片目录里所有子目录和文件
|
|
|
|
* 建立年份、月份索引数据
|
|
|
|
* 汇总每个目录下的照片、视频、MP3音乐文件数量
|
|
|
|
* 记录没有original_ctime的文件,把它们单独归类
|
|
|
|
*/
|
|
|
|
public function actionMainBot() {
|
|
|
|
$thisTime = date('Y-m-d H:i:s');
|
|
|
|
$botLogPrefix = $this->logPrefix;
|
|
|
|
echo "{$botLogPrefix} Main bot started @{$thisTime}\n";
|
|
|
|
|
|
|
|
|
|
|
|
$menus = array(); //菜单,一级目录
|
|
|
|
$htmlReadme = array(); //Readme.md 内容,底部网站详细介绍
|
|
|
|
$htmlCateReadme = ''; //当前目录下的Readme.md 内容
|
|
|
|
$menus_sorted = array(); //Readme_sort.txt 说明文件内容,一级目录菜单从上到下的排序
|
|
|
|
|
|
|
|
|
|
|
|
//执行一次扫描任务
|
|
|
|
$this->cleanScanCaches();
|
|
|
|
$this->scanMediaFiles();
|
|
|
|
$this->saveDateIndexIntoCacheFile();
|
|
|
|
$this->saveNoOriginalCtimeFilesIntoFile();
|
|
|
|
//缓存所有文件id跟文件信息,便于根据id列表来渲染,并按id首字母分子目录存放,以支持大量文件的场景
|
|
|
|
$this->saveAllFilesIntoCacheFile();
|
|
|
|
|
|
|
|
//while (true) {
|
|
|
|
//$time = date('Y-m-d H:i:s');
|
|
|
|
//echo "{$botLogPrefix} {$time}\n";
|
|
|
|
|
|
|
|
//sleep(3);
|
|
|
|
//}
|
|
|
|
}
|
|
|
|
|
|
|
|
//清空内存中的临时缓存数据
|
|
|
|
protected function cleanScanCaches() {
|
|
|
|
Common::setCache($this->scanedDirCacheKey, array());
|
|
|
|
Common::setCache($this->dateIndexCacheKey, array());
|
|
|
|
Common::setCache($this->noOriginalCtimeFilesCacheKey, array());
|
|
|
|
}
|
|
|
|
|
|
|
|
//扫描媒体文件:图片、视频、音乐
|
|
|
|
//TODO: 把它们按年份、月份归类,并缓存到/runtime/cache/目录,方便前端展示读取
|
|
|
|
//把当前扫描进度保存到单独的缓存文件,便于用户随时获取
|
|
|
|
//TODO: 没有original_ctime的视频文件调用exiftool获取拍摄时间
|
|
|
|
protected function scanMediaFiles($dirpath = '') {
|
|
|
|
$rootDir = __DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'];
|
|
|
|
if (empty($dirpath)) {
|
|
|
|
$dirpath = $rootDir;
|
|
|
|
}
|
|
|
|
|
|
|
|
echo "\n\n== Scanning directory {$dirpath} ...\n";
|
|
|
|
$scanner = new DirScanner();
|
|
|
|
$scanner->setWebRoot(FSC::$app['config']['content_directory']);
|
|
|
|
$scanner->setRootDir($dirpath);
|
|
|
|
|
|
|
|
//尝试使用exiftool来获取视频的拍摄时间
|
|
|
|
$scanner->exiftoolSupported = true;
|
|
|
|
|
|
|
|
$maxScanDeep = 0; //最大扫描目录级数
|
|
|
|
$dirTree = $scanner->scan($dirpath, $maxScanDeep);
|
|
|
|
$scanResults = $scanner->getScanResults();
|
|
|
|
echo 'Total directories or files: ' . count($scanResults);
|
|
|
|
echo "\n";
|
|
|
|
|
|
|
|
$supportedImageExts = FSC::$app['config']['supportedImageExts'];
|
|
|
|
$supportedVideoExts = FSC::$app['config']['supportedVideoExts'];
|
|
|
|
$supportedAudioExts = FSC::$app['config']['supportedAudioExts'];
|
|
|
|
$cacheKey = $this->scanedDirCacheKey;
|
|
|
|
|
|
|
|
if (!empty($scanResults)) {
|
|
|
|
$scanIndex = 0;
|
|
|
|
$scanTotal = count($scanResults);
|
|
|
|
|
|
|
|
foreach ($scanResults as $id => $item) {
|
|
|
|
$hadScanedDirs = Common::getCache($cacheKey);
|
|
|
|
|
|
|
|
if (
|
|
|
|
!empty($item['filename'])
|
|
|
|
&& empty($item['original_ctime'])
|
|
|
|
&& in_array($item['extension'], $supportedImageExts)
|
|
|
|
&& !in_array($item['extension'], $scanner->exifSupportFileExtensions)
|
|
|
|
) {
|
|
|
|
echo "Image file no original_ctime: {$item['filename']}.{$item['extension']}, {$item['realpath']}\n";
|
|
|
|
}else if (
|
|
|
|
!empty($item['filename'])
|
|
|
|
&& empty($item['original_ctime'])
|
|
|
|
&& (in_array($item['extension'], $supportedVideoExts) || in_array($item['extension'], $supportedAudioExts))
|
|
|
|
) {
|
|
|
|
echo "Video or audio file no original_ctime: {$item['filename']}.{$item['extension']}, {$item['realpath']}\n";
|
|
|
|
}else if (!empty($item['directory']) && empty($hadScanedDirs[$id])) { //if it's directory
|
|
|
|
$hadScanedDirs[$id] = true;
|
|
|
|
Common::setCache($cacheKey, $hadScanedDirs);
|
|
|
|
|
|
|
|
$this->scanMediaFiles($item['realpath']);
|
|
|
|
}
|
|
|
|
|
|
|
|
$scanIndex ++;
|
|
|
|
|
|
|
|
if (!empty($item['filename'])) {
|
|
|
|
//保存所有文件到索引
|
|
|
|
$this->updateAllFilesCache($item);
|
|
|
|
//更新年份、月份时间索引
|
|
|
|
$this->updateDateIndex($item);
|
|
|
|
//更新没有拍摄时间的文件索引
|
|
|
|
$this->updateNoOriginalCtimeFiles($item);
|
|
|
|
}
|
|
|
|
|
|
|
|
//更新扫描进度
|
|
|
|
$stats = $this->updateScanStats($dirpath, $scanTotal, $scanIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
sleep(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//更新扫描进度
|
|
|
|
protected function updateScanStats($dirpath, $total, $index) {
|
|
|
|
if (empty($total)) {return false;}
|
|
|
|
|
|
|
|
$stats = array(
|
|
|
|
'currentDir' => $dirpath,
|
|
|
|
'total' => $total,
|
|
|
|
'current' => $index,
|
|
|
|
'percent' => floor($index*100/$total),
|
|
|
|
);
|
|
|
|
|
|
|
|
$botLogPrefix = $this->logPrefix;
|
|
|
|
$cacheDir = __DIR__ . '/../../../runtime/cache/';
|
|
|
|
$statsFile = "{$cacheDir}stats_scan.json";
|
|
|
|
if (!is_dir($cacheDir)) {
|
|
|
|
mkdir($cacheDir, 0777, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
$rootDir = __DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'];
|
|
|
|
if ($dirpath == $rootDir) {
|
|
|
|
echo "{$botLogPrefix} Scan has finished {$stats['percent']}%, total {$stats['total']}, current {$stats['current']}\n";
|
|
|
|
|
|
|
|
//保存进度文件
|
|
|
|
file_put_contents($statsFile, json_encode($stats) . "\n");
|
|
|
|
}else if (file_exists($statsFile)) { //更新当前扫描目录
|
|
|
|
$json = file_get_contents($statsFile);
|
|
|
|
if (!empty($json)) {
|
|
|
|
$jsonData = json_decode(trim($json), true);
|
|
|
|
if ($jsonData['currentDir'] != $dirpath) {
|
|
|
|
$jsonData['currentDir'] = $dirpath;
|
|
|
|
$json = json_encode($jsonData) . "\n";
|
|
|
|
file_put_contents($statsFile, $json);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stats;
|
|
|
|
}
|
|
|
|
|
|
|
|
//建立年份、月份时间索引
|
|
|
|
/**
|
|
|
|
* 数据格式:
|
|
|
|
* {"y2024": {"m1": [id1, id2, ...], "m10": [id1, id2, ...]}}
|
|
|
|
*/
|
|
|
|
protected function updateDateIndex($file) {
|
|
|
|
$ctime = !empty($file['original_ctime']) ? $file['original_ctime'] : $file['fstat']['ctime'];
|
|
|
|
|
|
|
|
$cacheKey = $this->dateIndexCacheKey;
|
|
|
|
$cacheData = Common::getCache($cacheKey);
|
|
|
|
if (empty($cacheData)) {
|
|
|
|
$cacheData = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$year = 'y' . date('Y', $ctime);
|
|
|
|
$month = 'm' . date('m', $ctime);
|
|
|
|
if (empty($cacheData[$year])) {
|
|
|
|
$cacheData[$year] = array();
|
|
|
|
}
|
|
|
|
if (empty($cacheData[$year][$month])) {
|
|
|
|
$cacheData[$year][$month] = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_array($file['id'], $cacheData[$year][$month])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
array_push($cacheData[$year][$month], $file['id']);
|
|
|
|
|
|
|
|
return Common::setCache($cacheKey, $cacheData);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function saveDateIndexIntoCacheFile() {
|
|
|
|
$cacheKey = $this->dateIndexCacheKey;
|
|
|
|
$cacheData = Common::getCache($cacheKey);
|
|
|
|
if (empty($cacheData)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cacheDir = 'index';
|
|
|
|
|
|
|
|
//save index keys
|
|
|
|
$indexKeys = [];
|
|
|
|
foreach ($cacheData as $year => $item) {
|
|
|
|
$indexKeys[$year] = array_keys($item);
|
|
|
|
}
|
|
|
|
Common::saveCacheToFile("{$cacheKey}_keys", $indexKeys, $cacheDir);
|
|
|
|
|
|
|
|
return Common::saveCacheToFile($cacheKey, $cacheData, $cacheDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function updateAllFilesCache($file) {
|
|
|
|
$cacheKey = $this->allFilesCacheKey;
|
|
|
|
$cacheData = Common::getCache($cacheKey);
|
|
|
|
if (empty($cacheData)) {
|
|
|
|
$cacheData = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$cacheData[$file['id']] = $file;
|
|
|
|
return Common::setCache($cacheKey, $cacheData);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getFilesByFirstCharcter($files, $dirNum) {
|
|
|
|
$byFirst = [];
|
|
|
|
|
|
|
|
foreach ($files as $id => $item) {
|
|
|
|
$firstChar = substr($id, 0, 1);
|
|
|
|
$ascNum = ord($firstChar);
|
|
|
|
$index = $ascNum % $dirNum;
|
|
|
|
if (empty($byFirst[$index])) {
|
|
|
|
$byFirst[$index] = [];
|
|
|
|
}
|
|
|
|
$byFirst[$index][$id] = $item;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $byFirst;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function saveAllFilesIntoCacheFile() {
|
|
|
|
$cacheKey = $this->allFilesCacheKey;
|
|
|
|
$cacheData = Common::getCache($cacheKey);
|
|
|
|
if (empty($cacheData)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$total = count($cacheData);
|
|
|
|
$dirNum = 1;
|
|
|
|
|
|
|
|
if ($total > 1000 && $total <= 10000) {
|
|
|
|
$dirNum = 10;
|
|
|
|
}else if ($total > 10000){
|
|
|
|
$dirNum = 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
$filesByFirstChar = $this->getFilesByFirstCharcter($cacheData, $dirNum);
|
|
|
|
$cacheDir = 'index';
|
|
|
|
for ($i=1;$i<=$dirNum;$i++) {
|
|
|
|
Common::saveCacheToFile("{$cacheKey}_{$i}", $filesByFirstChar[$i-1], $cacheDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//汇总每个目录下图片、视频、音乐文件数量
|
|
|
|
/**
|
|
|
|
* 数据格式:
|
|
|
|
* {dirid: {image: 10, video: 20, audio: 0}, ...}
|
|
|
|
*/
|
|
|
|
protected function updateDirCounter() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//归类没有original_ctime的图片、视频文件
|
|
|
|
/**
|
|
|
|
* 数据格式:
|
|
|
|
* [id1, id2, ...]
|
|
|
|
*/
|
|
|
|
protected function updateNoOriginalCtimeFiles($file) {
|
|
|
|
if (!empty($file['original_ctime'])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cacheKey = $this->noOriginalCtimeFilesCacheKey;
|
|
|
|
$cacheData = Common::getCache($cacheKey);
|
|
|
|
if (empty($cacheData)) {
|
|
|
|
$cacheData = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_array($file['id'], $cacheData)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
array_push($cacheData, $file['id']);
|
|
|
|
|
|
|
|
return Common::setCache($cacheKey, $cacheData);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function saveNoOriginalCtimeFilesIntoFile() {
|
|
|
|
$cacheKey = $this->noOriginalCtimeFilesCacheKey;
|
|
|
|
$cacheData = Common::getCache($cacheKey);
|
|
|
|
if (empty($cacheData)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cacheDir = 'index';
|
|
|
|
return Common::saveCacheToFile($cacheKey, $cacheData, $cacheDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function actionTest() {
|
|
|
|
$cacheKey = 'TestSTData';
|
|
|
|
$time = Common::getCache($cacheKey);
|
|
|
|
if (empty($time)) {
|
|
|
|
$time = date('Y-m-d H:i:s');
|
|
|
|
Common::setCache($cacheKey, $time);
|
|
|
|
}
|
|
|
|
|
|
|
|
echo "Cache time {$time}\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|