view 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
line wrap: on
line source

#include "library/library.h"
#include "core/anime_db.h"
#include "core/session.h"
#include "core/strings.h"

#include "anitomy/anitomy.h"

#include <filesystem>
#include <string>
#include <unordered_map>

#include <iostream>

namespace Library {

Database::Database()
{
	/* Do this immediately :) */
	UpdateWatchers();
}

void Database::UpdateWatchers()
{
	/* TODO also need to remove unused watchers */
	for (const auto &p : session.config.library.paths) {
		if (watchers_.count(p))
			continue;

		watchers_[p].reset(Filesystem::GetRecursiveFilesystemWatcher(reinterpret_cast<void *>(this), p, Database::StaticEventHandler));
	}
}

bool Database::GetPathAnimeAndEpisode(const std::string &basename, int *aid, int *ep)
{
	anitomy::Anitomy anitomy;
	anitomy.Parse(basename);

	const auto &elements = anitomy.elements();

	const std::string title = Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle));

	const int id = Anime::db.LookupAnimeTitle(title);
	if (id <= 0 || (find_id_ && find_id_.value() != id))
		return false;

	const int episode =
	    Strings::ToInt<int>(Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber)));

	*aid = id;
	*ep = episode;
	return true;
}

void Database::EventHandler(const std::filesystem::path &path, Filesystem::IWatcher::Event event)
{
	std::string bname = path.filename().u8string();
	int aid, ep;

	std::cout << path << '\n';

	if (!GetPathAnimeAndEpisode(bname, &aid, &ep))
		return;

	switch (event) {
	case Filesystem::IWatcher::Created:
		items[aid][ep] = path;
		break;
	case Filesystem::IWatcher::Deleted:
		/* kill it off */
		items[aid].erase(ep);
		if (items[aid].empty())
			items.erase(aid);
		break;
	}
}

void Database::StaticEventHandler(void *opaque, const std::filesystem::path &path, Filesystem::IWatcher::Event event)
{
	/* Forward to class function */
	reinterpret_cast<Database *>(opaque)->EventHandler(path, event);
}

std::optional<std::filesystem::path> Database::GetAnimeFolder(int id)
{
	// this function sucks, but it's the most I can really do for now.
	//
	// in the future the Refresh() function should look for directories
	// as well that fit the anime name and *also* have episodes in them.
	// it should give each of these directories a rating by how many
	// episodes are contained in them. whichever directory has more episodes
	// wins, or the first found if there is an equal amount.

	for (const auto &[anime_id, episodes] : items) {
		if (id != anime_id)
			continue;

		for (const auto &[episode, path] : episodes)
			return path.parent_path();

		break;
	}

	return std::nullopt;
}

/* TODO shove this into a separate thread; currently it blocks */
void Database::Refresh(std::optional<int> find_id)
{
	find_id_ = find_id;

	UpdateWatchers();

	for (const auto &w : watchers_)
		w.second->Process();
}

void Database::Refresh()
{
	Refresh(std::nullopt);
}

void Database::Refresh(int id)
{
	Refresh(std::optional<int>(id));
}

// TODO export to JSON

Database db;

} // namespace Library