Mercurial > minori
comparison src/library/library.cc @ 382:0265e125f680
filesystem: implement filesystem watcher
I also ported the library code to use it as well. Once we implement
proper directory watching on Windows (and maybe others) this will
be fairly useful :)
| author | Paper <paper@tflc.us> |
|---|---|
| date | Thu, 06 Nov 2025 03:16:55 -0500 |
| parents | 47c9f8502269 |
| children | 27c462bc7815 |
comparison
equal
deleted
inserted
replaced
| 381:5beae59cf042 | 382:0265e125f680 |
|---|---|
| 11 | 11 |
| 12 #include <iostream> | 12 #include <iostream> |
| 13 | 13 |
| 14 namespace Library { | 14 namespace Library { |
| 15 | 15 |
| 16 Database::Database() | |
| 17 { | |
| 18 /* Do this immediately :) */ | |
| 19 UpdateWatchers(); | |
| 20 } | |
| 21 | |
| 22 void Database::UpdateWatchers() | |
| 23 { | |
| 24 /* TODO also need to remove unused watchers */ | |
| 25 for (const auto &p : session.config.library.paths) { | |
| 26 if (watchers_.count(p)) | |
| 27 continue; | |
| 28 | |
| 29 watchers_[p].reset(Filesystem::GetRecursiveFilesystemWatcher(reinterpret_cast<void *>(this), p, Database::StaticEventHandler)); | |
| 30 } | |
| 31 } | |
| 32 | |
| 33 bool Database::GetPathAnimeAndEpisode(const std::string &basename, int *aid, int *ep) | |
| 34 { | |
| 35 anitomy::Anitomy anitomy; | |
| 36 anitomy.Parse(basename); | |
| 37 | |
| 38 const auto &elements = anitomy.elements(); | |
| 39 | |
| 40 const std::string title = Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle)); | |
| 41 | |
| 42 const int id = Anime::db.LookupAnimeTitle(title); | |
| 43 if (id <= 0 || (find_id_ && find_id_.value() != id)) | |
| 44 return false; | |
| 45 | |
| 46 const int episode = | |
| 47 Strings::ToInt<int>(Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber))); | |
| 48 | |
| 49 *aid = id; | |
| 50 *ep = episode; | |
| 51 return true; | |
| 52 } | |
| 53 | |
| 54 void Database::EventHandler(const std::filesystem::path &path, Filesystem::IWatcher::Event event) | |
| 55 { | |
| 56 std::string bname = path.filename().u8string(); | |
| 57 int aid, ep; | |
| 58 | |
| 59 std::cout << path << '\n'; | |
| 60 | |
| 61 if (!GetPathAnimeAndEpisode(bname, &aid, &ep)) | |
| 62 return; | |
| 63 | |
| 64 switch (event) { | |
| 65 case Filesystem::IWatcher::Created: | |
| 66 items[aid][ep] = path; | |
| 67 break; | |
| 68 case Filesystem::IWatcher::Deleted: | |
| 69 /* kill it off */ | |
| 70 items[aid].erase(ep); | |
| 71 if (items[aid].empty()) | |
| 72 items.erase(aid); | |
| 73 break; | |
| 74 } | |
| 75 } | |
| 76 | |
| 77 void Database::StaticEventHandler(void *opaque, const std::filesystem::path &path, Filesystem::IWatcher::Event event) | |
| 78 { | |
| 79 /* Forward to class function */ | |
| 80 reinterpret_cast<Database *>(opaque)->EventHandler(path, event); | |
| 81 } | |
| 82 | |
| 16 std::optional<std::filesystem::path> Database::GetAnimeFolder(int id) | 83 std::optional<std::filesystem::path> Database::GetAnimeFolder(int id) |
| 17 { | 84 { |
| 18 // this function sucks, but it's the most I can really do for now. | 85 // this function sucks, but it's the most I can really do for now. |
| 19 // | 86 // |
| 20 // in the future the Refresh() function should look for directories | 87 // in the future the Refresh() function should look for directories |
| 25 | 92 |
| 26 for (const auto &[anime_id, episodes] : items) { | 93 for (const auto &[anime_id, episodes] : items) { |
| 27 if (id != anime_id) | 94 if (id != anime_id) |
| 28 continue; | 95 continue; |
| 29 | 96 |
| 30 for (const auto &[episode, path] : episodes) { | 97 for (const auto &[episode, path] : episodes) |
| 31 return path.parent_path(); | 98 return path.parent_path(); |
| 32 break; | |
| 33 } | |
| 34 | 99 |
| 35 break; | 100 break; |
| 36 } | 101 } |
| 37 | 102 |
| 38 return std::nullopt; | 103 return std::nullopt; |
| 39 } | 104 } |
| 40 | 105 |
| 106 /* TODO shove this into a separate thread; currently it blocks */ | |
| 41 void Database::Refresh(std::optional<int> find_id) | 107 void Database::Refresh(std::optional<int> find_id) |
| 42 { | 108 { |
| 43 items.clear(); | 109 find_id_ = find_id; |
| 44 | 110 |
| 45 for (const auto &folder : session.config.library.paths) { | 111 UpdateWatchers(); |
| 46 for (const auto &entry : std::filesystem::recursive_directory_iterator(folder)) { | |
| 47 const std::filesystem::path path = entry.path(); | |
| 48 if (!std::filesystem::is_regular_file(path)) | |
| 49 continue; | |
| 50 | 112 |
| 51 const std::string basename = path.filename().u8string(); | 113 for (const auto &w : watchers_) |
| 52 | 114 w.second->Process(); |
| 53 anitomy::Anitomy anitomy; | |
| 54 anitomy.Parse(basename); | |
| 55 | |
| 56 const auto &elements = anitomy.elements(); | |
| 57 | |
| 58 const std::string title = Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle)); | |
| 59 | |
| 60 const int id = Anime::db.LookupAnimeTitle(title); | |
| 61 if (id <= 0 || (find_id && find_id.value() != id)) | |
| 62 continue; | |
| 63 | |
| 64 const int episode = | |
| 65 Strings::ToInt<int>(Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber))); | |
| 66 | |
| 67 // we have an ID now! | |
| 68 items[id][episode] = path; | |
| 69 } | |
| 70 } | |
| 71 } | 115 } |
| 72 | 116 |
| 73 void Database::Refresh() | 117 void Database::Refresh() |
| 74 { | 118 { |
| 75 Refresh(std::nullopt); | 119 Refresh(std::nullopt); |
