222 const auto system_time = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
223 file_time - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
224 const std::time_t seconds = std::chrono::system_clock::to_time_t(system_time);
228 if (localtime_r(&seconds, &local) ==
nullptr) {
232 if (std::strftime(buffer,
sizeof(buffer),
"%Y-%m-%d %H:%M:%S", &local) == 0) {
238std::expected<SyncSummary, SyncError>
sync_all(
const fs::path& search_path,
const DbPaths& paths) {
240 const std::string input =
241 std::format(
"search_path={} project_db={} stack_db={}", search_path.string(),
244 std::optional<SyncError> first_error;
247 const auto operation = [&]() -> std::expected<std::monostate, SyncError> {
249 auto library_paths = load_library_paths(paths.
project_db);
250 if (!library_paths) {
251 return std::unexpected(library_paths.error());
253 if (!search_path.empty()) {
254 if (!fs::exists(search_path)) {
257 if (!fs::is_directory(search_path)) {
260 library_paths->insert(normalized_path(search_path).
string());
264 json project_nodes = json::array();
265 json stack_nodes = json::array();
266 std::set<std::string> project_ids;
268 for (
const auto& library_path_string : *library_paths) {
269 const fs::path library_path(library_path_string);
270 if (!fs::exists(library_path) || !fs::is_directory(library_path)) {
276 std::vector<fs::directory_entry> project_files;
279 project_files.push_back(entry);
281 }
catch (
const std::exception&) {
287 std::ranges::sort(project_files, {},
288 [](
const auto& entry) {
return entry.path().lexically_normal().string(); });
290 for (
const auto& entry : project_files) {
293 std::ifstream project_file(entry.path(), std::ios::binary);
295 if (!(project_file >> content) || !content.is_object()) {
303 const std::string project_id = safe_string(content,
"id");
305 if (project_id.empty() || !project_ids.insert(project_id).second) {
312 const std::string project_title = safe_string(content,
"title",
"Untitled");
313 const std::string project_path = normalized_path(entry.path()).string();
317 project_nodes.push_back({
319 {
"title", project_title},
320 {
"path", project_path},
321 {
"library_path", library_path.string()},
324 stack_nodes.push_back({
326 {
"projectid", project_id},
327 {
"title", project_title},
329 {
"project_path", project_path},
330 {
"library_path", library_path.string()},
333 if (!content.contains(
"stacks") || !content[
"stacks"].is_array()) {
337 for (
const auto& stack : content[
"stacks"]) {
338 if (!stack.is_object()) {
341 const std::string stack_id = safe_string(stack,
"id");
342 if (stack_id.empty()) {
345 stack_nodes.push_back({
347 {
"projectid", project_id},
348 {
"title", safe_string(stack,
"title",
"Untitled")},
350 {
"project_path", project_path},
351 {
"library_path", library_path.string()},
354 }
catch (
const std::exception&) {
367 const json project_db = {
368 {
"library_paths", json(*library_paths)},
369 {
"nodes", std::move(project_nodes)},
370 {
"updateTime", now_timestamp()},
372 const json stack_db = {
373 {
"nodes", std::move(stack_nodes)},
374 {
"updateTime", now_timestamp()},
376 if (!atomic_write_json(paths.
project_db, project_db) ||
377 !atomic_write_json(paths.
stack_db, stack_db)) {
382 return std::unexpected(*first_error);
388 const bool ok = operation.has_value();
389 const std::string output =
390 std::format(
"ok={} projects={} stacks={} failed_projects={} error={}", ok,
397 return std::unexpected(operation.error());
接口输入/输出文本强类型。 语义:与 ApiName 一样显式构造,参数顺序在编译期被类型约束。