view src/core/filesystem.cc @ 382:0265e125f680 default tip

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 5912dafc6e28
children
line wrap: on
line source

#include "core/filesystem.h"
#include "core/config.h"
#include "core/strings.h"

#include <QStandardPaths>

#include <filesystem>

namespace Filesystem {

/* this runs fs::create_directories() on the
   PARENT directory. */
void CreateDirectories(const std::filesystem::path &path)
{
	if (path.empty())
		return;

	const auto &parent = path.parent_path();
	if (!std::filesystem::exists(parent))
		std::filesystem::create_directories(parent);
}

std::filesystem::path GetDotPath()
{
	/*
	 * Windows: ~/AppData/Roaming/Minori
	 * macOS: ~/Library/Application Support/Minori
	 * ...: ~/.config/minori
	 *
	 * FIXME: are windows and mac properly cased?
	 */
#ifdef WIN32
	return Strings::ToUtf8String(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
#else
	return Strings::ToUtf8String(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
#endif
}

std::filesystem::path GetConfigPath()
{
	return GetDotPath() / CONFIG_NAME;
}

std::filesystem::path GetAnimeDBPath()
{
	return GetDotPath() / "anime" / "db.json";
}

std::filesystem::path GetTorrentsPath()
{
	return GetDotPath() / "torrents";
}

std::filesystem::path GetAnimePostersPath()
{
	return GetDotPath() / "anime" / "posters";
}

/* ------------------------------------------------------------------------ */
/* Ehhhhh */

class Watcher : public IWatcher {
public:
	Watcher(void *opaque, const std::filesystem::path &path, EventHandler handler)
		: path_(path)
		, handler_(handler)
		, opaque_(opaque)
	{
	}

	virtual ~Watcher() override
	{
	}

protected:
	std::filesystem::path path_;
	EventHandler handler_;
	void *opaque_;
};

/* ------------------------------------------------------------------------ */
/* Non-recursive filesystem watcher.
 * This is the portable version for non-Windows */

template<typename T>
class StdFilesystemWatcher : public Watcher {
public:
	StdFilesystemWatcher(void *opaque, const std::filesystem::path &path, EventHandler handler)
		: Watcher(opaque, path, handler)
	{
	}

	virtual ~StdFilesystemWatcher() override
	{
	}

	virtual void Process() override
	{
		/* Untoggle all paths. This allows us to only ever
		 * iterate over the directory ONCE. */
		UntoggleAllPaths();

		for (const auto &item : T(path_)) {
			std::filesystem::path p = item.path();

			if (FindAndTogglePath(p))
				continue;

			/* Hand the path off to the listener */
			handler_(opaque_, p, Event::Created);
			paths_.push_back({true, p});
		}

		DeleteUntoggledPaths();
	}

protected:
	bool FindAndTogglePath(const std::filesystem::path &p) {
		for (auto &pp : paths_) {
			if (pp.path == p) {
				pp.found = true;
				return true;
			}
		}

		return false;
	}

	void UntoggleAllPaths()
	{
		for (auto &pp : paths_)
			pp.found = false;
	}

	void DeleteUntoggledPaths()
	{
		for (const auto &path : paths_)
			if (!path.found)
				handler_(opaque_, path.path, Event::Deleted);

		auto it = paths_.begin();

		while (it != paths_.end()) {
			if (!it->found) {
				it = paths_.erase(it);
			} else {
				it++;
			}
		}
	}

	struct PathStatus {
		bool found;
		std::filesystem::path path;
	};

	/* TODO this is probably DAMN slow */
	std::vector<PathStatus> paths_;
};

IWatcher *GetRecursiveFilesystemWatcher(void *opaque,
	const std::filesystem::path &path, IWatcher::EventHandler handler)
{
	/* .... :) */
	return new StdFilesystemWatcher<std::filesystem::recursive_directory_iterator>(opaque, path, handler);
}

IWatcher *GetFilesystemWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler)
{
	return new StdFilesystemWatcher<std::filesystem::directory_iterator>(opaque, path, handler);
}

} // namespace Filesystem