Source code of filesite.io.
https://filesite.io
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
460 lines
15 KiB
460 lines
15 KiB
<?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(); |
|
|
|
//TODO: 保存所有目录下文件数量统计 |
|
//按年、月保存文件数据,以便按年、月显示 |
|
|
|
//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()); |
|
Common::setCache($this->allFilesCacheKey, array()); |
|
} |
|
|
|
//扫描媒体文件:图片、视频、音乐 |
|
//TODO: 把它们按年份、月份归类,并缓存到/runtime/cache/目录,方便前端展示读取 |
|
//把当前扫描进度保存到单独的缓存文件,便于用户随时获取 |
|
//TODO: 没有original_ctime的视频文件调用exiftool获取拍摄时间 |
|
protected function scanMediaFiles($dirpath = '') { |
|
$rootDir = __DIR__ . '/../../../www/' . FSC::$app['config']['content_directory']; |
|
if (empty($dirpath)) { |
|
$dirpath = realpath($rootDir); |
|
} |
|
|
|
echo "\n\n== Scanning directory {$dirpath} ...\n"; |
|
$scanner = new DirScanner(); |
|
$scanner->setWebRoot($this->getCurrentWebroot($dirpath)); |
|
$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'])) { |
|
//保存所有文件到索引 |
|
$this->updateAllFilesCache($item); |
|
//更新年份、月份时间索引 |
|
$this->updateDateIndex($item); |
|
//更新没有拍摄时间的文件索引 |
|
$this->updateNoOriginalCtimeFiles($item); |
|
} |
|
|
|
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 ++; |
|
$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), |
|
'status' => 'running', |
|
); |
|
|
|
$botLogPrefix = $this->logPrefix; |
|
$cacheDir = __DIR__ . '/../../../runtime/cache/'; |
|
$statsFile = "{$cacheDir}stats_scan.json"; |
|
if (!is_dir($cacheDir)) { |
|
mkdir($cacheDir, 0777, true); |
|
} |
|
|
|
$rootDir = realpath(__DIR__ . '/../../../www/' . FSC::$app['config']['content_directory']); |
|
if ($dirpath == $rootDir) { |
|
if ($index == $total) { |
|
$stats['status'] = 'finished'; |
|
} |
|
|
|
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'] : Common::getFileCreateTime($file); |
|
|
|
$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 saveAllFilesGroupedByDate($dateIndexes) { |
|
$cacheKey = $this->allFilesCacheKey; |
|
$cacheData = Common::getCache($cacheKey); |
|
if (empty($cacheData)) { |
|
return false; |
|
} |
|
|
|
$dateCacheKey = $this->dateIndexCacheKey; |
|
$cacheDir = 'index'; |
|
foreach($dateIndexes as $year => $fileIdsInMonth) { |
|
$tmpData = []; |
|
foreach($fileIdsInMonth as $month => $ids) { |
|
$tmpData[$month] = []; |
|
foreach($ids as $id) { |
|
$tmpData[$month][$id] = $cacheData[$id]; |
|
} |
|
} |
|
|
|
Common::saveCacheToFile("{$dateCacheKey}_{$year}", $tmpData, $cacheDir); |
|
} |
|
} |
|
|
|
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); |
|
|
|
//按年、月保存文件数据缓存,以便前端展示 |
|
$this->saveAllFilesGroupedByDate($cacheData); |
|
|
|
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); |
|
} |
|
|
|
//根据文件id首字母以及需要分批存储的数量,来对文件进行分组 |
|
protected function getFilesByFirstCharcter($files, $dirNum) { |
|
$byFirst = []; |
|
|
|
foreach ($files as $id => $item) { |
|
$index = Common::getIndexNumByFileId($id, $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); |
|
} |
|
|
|
//保存文件总数,以及分批数量,以便前端根据id来索引数据 |
|
$statsData = [ |
|
'filetotal' => $total, |
|
'dirnum' => $dirNum, |
|
]; |
|
Common::saveCacheToFile("{$cacheKey}_stats", $statsData, $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"; |
|
} |
|
|
|
}
|
|
|