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"; } }