2021-08-18 07:36:31 -04:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
#include "Pch.h"
|
|
|
|
|
#include "Store.h"
|
2023-03-28 09:47:42 -04:00
|
|
|
#include "StoreSettings.h"
|
2021-08-18 07:36:31 -04:00
|
|
|
|
2022-03-21 09:36:32 -04:00
|
|
|
#if TS_USING(TS_PLATFORM_LINUX)
|
|
|
|
|
# include <sys/inotify.h>
|
|
|
|
|
#endif
|
|
|
|
|
#if TS_USING(TS_PLATFORM_MAC)
|
|
|
|
|
# include <CoreServices/CoreServices.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2023-05-26 05:04:29 -04:00
|
|
|
#ifndef TS_DEBUG_FS_EVENTS
|
|
|
|
|
#define TS_DEBUG_FS_EVENTS TS_OFF
|
2023-04-24 10:02:27 -04:00
|
|
|
#endif
|
2022-03-21 09:36:32 -04:00
|
|
|
|
2021-08-18 10:20:33 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Pre-C++20 there is now way to convert between clocks. C++20 onwards it is
|
|
|
|
|
// possible to create a clock with an Unreal epoch and convert file times to it.
|
|
|
|
|
// But at time of writing, C++20 isn't complete enough across the board.
|
|
|
|
|
static const uint64 UnrealEpochYear = 1;
|
|
|
|
|
#if TS_USING(TS_PLATFORM_WINDOWS)
|
|
|
|
|
static const uint64 FsEpochYear = 1601;
|
|
|
|
|
#else
|
|
|
|
|
static const uint64 FsEpochYear = 1970;
|
|
|
|
|
#endif
|
|
|
|
|
static int64 FsToUnrealEpochBiasSeconds = uint64(double(FsEpochYear - UnrealEpochYear) * 365.2425) * 86400;
|
|
|
|
|
|
2021-08-18 07:36:31 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
#if TS_USING(TS_PLATFORM_WINDOWS)
|
2023-03-28 09:47:42 -04:00
|
|
|
class FStore::FMount::FDirWatcher
|
2021-08-18 07:36:31 -04:00
|
|
|
: public asio::windows::object_handle
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
using asio::windows::object_handle::object_handle;
|
|
|
|
|
};
|
2023-01-09 03:48:09 -05:00
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-03-21 09:36:32 -04:00
|
|
|
#elif TS_USING(TS_PLATFORM_LINUX)
|
2023-03-28 09:47:42 -04:00
|
|
|
class FStore::FMount::FDirWatcher
|
2022-03-21 09:36:32 -04:00
|
|
|
: public asio::posix::stream_descriptor
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
typedef std::function<void(asio::error_code error)> HandlerType;
|
2021-08-18 07:36:31 -04:00
|
|
|
public:
|
2022-03-21 09:36:32 -04:00
|
|
|
using asio::posix::stream_descriptor::stream_descriptor;
|
|
|
|
|
void async_wait(HandlerType InHandler)
|
|
|
|
|
{
|
|
|
|
|
asio::posix::stream_descriptor::async_wait(asio::posix::stream_descriptor::wait_read, InHandler);
|
2023-04-24 10:02:27 -04:00
|
|
|
|
|
|
|
|
// Some new events have come into the stream. We need to read the events
|
|
|
|
|
// event if we are not acting on the events themself (see Refresh for platform
|
|
|
|
|
// independent update). If we don't read the data the next call to wait will
|
|
|
|
|
// trigger immediately.
|
|
|
|
|
bytes_readable AvailableCmd(true);
|
|
|
|
|
io_control(AvailableCmd);
|
|
|
|
|
size_t AvailableBytes = AvailableCmd.get();
|
|
|
|
|
uint8* Buffer = (uint8*)malloc(AvailableBytes);
|
|
|
|
|
read_some(asio::buffer(Buffer, AvailableBytes));
|
|
|
|
|
|
2023-05-26 05:04:29 -04:00
|
|
|
#if TS_USING(TS_DEBUG_FS_EVENTS)
|
2023-04-24 10:02:27 -04:00
|
|
|
size_t Cursor = 0;
|
|
|
|
|
while(Cursor < AvailableBytes)
|
|
|
|
|
{
|
|
|
|
|
inotify_event *Event = (inotify_event *)Buffer + Cursor;
|
|
|
|
|
printf("Recieved file event (0x08x) ", Event->cookie);
|
|
|
|
|
if (Event->len > 0)
|
|
|
|
|
printf("on '%s': ", Event->name);
|
|
|
|
|
if (Event->mask & IN_ACCESS)
|
|
|
|
|
printf("ACCESS ");
|
|
|
|
|
if ((Event->mask & IN_ATTRIB) != 0)
|
|
|
|
|
printf("ATTRIB ");
|
|
|
|
|
if ((Event->mask & IN_CLOSE_WRITE) != 0)
|
|
|
|
|
printf("CLOSE_WRITE ");
|
|
|
|
|
if ((Event->mask & IN_CLOSE_NOWRITE) != 0)
|
|
|
|
|
printf("CLOSE_NOWRITE ");
|
|
|
|
|
if ((Event->mask & IN_CREATE) != 0)
|
|
|
|
|
printf("CREATE ");
|
|
|
|
|
if ((Event->mask & IN_DELETE) != 0)
|
|
|
|
|
printf("DELETE ");
|
|
|
|
|
if ((Event->mask & IN_DELETE_SELF) != 0)
|
|
|
|
|
printf("DELETE_SELF ");
|
|
|
|
|
if ((Event->mask & IN_MODIFY) != 0)
|
|
|
|
|
printf("MODIFY ");
|
|
|
|
|
if ((Event->mask & IN_MOVE_SELF) != 0)
|
|
|
|
|
printf("MOVE_SELF ");
|
|
|
|
|
if ((Event->mask & IN_MOVED_FROM) != 0)
|
|
|
|
|
printf("MOVED_FROM ");
|
|
|
|
|
if ((Event->mask & IN_MOVED_TO) != 0)
|
|
|
|
|
printf("MOVED_TO ");
|
|
|
|
|
if ((Event->mask & IN_OPEN) != 0)
|
|
|
|
|
printf("OPEN ");
|
|
|
|
|
printf("\n");
|
|
|
|
|
Cursor += sizeof(inotify_event) + Event->len;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2022-03-21 09:36:32 -04:00
|
|
|
}
|
2021-08-18 07:36:31 -04:00
|
|
|
};
|
2023-01-09 03:48:09 -05:00
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-03-21 09:36:32 -04:00
|
|
|
#elif TS_USING(TS_PLATFORM_MAC)
|
2023-03-28 09:47:42 -04:00
|
|
|
class FStore::FMount::FDirWatcher
|
2022-03-21 09:36:32 -04:00
|
|
|
{
|
|
|
|
|
typedef std::function<void(asio::error_code error)> HandlerType;
|
|
|
|
|
public:
|
|
|
|
|
FDirWatcher(const char* InStoreDir)
|
|
|
|
|
{
|
|
|
|
|
std::error_code ErrorCode;
|
|
|
|
|
StoreDir = std::filesystem::absolute(InStoreDir, ErrorCode);
|
2023-05-26 05:04:29 -04:00
|
|
|
// Create watcher queue
|
|
|
|
|
char WatcherName[128];
|
|
|
|
|
snprintf(WatcherName, sizeof(WatcherName), "%s-%p", "FileWatcher", this);
|
|
|
|
|
DispatchQueue = dispatch_queue_create(WatcherName, DISPATCH_QUEUE_SERIAL);
|
2022-03-21 09:36:32 -04:00
|
|
|
}
|
|
|
|
|
void async_wait(HandlerType InHandler);
|
|
|
|
|
void cancel()
|
|
|
|
|
{
|
|
|
|
|
close();
|
|
|
|
|
}
|
|
|
|
|
void close()
|
|
|
|
|
{
|
|
|
|
|
if (bIsRunning)
|
|
|
|
|
{
|
|
|
|
|
FSEventStreamStop(EventStream);
|
2023-05-26 05:04:29 -04:00
|
|
|
FSEventStreamInvalidate(EventStream); // Also removed from dispatch queue
|
2022-03-21 09:36:32 -04:00
|
|
|
FSEventStreamRelease(EventStream);
|
2023-05-26 05:04:29 -04:00
|
|
|
dispatch_release(DispatchQueue);
|
2022-03-21 09:36:32 -04:00
|
|
|
bIsRunning = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool is_open() { return bIsRunning; }
|
|
|
|
|
|
|
|
|
|
void ProcessChanges(size_t EventCount, void* EventPaths, const FSEventStreamEventFlags EventFlags[])
|
|
|
|
|
{
|
2023-05-26 05:04:29 -04:00
|
|
|
bool bWatchedEvent = false;
|
|
|
|
|
const char** EventPathsArray = (const char**) EventPaths;
|
|
|
|
|
for (size_t EventIdx = 0; EventIdx < EventCount; ++EventIdx)
|
|
|
|
|
{
|
|
|
|
|
const char* Path = EventPathsArray[EventIdx];
|
|
|
|
|
const FSEventStreamEventFlags& Flags = EventFlags[EventIdx];
|
|
|
|
|
#if TS_USING(TS_DEBUG_FS_EVENTS)
|
|
|
|
|
printf("Recieved file event (%d) ", EventIdx);
|
|
|
|
|
if (Path != nullptr)
|
|
|
|
|
printf("on '%s': ", Path);
|
|
|
|
|
else
|
|
|
|
|
printf("on unknown file: ");
|
|
|
|
|
if (Flags & kFSEventStreamEventFlagItemCreated)
|
|
|
|
|
printf(" CREATED");
|
|
|
|
|
if (Flags & kFSEventStreamEventFlagItemRemoved)
|
|
|
|
|
printf(" REMOVED");
|
|
|
|
|
if (Flags & kFSEventStreamEventFlagItemRenamed)
|
|
|
|
|
printf(" RENAMED");
|
|
|
|
|
printf("\n");
|
|
|
|
|
#endif
|
|
|
|
|
constexpr unsigned int InterestingFlags = kFSEventStreamEventFlagItemCreated |
|
|
|
|
|
kFSEventStreamEventFlagItemRemoved | kFSEventStreamEventFlagItemRenamed;
|
|
|
|
|
bWatchedEvent |= !!(Flags & InterestingFlags);
|
|
|
|
|
}
|
|
|
|
|
if (bWatchedEvent)
|
|
|
|
|
{
|
|
|
|
|
Handler(asio::error_code());
|
|
|
|
|
}
|
2022-03-21 09:36:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FSEventStreamRef EventStream;
|
2023-05-26 05:04:29 -04:00
|
|
|
dispatch_queue_t DispatchQueue;
|
|
|
|
|
HandlerType Handler;
|
2022-03-21 09:36:32 -04:00
|
|
|
private:
|
2023-05-26 05:04:29 -04:00
|
|
|
static void MacCallback(ConstFSEventStreamRef StreamRef,
|
|
|
|
|
void* InDirWatcherPtr,
|
|
|
|
|
size_t EventCount,
|
|
|
|
|
void* EventPaths,
|
|
|
|
|
const FSEventStreamEventFlags EventFlags[],
|
|
|
|
|
const FSEventStreamEventId EventIDs[]);
|
2022-03-21 09:36:32 -04:00
|
|
|
bool bIsRunning = false;
|
2022-07-01 07:38:06 -04:00
|
|
|
FPath StoreDir;
|
2022-03-21 09:36:32 -04:00
|
|
|
};
|
|
|
|
|
|
2023-01-09 03:48:09 -05:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2023-05-26 05:04:29 -04:00
|
|
|
void FStore::FMount::FDirWatcher::MacCallback(ConstFSEventStreamRef StreamRef,
|
2022-03-21 09:36:32 -04:00
|
|
|
void* InDirWatcherPtr,
|
|
|
|
|
size_t EventCount,
|
|
|
|
|
void* EventPaths,
|
|
|
|
|
const FSEventStreamEventFlags EventFlags[],
|
|
|
|
|
const FSEventStreamEventId EventIDs[])
|
|
|
|
|
{
|
2023-03-28 09:47:42 -04:00
|
|
|
FStore::FMount::FDirWatcher* DirWatcherPtr = (FStore::FMount::FDirWatcher*)InDirWatcherPtr;
|
2022-03-21 09:36:32 -04:00
|
|
|
check(DirWatcherPtr);
|
|
|
|
|
check(DirWatcherPtr->EventStream == StreamRef);
|
|
|
|
|
|
|
|
|
|
DirWatcherPtr->ProcessChanges(EventCount, EventPaths, EventFlags);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-09 03:48:09 -05:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2023-03-28 09:47:42 -04:00
|
|
|
void FStore::FMount::FDirWatcher::async_wait(HandlerType InHandler)
|
2022-03-21 09:36:32 -04:00
|
|
|
{
|
|
|
|
|
if (bIsRunning)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CFAbsoluteTime Latency = 0.2; // seconds
|
|
|
|
|
|
|
|
|
|
FSEventStreamContext Context;
|
|
|
|
|
Context.version = 0;
|
|
|
|
|
Context.info = this;
|
|
|
|
|
Context.retain = NULL;
|
|
|
|
|
Context.release = NULL;
|
|
|
|
|
Context.copyDescription = NULL;
|
|
|
|
|
|
|
|
|
|
// Set up streaming and turn it on
|
2023-05-26 05:04:29 -04:00
|
|
|
CFStringRef FullPathMac = CFStringCreateWithFileSystemRepresentation(
|
|
|
|
|
NULL,
|
|
|
|
|
(const char*)StoreDir.c_str());
|
2022-03-21 09:36:32 -04:00
|
|
|
CFArrayRef PathsToWatch = CFArrayCreate(NULL, (const void**)&FullPathMac, 1, NULL);
|
|
|
|
|
|
2023-05-26 05:04:29 -04:00
|
|
|
// Create the event stream object
|
|
|
|
|
EventStream = FSEventStreamCreate(
|
|
|
|
|
NULL,
|
2022-03-21 09:36:32 -04:00
|
|
|
&MacCallback,
|
|
|
|
|
&Context,
|
|
|
|
|
PathsToWatch,
|
|
|
|
|
kFSEventStreamEventIdSinceNow,
|
|
|
|
|
Latency,
|
2023-05-26 05:04:29 -04:00
|
|
|
kFSEventStreamCreateFlagNoDefer|kFSEventStreamCreateFlagFileEvents
|
2022-03-21 09:36:32 -04:00
|
|
|
);
|
|
|
|
|
|
2023-05-26 05:04:29 -04:00
|
|
|
if (EventStream == nullptr)
|
|
|
|
|
{
|
|
|
|
|
printf("Failed to create file event stream for %s\n", CFStringGetCStringPtr(FullPathMac, kCFStringEncodingUnicode));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FSEventStreamSetDispatchQueue(EventStream, DispatchQueue);
|
|
|
|
|
bIsRunning = FSEventStreamStart(EventStream);
|
|
|
|
|
|
|
|
|
|
if (bIsRunning)
|
|
|
|
|
{
|
|
|
|
|
printf("Watcher enabled on %s\n", StoreDir.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
printf("Failed to start watcher for %s\n", StoreDir.c_str());
|
|
|
|
|
}
|
2022-03-21 09:36:32 -04:00
|
|
|
|
|
|
|
|
Handler = InHandler;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2021-08-18 07:36:31 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-07-01 07:38:06 -04:00
|
|
|
FStore::FTrace::FTrace(const FPath& InPath)
|
2021-08-18 07:36:31 -04:00
|
|
|
: Path(InPath)
|
|
|
|
|
{
|
2022-07-07 02:17:15 -04:00
|
|
|
const FString Name = GetName();
|
2023-04-24 17:01:47 -04:00
|
|
|
Id = QuickStoreHash(InPath.c_str());
|
2021-08-18 07:36:31 -04:00
|
|
|
|
2022-07-01 07:38:06 -04:00
|
|
|
std::error_code Ec;
|
2021-08-18 10:20:33 -04:00
|
|
|
// Calculate that trace's timestamp. Bias in seconds then convert to 0.1us.
|
2022-07-01 07:38:06 -04:00
|
|
|
std::filesystem::file_time_type LastWriteTime = std::filesystem::last_write_time(Path, Ec);
|
2021-08-18 10:20:33 -04:00
|
|
|
auto LastWriteDuration = LastWriteTime.time_since_epoch();
|
|
|
|
|
Timestamp = std::chrono::duration_cast<std::chrono::seconds>(LastWriteDuration).count();
|
|
|
|
|
Timestamp += FsToUnrealEpochBiasSeconds;
|
|
|
|
|
Timestamp *= 10'000'000;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-07 02:17:15 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FString FStore::FTrace::GetName() const
|
|
|
|
|
{
|
2022-08-29 03:50:07 -04:00
|
|
|
return FString((const char*)Path.stem().u8string().c_str());
|
2022-07-07 02:17:15 -04:00
|
|
|
}
|
|
|
|
|
|
2021-08-18 07:36:31 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-07-01 07:38:06 -04:00
|
|
|
const FPath& FStore::FTrace::GetPath() const
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-07-01 07:38:06 -04:00
|
|
|
return Path;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FStore::FTrace::GetId() const
|
|
|
|
|
{
|
|
|
|
|
return Id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint64 FStore::FTrace::GetSize() const
|
|
|
|
|
{
|
2023-03-28 09:47:42 -04:00
|
|
|
std::error_code Error;
|
|
|
|
|
const uint64 Size = std::filesystem::file_size(Path, Error);
|
|
|
|
|
return Error ? 0 : Size;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint64 FStore::FTrace::GetTimestamp() const
|
|
|
|
|
{
|
|
|
|
|
return Timestamp;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2023-04-17 09:03:52 -04:00
|
|
|
FStore::FMount* FStore::FMount::Create(FStore* InParent, asio::io_context& InIoContext, const FPath& InDir, bool bCreate)
|
2023-03-28 09:47:42 -04:00
|
|
|
{
|
|
|
|
|
std::error_code ErrorCodeAbs, ErrorCodeCreate;
|
|
|
|
|
// We would like to check if the path is absolute here, but Microsoft
|
|
|
|
|
// has a very specific interpretation of what constitutes absolute path
|
|
|
|
|
// which doesn't correspond to what UE's file utilities does. Hence paths
|
|
|
|
|
// from Insights will not be correctly formatted.
|
|
|
|
|
//const FPath AbsoluteDir = InDir.is_absolute() ? InDir : fs::absolute(InDir, ErrorCodeAbs);
|
|
|
|
|
const FPath AbsoluteDir = InDir;
|
2023-04-17 09:03:52 -04:00
|
|
|
if (bCreate)
|
2023-03-28 09:47:42 -04:00
|
|
|
{
|
2023-04-17 09:03:52 -04:00
|
|
|
// Make sure the directory exists
|
|
|
|
|
fs::create_directories(AbsoluteDir, ErrorCodeCreate);
|
|
|
|
|
if (ErrorCodeAbs || ErrorCodeCreate)
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// If we are not allowed to create and if the directory does not exist
|
|
|
|
|
// there is no point adding a mount for it.
|
|
|
|
|
if (!fs::is_directory(AbsoluteDir))
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2023-03-28 09:47:42 -04:00
|
|
|
}
|
|
|
|
|
return new FMount(InParent, InIoContext, AbsoluteDir);
|
|
|
|
|
}
|
2021-08-18 07:36:31 -04:00
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2023-03-28 09:47:42 -04:00
|
|
|
FStore::FMount::FMount(FStore* InParent, asio::io_context& InIoContext, const fs::path& InDir)
|
2022-03-21 09:36:32 -04:00
|
|
|
: Id(QuickStoreHash(InDir.c_str()))
|
2023-03-28 09:47:42 -04:00
|
|
|
, Dir(InDir)
|
|
|
|
|
, Parent(InParent)
|
|
|
|
|
, IoContext(InIoContext)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2023-03-28 09:47:42 -04:00
|
|
|
|
|
|
|
|
#if TS_USING(TS_PLATFORM_WINDOWS)
|
|
|
|
|
std::wstring StoreDirW = Dir;
|
|
|
|
|
HANDLE DirWatchHandle = FindFirstChangeNotificationW(StoreDirW.c_str(), false, FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME);
|
|
|
|
|
if (DirWatchHandle == INVALID_HANDLE_VALUE)
|
|
|
|
|
{
|
|
|
|
|
DirWatchHandle = 0;
|
|
|
|
|
}
|
|
|
|
|
DirWatcher = new FDirWatcher(IoContext, DirWatchHandle);
|
|
|
|
|
#elif TS_USING(TS_PLATFORM_LINUX)
|
|
|
|
|
int inotfd = inotify_init();
|
2023-04-20 04:00:10 -04:00
|
|
|
int watch_desc = inotify_add_watch(inotfd, Dir.c_str(), IN_CREATE | IN_DELETE);
|
2023-03-28 09:47:42 -04:00
|
|
|
DirWatcher = new FDirWatcher(IoContext, inotfd);
|
2023-04-24 10:02:27 -04:00
|
|
|
#elif TS_USING(TS_PLATFORM_MAC)
|
|
|
|
|
DirWatcher = new FDirWatcher(Dir.c_str());
|
2023-03-28 09:47:42 -04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
WatchDir();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FStore::FMount::~FMount()
|
|
|
|
|
{
|
|
|
|
|
if (DirWatcher != nullptr)
|
|
|
|
|
{
|
|
|
|
|
delete DirWatcher;
|
|
|
|
|
}
|
2022-03-21 09:36:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FString FStore::FMount::GetDir() const
|
|
|
|
|
{
|
|
|
|
|
return fs::ToFString(Dir);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
const FPath& FStore::FMount::GetPath() const
|
|
|
|
|
{
|
|
|
|
|
return Dir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
void FStore::FMount::Close()
|
|
|
|
|
{
|
|
|
|
|
if (DirWatcher != nullptr)
|
|
|
|
|
{
|
|
|
|
|
DirWatcher->cancel();
|
|
|
|
|
DirWatcher->close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-21 09:36:32 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FStore::FMount::GetId() const
|
|
|
|
|
{
|
|
|
|
|
return Id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FStore::FMount::GetTraceCount() const
|
|
|
|
|
{
|
|
|
|
|
return Traces.Num();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
const FStore::FTrace* FStore::FMount::GetTraceInfo(uint32 Index) const
|
|
|
|
|
{
|
|
|
|
|
if (Index >= uint32(Traces.Num()))
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Traces[Index];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FStore::FTrace* FStore::FMount::GetTrace(uint32 Id) const
|
|
|
|
|
{
|
|
|
|
|
for (FTrace* Trace : Traces)
|
|
|
|
|
{
|
|
|
|
|
if (Trace->GetId() == Id)
|
|
|
|
|
{
|
|
|
|
|
return Trace;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-07-01 07:38:06 -04:00
|
|
|
FStore::FTrace* FStore::FMount::AddTrace(const FPath& Path)
|
2022-03-21 09:36:32 -04:00
|
|
|
{
|
|
|
|
|
FTrace NewTrace(Path);
|
|
|
|
|
|
|
|
|
|
uint32 Id = NewTrace.GetId();
|
|
|
|
|
if (FTrace* Existing = GetTrace(Id))
|
|
|
|
|
{
|
|
|
|
|
return Existing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FTrace* Trace = new FTrace(MoveTemp(NewTrace));
|
|
|
|
|
Traces.Add(Trace);
|
|
|
|
|
return Trace;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2023-03-28 09:47:42 -04:00
|
|
|
void FStore::FMount::ClearTraces()
|
2022-03-21 09:36:32 -04:00
|
|
|
{
|
2022-07-06 08:01:02 -04:00
|
|
|
Traces.Empty();
|
2023-03-28 09:47:42 -04:00
|
|
|
}
|
2022-07-06 08:01:02 -04:00
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FStore::FMount::Refresh()
|
|
|
|
|
{
|
|
|
|
|
ClearTraces();
|
2022-03-21 09:36:32 -04:00
|
|
|
uint32 ChangeSerial = 0;
|
2023-04-17 09:03:52 -04:00
|
|
|
std::error_code Ec;
|
|
|
|
|
for (auto& DirItem : std::filesystem::directory_iterator(Dir, Ec))
|
2022-03-21 09:36:32 -04:00
|
|
|
{
|
|
|
|
|
if (DirItem.is_directory())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 07:38:06 -04:00
|
|
|
FPath Extension = DirItem.path().extension();
|
2022-03-21 09:36:32 -04:00
|
|
|
if (Extension != ".utrace")
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 07:38:06 -04:00
|
|
|
FTrace* Trace = AddTrace(DirItem.path());
|
|
|
|
|
|
2022-03-21 09:36:32 -04:00
|
|
|
if (Trace != nullptr)
|
|
|
|
|
{
|
|
|
|
|
ChangeSerial += Trace->GetId();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ChangeSerial;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
void FStore::SetupMounts()
|
2022-03-21 09:36:32 -04:00
|
|
|
{
|
2023-04-17 09:03:52 -04:00
|
|
|
AddMount(Settings->StoreDir, true);
|
2021-08-18 07:36:31 -04:00
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
for (const FPath& Path : Settings->AdditionalWatchDirs)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2023-04-17 09:03:52 -04:00
|
|
|
AddMount(Path, false);
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
2023-03-28 09:47:42 -04:00
|
|
|
}
|
2021-08-18 07:36:31 -04:00
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FStore::FStore(asio::io_context& InIoContext, const FStoreSettings* InSettings)
|
|
|
|
|
: IoContext(InIoContext)
|
|
|
|
|
, Settings(InSettings)
|
|
|
|
|
{
|
|
|
|
|
SetupMounts();
|
|
|
|
|
Refresh();
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FStore::~FStore()
|
|
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
for (FMount* Mount : Mounts)
|
|
|
|
|
{
|
|
|
|
|
delete Mount;
|
|
|
|
|
}
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
void FStore::Close()
|
|
|
|
|
{
|
|
|
|
|
for (FMount* Mount : Mounts)
|
|
|
|
|
{
|
|
|
|
|
Mount->Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 07:36:31 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2023-04-17 09:03:52 -04:00
|
|
|
bool FStore::AddMount(const FPath& Dir, bool bCreate)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2023-04-17 09:03:52 -04:00
|
|
|
FMount* Mount = FMount::Create(this, IoContext, Dir, bCreate);
|
2023-03-28 09:47:42 -04:00
|
|
|
if (Mount)
|
|
|
|
|
{
|
|
|
|
|
Mounts.Add(Mount);
|
|
|
|
|
}
|
|
|
|
|
return Mount != nullptr;
|
2022-03-21 09:36:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
bool FStore::RemoveMount(uint32 Id)
|
|
|
|
|
{
|
|
|
|
|
for (uint32 i = 1, n = Mounts.Num() - 1; i <= n; ++i) // 1 because 0th must always exist
|
|
|
|
|
{
|
|
|
|
|
if (Mounts[i]->GetId() == Id)
|
|
|
|
|
{
|
|
|
|
|
std::swap(Mounts[n], Mounts[i]);
|
|
|
|
|
Mounts.SetNum(n);
|
|
|
|
|
Refresh();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2023-03-28 09:47:42 -04:00
|
|
|
void FStore::FMount::WatchDir()
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
|
|
|
|
if (DirWatcher == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DirWatcher->async_wait([this] (asio::error_code ErrorCode)
|
|
|
|
|
{
|
|
|
|
|
if (ErrorCode)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if TS_USING(TS_PLATFORM_WINDOWS)
|
2021-08-19 04:14:53 -04:00
|
|
|
// Windows doesn't update modified timestamps in a timely fashion when
|
|
|
|
|
// copying files (or it could be Explorer that doesn't update it until
|
|
|
|
|
// later). This is a not-so-pretty "wait for a little bit" workaround.
|
|
|
|
|
auto* DelayTimer = new asio::steady_timer(IoContext);
|
|
|
|
|
DelayTimer->expires_after(std::chrono::seconds(2));
|
|
|
|
|
DelayTimer->async_wait([this, DelayTimer] (const asio::error_code& ErrorCode)
|
|
|
|
|
{
|
|
|
|
|
delete DelayTimer;
|
|
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
Parent->Refresh();
|
2021-08-19 04:14:53 -04:00
|
|
|
|
|
|
|
|
FindNextChangeNotification(DirWatcher->native_handle());
|
|
|
|
|
WatchDir();
|
|
|
|
|
});
|
|
|
|
|
#else
|
2023-04-24 10:02:27 -04:00
|
|
|
Parent->Refresh();
|
2021-08-18 07:36:31 -04:00
|
|
|
WatchDir();
|
2021-08-19 04:14:53 -04:00
|
|
|
#endif
|
2021-08-18 07:36:31 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-03-21 09:36:32 -04:00
|
|
|
FString FStore::GetStoreDir() const
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
FMount* Mount = Mounts[0];
|
|
|
|
|
return Mount->GetDir();
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FStore::GetChangeSerial() const
|
|
|
|
|
{
|
|
|
|
|
return ChangeSerial;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
uint32 FStore::GetTraceCount() const
|
|
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
uint32 Count = 0;
|
|
|
|
|
for (const FMount* Mount : Mounts)
|
|
|
|
|
{
|
|
|
|
|
Count += Mount->GetTraceCount();
|
|
|
|
|
}
|
|
|
|
|
return Count;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
const FStore::FTrace* FStore::GetTraceInfo(uint32 Index) const
|
|
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
for (const FMount* Mount : Mounts)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
uint32 Count = Mount->GetTraceCount();
|
|
|
|
|
if (Index < Count)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
return Mount->GetTraceInfo(Index);
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
2022-03-21 09:36:32 -04:00
|
|
|
Index -= Count;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2022-03-21 09:36:32 -04:00
|
|
|
FStore::FTrace* FStore::GetTrace(uint32 Id, FMount** OutMount) const
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
for (FMount* Mount : Mounts)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
if (FTrace* Trace = Mount->GetTrace(Id))
|
|
|
|
|
{
|
|
|
|
|
if (OutMount != nullptr)
|
|
|
|
|
{
|
|
|
|
|
*OutMount = Mount;
|
|
|
|
|
}
|
|
|
|
|
return Trace;
|
|
|
|
|
}
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
2022-03-21 09:36:32 -04:00
|
|
|
return nullptr;
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FStore::FNewTrace FStore::CreateTrace()
|
|
|
|
|
{
|
|
|
|
|
// N.B. Not thread safe!?
|
|
|
|
|
char Prefix[24];
|
|
|
|
|
std::time_t Now = std::time(nullptr);
|
|
|
|
|
std::tm* LocalNow = std::localtime(&Now);
|
|
|
|
|
std::strftime(Prefix, TS_ARRAY_COUNT(Prefix), "%Y%m%d_%H%M%S", LocalNow);
|
|
|
|
|
|
2022-03-21 09:36:32 -04:00
|
|
|
FMount* DefaultMount = Mounts[0];
|
|
|
|
|
|
2022-07-01 07:38:06 -04:00
|
|
|
FPath TracePath(*DefaultMount->GetDir());
|
|
|
|
|
TracePath /= Prefix;
|
2021-08-18 07:36:31 -04:00
|
|
|
TracePath += ".utrace";
|
2022-07-01 07:38:06 -04:00
|
|
|
|
|
|
|
|
for (uint32 Index = 0; std::filesystem::is_regular_file(TracePath); ++Index)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-07-06 07:44:02 -04:00
|
|
|
char FilenameIndexed[64];
|
2022-07-01 07:38:06 -04:00
|
|
|
std::sprintf(FilenameIndexed, "%s_%02d.utrace", Prefix, Index);
|
|
|
|
|
TracePath.replace_filename(FPath(FilenameIndexed));
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-01 07:38:06 -04:00
|
|
|
FAsioWriteable* File = FAsioFile::WriteFile(IoContext, TracePath);
|
2021-08-18 07:36:31 -04:00
|
|
|
if (File == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 07:38:06 -04:00
|
|
|
FTrace* Trace = DefaultMount->AddTrace(TracePath);
|
2021-08-18 07:36:31 -04:00
|
|
|
if (Trace == nullptr)
|
|
|
|
|
{
|
|
|
|
|
delete File;
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { Trace->GetId(), File };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
bool FStore::HasTrace(uint32 Id) const
|
|
|
|
|
{
|
|
|
|
|
return GetTrace(Id) != nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
FAsioReadable* FStore::OpenTrace(uint32 Id)
|
|
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
FMount* Mount;
|
|
|
|
|
FTrace* Trace = GetTrace(Id, &Mount);
|
2021-08-18 07:36:31 -04:00
|
|
|
if (Trace == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 07:38:06 -04:00
|
|
|
return FAsioFile::ReadFile(IoContext, Trace->GetPath());
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
2023-03-28 09:47:42 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
void FStore::OnSettingsChanged()
|
|
|
|
|
{
|
|
|
|
|
// Remove all existing mounts
|
|
|
|
|
for (const FMount* Mount : Mounts)
|
|
|
|
|
{
|
|
|
|
|
delete Mount;
|
|
|
|
|
}
|
|
|
|
|
Mounts.Empty();
|
|
|
|
|
SetupMounts();
|
|
|
|
|
Refresh();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 07:36:31 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
void FStore::Refresh()
|
|
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
ChangeSerial = 0;
|
|
|
|
|
for (FMount* Mount : Mounts)
|
2021-08-18 07:36:31 -04:00
|
|
|
{
|
2022-03-21 09:36:32 -04:00
|
|
|
ChangeSerial += Mount->Refresh();
|
|
|
|
|
}
|
2021-08-18 07:36:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* vim: set noexpandtab : */
|