@photostructure/fs-metadata 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -200
- package/binding.gyp +6 -3
- package/package.json +27 -24
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/src/binding.cpp +59 -29
- package/src/common/debug_log.h +64 -0
- package/src/common/hidden.h +12 -0
- package/src/common/metadata_worker.h +1 -44
- package/src/common/volume_metadata.h +98 -8
- package/src/common/volume_mount_points.h +42 -2
- package/src/darwin/fs_meta.h +0 -11
- package/src/darwin/hidden.cpp +139 -0
- package/src/darwin/hidden.h +35 -0
- package/src/darwin/volume_metadata.cpp +105 -40
- package/src/darwin/volume_mount_points.cpp +88 -29
- package/src/linux/blkid_cache.cpp +9 -0
- package/src/linux/blkid_cache.h +1 -0
- package/src/linux/gio_mount_points.cpp +81 -0
- package/src/linux/{gio_worker.h → gio_mount_points.h} +7 -2
- package/src/linux/gio_utils.cpp +29 -115
- package/src/linux/gio_utils.h +69 -9
- package/src/linux/gio_volume_metadata.cpp +90 -0
- package/src/linux/gio_volume_metadata.h +20 -0
- package/src/linux/volume_metadata.cpp +31 -16
- package/src/windows/drive_status.h +227 -0
- package/src/windows/error_utils.h +27 -6
- package/src/windows/fs_meta.h +3 -33
- package/src/windows/hidden.cpp +160 -0
- package/src/windows/hidden.h +3 -0
- package/src/windows/string.h +48 -0
- package/src/windows/system_volume.h +63 -0
- package/src/windows/volume_metadata.cpp +171 -138
- package/src/windows/volume_mount_points.cpp +102 -26
- package/src/common/mount_point.h +0 -13
- package/src/linux/gio_worker.cpp +0 -87
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/windows/drive_status.h
|
|
2
|
+
|
|
3
|
+
#pragma once
|
|
4
|
+
#include "../common/debug_log.h"
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <vector>
|
|
8
|
+
#include <windows.h>
|
|
9
|
+
|
|
10
|
+
namespace FSMeta {
|
|
11
|
+
|
|
12
|
+
enum class DriveStatus {
|
|
13
|
+
Healthy,
|
|
14
|
+
Timeout,
|
|
15
|
+
Inaccessible,
|
|
16
|
+
Disconnected,
|
|
17
|
+
Unknown
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
inline std::string DriveStatusToString(DriveStatus status) {
|
|
21
|
+
switch (status) {
|
|
22
|
+
case DriveStatus::Healthy:
|
|
23
|
+
return "healthy";
|
|
24
|
+
case DriveStatus::Timeout:
|
|
25
|
+
return "timeout";
|
|
26
|
+
case DriveStatus::Inaccessible:
|
|
27
|
+
return "inaccessible";
|
|
28
|
+
case DriveStatus::Disconnected:
|
|
29
|
+
return "disconnected";
|
|
30
|
+
default:
|
|
31
|
+
return "unknown";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class IOOperation {
|
|
36
|
+
private:
|
|
37
|
+
HANDLE completionEvent;
|
|
38
|
+
DriveStatus result;
|
|
39
|
+
std::string path;
|
|
40
|
+
DWORD threadId;
|
|
41
|
+
volatile bool shouldTerminate;
|
|
42
|
+
HANDLE threadHandle; // Store thread handle as member
|
|
43
|
+
|
|
44
|
+
public:
|
|
45
|
+
IOOperation()
|
|
46
|
+
: result(DriveStatus::Unknown), threadId(0), shouldTerminate(false),
|
|
47
|
+
threadHandle(NULL) {
|
|
48
|
+
completionEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
~IOOperation() {
|
|
52
|
+
if (completionEvent) {
|
|
53
|
+
CloseHandle(completionEvent);
|
|
54
|
+
}
|
|
55
|
+
CleanupThread();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
void CleanupThread() {
|
|
59
|
+
if (threadHandle) {
|
|
60
|
+
DEBUG_LOG("[IOOperation] Cleaning up thread %lu", threadId);
|
|
61
|
+
// Signal termination
|
|
62
|
+
shouldTerminate = true;
|
|
63
|
+
|
|
64
|
+
// Give thread chance to exit gracefully
|
|
65
|
+
if (WaitForSingleObject(threadHandle, 100) != WAIT_OBJECT_0) {
|
|
66
|
+
DEBUG_LOG("[IOOperation] Force terminating thread %lu", threadId);
|
|
67
|
+
TerminateThread(threadHandle, 1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
CloseHandle(threadHandle);
|
|
71
|
+
threadHandle = NULL;
|
|
72
|
+
DEBUG_LOG("[IOOperation] Thread cleanup complete");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static DWORD WINAPI WorkerThread(LPVOID param) {
|
|
77
|
+
IOOperation *self = static_cast<IOOperation *>(param);
|
|
78
|
+
std::string searchPath = self->path + "*";
|
|
79
|
+
HANDLE findHandle = INVALID_HANDLE_VALUE;
|
|
80
|
+
WIN32_FIND_DATAA findData;
|
|
81
|
+
|
|
82
|
+
DEBUG_LOG("[WorkerThread] Starting search on path: %s", searchPath.c_str());
|
|
83
|
+
|
|
84
|
+
findHandle = FindFirstFileExA(
|
|
85
|
+
searchPath.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch,
|
|
86
|
+
NULL, FIND_FIRST_EX_LARGE_FETCH | FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY);
|
|
87
|
+
|
|
88
|
+
if (findHandle == INVALID_HANDLE_VALUE) {
|
|
89
|
+
self->result = self->MapErrorToDriveStatus(GetLastError());
|
|
90
|
+
DEBUG_LOG("[WorkerThread] Search failed with error: %lu", GetLastError());
|
|
91
|
+
} else {
|
|
92
|
+
self->result = DriveStatus::Healthy;
|
|
93
|
+
FindClose(findHandle);
|
|
94
|
+
DEBUG_LOG("[WorkerThread] Search completed successfully");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
SetEvent(self->completionEvent);
|
|
98
|
+
DEBUG_LOG("[WorkerThread] Thread %lu exiting", self->threadId);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
DriveStatus CheckDriveWithTimeout(const std::string &checkPath,
|
|
103
|
+
DWORD timeoutMs) {
|
|
104
|
+
DEBUG_LOG("[CheckDriveStatus] Starting check for: %s timeout: %lu ms",
|
|
105
|
+
checkPath.c_str(), timeoutMs);
|
|
106
|
+
|
|
107
|
+
// Cleanup any previous thread
|
|
108
|
+
CleanupThread();
|
|
109
|
+
|
|
110
|
+
path = checkPath;
|
|
111
|
+
ResetEvent(completionEvent);
|
|
112
|
+
shouldTerminate = false;
|
|
113
|
+
|
|
114
|
+
threadHandle = CreateThread(NULL, 0, WorkerThread, this, 0, &threadId);
|
|
115
|
+
if (!threadHandle) {
|
|
116
|
+
DEBUG_LOG("[CheckDriveStatus] Failed to create thread");
|
|
117
|
+
return DriveStatus::Unknown;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
DEBUG_LOG("[CheckDriveStatus] Created thread %lu", threadId);
|
|
121
|
+
DWORD waitResult = WaitForSingleObject(threadHandle, timeoutMs);
|
|
122
|
+
|
|
123
|
+
if (waitResult == WAIT_TIMEOUT) {
|
|
124
|
+
DEBUG_LOG("[CheckDriveStatus] Thread %lu timed out after %lu ms",
|
|
125
|
+
threadId, timeoutMs);
|
|
126
|
+
CleanupThread();
|
|
127
|
+
return DriveStatus::Timeout;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
DEBUG_LOG("[CheckDriveStatus] Thread %lu completed normally", threadId);
|
|
131
|
+
CloseHandle(threadHandle);
|
|
132
|
+
DEBUG_LOG("[CheckDriveStatus] CloseHandle %lu completed", threadId);
|
|
133
|
+
threadHandle = NULL;
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private:
|
|
138
|
+
DriveStatus MapErrorToDriveStatus(DWORD error) {
|
|
139
|
+
switch (error) {
|
|
140
|
+
case ERROR_FILE_NOT_FOUND:
|
|
141
|
+
case ERROR_PATH_NOT_FOUND:
|
|
142
|
+
case ERROR_ACCESS_DENIED:
|
|
143
|
+
case ERROR_LOGON_FAILURE:
|
|
144
|
+
return DriveStatus::Inaccessible;
|
|
145
|
+
|
|
146
|
+
case ERROR_BAD_NET_NAME:
|
|
147
|
+
case ERROR_NETWORK_UNREACHABLE:
|
|
148
|
+
case ERROR_NOT_CONNECTED:
|
|
149
|
+
case ERROR_NETWORK_ACCESS_DENIED:
|
|
150
|
+
case ERROR_BAD_NETPATH:
|
|
151
|
+
return DriveStatus::Disconnected;
|
|
152
|
+
|
|
153
|
+
default:
|
|
154
|
+
return DriveStatus::Unknown;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
class ParallelDriveStatus {
|
|
160
|
+
private:
|
|
161
|
+
struct PendingCheck {
|
|
162
|
+
std::string path;
|
|
163
|
+
std::unique_ptr<IOOperation> op;
|
|
164
|
+
DWORD timeoutMs;
|
|
165
|
+
DriveStatus status;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
std::vector<std::unique_ptr<PendingCheck>> pending_;
|
|
169
|
+
|
|
170
|
+
public:
|
|
171
|
+
void Submit(const std::string &path, DWORD timeoutMs = 1000) {
|
|
172
|
+
auto check = std::make_unique<PendingCheck>();
|
|
173
|
+
check->path = path;
|
|
174
|
+
check->op = std::make_unique<IOOperation>();
|
|
175
|
+
check->timeoutMs = timeoutMs;
|
|
176
|
+
check->status = DriveStatus::Unknown;
|
|
177
|
+
|
|
178
|
+
DEBUG_LOG("[ParallelDriveStatus] Submitting check for: %s", path.c_str());
|
|
179
|
+
pending_.push_back(std::move(check));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
std::vector<DriveStatus> WaitForResults() {
|
|
183
|
+
std::vector<DriveStatus> results;
|
|
184
|
+
results.reserve(pending_.size());
|
|
185
|
+
|
|
186
|
+
// Start all operations
|
|
187
|
+
for (auto &check : pending_) {
|
|
188
|
+
check->status =
|
|
189
|
+
check->op->CheckDriveWithTimeout(check->path, check->timeoutMs);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Collect results in original order
|
|
193
|
+
for (auto &check : pending_) {
|
|
194
|
+
DEBUG_LOG("[ParallelDriveStatus] Result for %s: %s", check->path.c_str(),
|
|
195
|
+
DriveStatusToString(check->status).c_str());
|
|
196
|
+
results.push_back(check->status);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
pending_.clear();
|
|
200
|
+
return results;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Update CheckDriveStatus to support checking multiple paths
|
|
205
|
+
inline std::vector<DriveStatus>
|
|
206
|
+
CheckDriveStatus(const std::vector<std::string> &paths,
|
|
207
|
+
DWORD timeoutMs = 1000) {
|
|
208
|
+
try {
|
|
209
|
+
ParallelDriveStatus checker;
|
|
210
|
+
for (const auto &path : paths) {
|
|
211
|
+
checker.Submit(path, timeoutMs);
|
|
212
|
+
}
|
|
213
|
+
return checker.WaitForResults();
|
|
214
|
+
} catch (...) {
|
|
215
|
+
DEBUG_LOG("[CheckDriveStatus] caught unexpected exception");
|
|
216
|
+
return std::vector<DriveStatus>(paths.size(), DriveStatus::Unknown);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Keep single path version for backwards compatibility
|
|
221
|
+
inline DriveStatus CheckDriveStatus(const std::string &path,
|
|
222
|
+
DWORD timeoutMs = 1000) {
|
|
223
|
+
std::vector<std::string> paths{path};
|
|
224
|
+
return CheckDriveStatus(paths, timeoutMs)[0];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
} // namespace FSMeta
|
|
@@ -11,12 +11,33 @@ class FSException : public std::runtime_error {
|
|
|
11
11
|
public:
|
|
12
12
|
explicit FSException(const std::string &message)
|
|
13
13
|
: std::runtime_error(message) {}
|
|
14
|
-
};
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
FSException(const std::string &operation, DWORD errorCode)
|
|
16
|
+
: std::runtime_error(FormatWindowsError(operation, errorCode)) {}
|
|
17
|
+
|
|
18
|
+
private:
|
|
19
|
+
static std::string FormatWindowsError(const std::string &operation,
|
|
20
|
+
DWORD error) {
|
|
21
|
+
if (error == 0) {
|
|
22
|
+
return operation + " failed with an unknown error";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
LPVOID messageBuffer;
|
|
26
|
+
size_t size = FormatMessageA(
|
|
27
|
+
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
28
|
+
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
29
|
+
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
30
|
+
(LPSTR)&messageBuffer, 0, NULL);
|
|
31
|
+
|
|
32
|
+
if (size == 0 || !messageBuffer) {
|
|
33
|
+
return operation + " failed with error code: " + std::to_string(error);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
std::string errorMessage((LPSTR)messageBuffer, size);
|
|
37
|
+
LocalFree(messageBuffer);
|
|
38
|
+
|
|
39
|
+
return operation + " failed: " + errorMessage;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
21
42
|
|
|
22
43
|
} // namespace FSMeta
|
package/src/windows/fs_meta.h
CHANGED
|
@@ -1,43 +1,13 @@
|
|
|
1
1
|
// src/windows/fs_meta.h
|
|
2
|
+
|
|
2
3
|
#pragma once
|
|
3
4
|
#include "../common/volume_metadata.h"
|
|
4
5
|
#include "../common/volume_mount_points.h"
|
|
5
|
-
#include
|
|
6
|
-
#include <napi.h>
|
|
7
|
-
|
|
8
|
-
#include <string>
|
|
6
|
+
#include <windows.h> // for MAX_PATH
|
|
9
7
|
|
|
10
8
|
namespace FSMeta {
|
|
11
9
|
|
|
12
10
|
constexpr size_t ERROR_BUFFER_SIZE = 256;
|
|
13
|
-
constexpr
|
|
14
|
-
|
|
15
|
-
enum DriveStatus {
|
|
16
|
-
Unknown,
|
|
17
|
-
Unavailable,
|
|
18
|
-
Healthy,
|
|
19
|
-
Disconnected,
|
|
20
|
-
Error,
|
|
21
|
-
NoMedia
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
inline const char *DriveStatusToString(DriveStatus status) {
|
|
25
|
-
switch (status) {
|
|
26
|
-
case Unknown:
|
|
27
|
-
return "unknown";
|
|
28
|
-
case Unavailable:
|
|
29
|
-
return "unavailable";
|
|
30
|
-
case Healthy:
|
|
31
|
-
return "healthy";
|
|
32
|
-
case Disconnected:
|
|
33
|
-
return "disconnected";
|
|
34
|
-
case Error:
|
|
35
|
-
return "error";
|
|
36
|
-
case NoMedia:
|
|
37
|
-
return "no_media";
|
|
38
|
-
default:
|
|
39
|
-
return "unknown";
|
|
40
|
-
}
|
|
41
|
-
}
|
|
11
|
+
constexpr size_t BUFFER_SIZE = MAX_PATH + 1;
|
|
42
12
|
|
|
43
13
|
} // namespace FSMeta
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/windows/hidden.cpp
|
|
2
|
+
#include "hidden.h"
|
|
3
|
+
#include "error_utils.h"
|
|
4
|
+
#include <windows.h>
|
|
5
|
+
|
|
6
|
+
namespace FSMeta {
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
// Utility class for path conversion
|
|
10
|
+
class PathConverter {
|
|
11
|
+
public:
|
|
12
|
+
static std::wstring ToWString(const std::string &path) {
|
|
13
|
+
if (path.empty()) {
|
|
14
|
+
return std::wstring();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Pre-calculate required buffer size
|
|
18
|
+
int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(),
|
|
19
|
+
static_cast<int>(path.length()), nullptr, 0);
|
|
20
|
+
if (wlen == 0) {
|
|
21
|
+
throw FSException("Path conversion", GetLastError());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Reserve exact size needed
|
|
25
|
+
std::wstring wpath(wlen, 0);
|
|
26
|
+
if (!MultiByteToWideChar(CP_UTF8, 0, path.c_str(),
|
|
27
|
+
static_cast<int>(path.length()), &wpath[0],
|
|
28
|
+
wlen)) {
|
|
29
|
+
throw FSException("Path conversion", GetLastError());
|
|
30
|
+
}
|
|
31
|
+
return wpath;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// RAII wrapper for file attributes
|
|
36
|
+
class FileAttributeHandler {
|
|
37
|
+
const std::wstring &path;
|
|
38
|
+
DWORD attributes;
|
|
39
|
+
|
|
40
|
+
public:
|
|
41
|
+
explicit FileAttributeHandler(const std::wstring &p)
|
|
42
|
+
: path(p), attributes(GetFileAttributesW(p.c_str())) {
|
|
43
|
+
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
|
44
|
+
throw FSException("GetFileAttributes", GetLastError());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
bool isHidden() const { return (attributes & FILE_ATTRIBUTE_HIDDEN) != 0; }
|
|
49
|
+
|
|
50
|
+
void setHidden(bool value) {
|
|
51
|
+
DWORD newAttrs = value ? (attributes | FILE_ATTRIBUTE_HIDDEN)
|
|
52
|
+
: (attributes & ~FILE_ATTRIBUTE_HIDDEN);
|
|
53
|
+
|
|
54
|
+
if (!SetFileAttributesW(path.c_str(), newAttrs)) {
|
|
55
|
+
throw FSException("SetFileAttributes", GetLastError());
|
|
56
|
+
}
|
|
57
|
+
attributes = newAttrs;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
} // anonymous namespace
|
|
61
|
+
|
|
62
|
+
class GetHiddenWorker : public Napi::AsyncWorker {
|
|
63
|
+
const std::string path;
|
|
64
|
+
bool result;
|
|
65
|
+
Napi::Promise::Deferred deferred;
|
|
66
|
+
|
|
67
|
+
public:
|
|
68
|
+
GetHiddenWorker(Napi::Env env, std::string p, Napi::Promise::Deferred def)
|
|
69
|
+
: Napi::AsyncWorker(env), path(std::move(p)), deferred(std::move(def)) {}
|
|
70
|
+
|
|
71
|
+
void Execute() override {
|
|
72
|
+
try {
|
|
73
|
+
auto wpath = PathConverter::ToWString(path);
|
|
74
|
+
FileAttributeHandler handler(wpath);
|
|
75
|
+
result = handler.isHidden();
|
|
76
|
+
} catch (const FSException &e) {
|
|
77
|
+
SetError(e.what());
|
|
78
|
+
} catch (const std::exception &e) {
|
|
79
|
+
SetError(std::string("Unexpected error: ") + e.what());
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void OnOK() override { deferred.Resolve(Napi::Boolean::New(Env(), result)); }
|
|
84
|
+
|
|
85
|
+
void OnError(const Napi::Error &e) override { deferred.Reject(e.Value()); }
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
class SetHiddenWorker : public Napi::AsyncWorker {
|
|
89
|
+
const std::string path;
|
|
90
|
+
const bool value;
|
|
91
|
+
Napi::Promise::Deferred deferred;
|
|
92
|
+
|
|
93
|
+
public:
|
|
94
|
+
SetHiddenWorker(Napi::Env env, std::string p, bool v,
|
|
95
|
+
Napi::Promise::Deferred def)
|
|
96
|
+
: Napi::AsyncWorker(env), path(std::move(p)), value(v),
|
|
97
|
+
deferred(std::move(def)) {}
|
|
98
|
+
|
|
99
|
+
void Execute() override {
|
|
100
|
+
try {
|
|
101
|
+
auto wpath = PathConverter::ToWString(path);
|
|
102
|
+
FileAttributeHandler handler(wpath);
|
|
103
|
+
handler.setHidden(value);
|
|
104
|
+
} catch (const FSException &e) {
|
|
105
|
+
SetError(e.what());
|
|
106
|
+
} catch (const std::exception &e) {
|
|
107
|
+
SetError(std::string("Unexpected error: ") + e.what());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
void OnOK() override { deferred.Resolve(Napi::Boolean::New(Env(), true)); }
|
|
112
|
+
|
|
113
|
+
void OnError(const Napi::Error &e) override { deferred.Reject(e.Value()); }
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
Napi::Promise GetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
117
|
+
Napi::Env env = info.Env();
|
|
118
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
122
|
+
throw Napi::TypeError::New(env, "String path expected");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
std::string path = info[0].As<Napi::String>();
|
|
126
|
+
auto *worker =
|
|
127
|
+
new GetHiddenWorker(env, std::move(path), std::move(deferred));
|
|
128
|
+
worker->Queue();
|
|
129
|
+
|
|
130
|
+
return deferred.Promise();
|
|
131
|
+
} catch (const Napi::Error &e) {
|
|
132
|
+
deferred.Reject(e.Value());
|
|
133
|
+
return deferred.Promise();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Napi::Promise SetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
138
|
+
Napi::Env env = info.Env();
|
|
139
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
if (info.Length() < 2 || !info[0].IsString() || !info[1].IsBoolean()) {
|
|
143
|
+
throw Napi::TypeError::New(env, "String path and boolean value expected");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
std::string path = info[0].As<Napi::String>();
|
|
147
|
+
bool value = info[1].As<Napi::Boolean>();
|
|
148
|
+
|
|
149
|
+
auto *worker =
|
|
150
|
+
new SetHiddenWorker(env, std::move(path), value, std::move(deferred));
|
|
151
|
+
worker->Queue();
|
|
152
|
+
|
|
153
|
+
return deferred.Promise();
|
|
154
|
+
} catch (const Napi::Error &e) {
|
|
155
|
+
deferred.Reject(e.Value());
|
|
156
|
+
return deferred.Promise();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
} // namespace FSMeta
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/windows/string.h
|
|
2
|
+
|
|
3
|
+
#pragma once
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <windows.h>
|
|
6
|
+
|
|
7
|
+
namespace FSMeta {
|
|
8
|
+
|
|
9
|
+
inline std::string WideToUtf8(const WCHAR *wide) {
|
|
10
|
+
if (!wide || wide[0] == 0)
|
|
11
|
+
return "";
|
|
12
|
+
|
|
13
|
+
int size =
|
|
14
|
+
WideCharToMultiByte(CP_UTF8, 0, wide, -1, nullptr, 0, nullptr, nullptr);
|
|
15
|
+
if (size <= 0)
|
|
16
|
+
return "";
|
|
17
|
+
|
|
18
|
+
std::string result(size - 1, 0);
|
|
19
|
+
WideCharToMultiByte(CP_UTF8, 0, wide, -1, &result[0], size, nullptr, nullptr);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class PathConverter {
|
|
24
|
+
public:
|
|
25
|
+
static std::wstring ToWString(const std::string &path) {
|
|
26
|
+
if (path.empty()) {
|
|
27
|
+
return L"";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(),
|
|
31
|
+
static_cast<int>(path.length()), nullptr, 0);
|
|
32
|
+
|
|
33
|
+
if (wlen == 0) {
|
|
34
|
+
return L"";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
std::wstring wpath(wlen, 0);
|
|
38
|
+
if (!MultiByteToWideChar(CP_UTF8, 0, path.c_str(),
|
|
39
|
+
static_cast<int>(path.length()), &wpath[0],
|
|
40
|
+
wlen)) {
|
|
41
|
+
return L"";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return wpath;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
} // namespace FSMeta
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/windows/system_volume.h
|
|
2
|
+
#pragma once
|
|
3
|
+
#include "../common/debug_log.h"
|
|
4
|
+
#include "string.h"
|
|
5
|
+
#include <PathCch.h>
|
|
6
|
+
#include <shlobj.h> // For SHGetFolderPathW and CSIDL constants
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <windows.h>
|
|
9
|
+
|
|
10
|
+
// If FILE_SUPPORTS_SYSTEM_PATHS is not defined (older SDK)
|
|
11
|
+
#ifndef FILE_SUPPORTS_SYSTEM_PATHS
|
|
12
|
+
#define FILE_SUPPORTS_SYSTEM_PATHS 0x00100000
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
#ifndef FILE_SUPPORTS_SYSTEM_FILES
|
|
16
|
+
#define FILE_SUPPORTS_SYSTEM_FILES 0x00200000
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
namespace FSMeta {
|
|
20
|
+
|
|
21
|
+
inline bool IsSystemVolume(const std::wstring &drive) {
|
|
22
|
+
WCHAR systemRoot[MAX_PATH];
|
|
23
|
+
if (SUCCEEDED(
|
|
24
|
+
SHGetFolderPathW(nullptr, CSIDL_WINDOWS, nullptr, 0, systemRoot))) {
|
|
25
|
+
WCHAR rootPath[4];
|
|
26
|
+
wcsncpy_s(rootPath, systemRoot, 3);
|
|
27
|
+
rootPath[3] = '\0';
|
|
28
|
+
|
|
29
|
+
if (_wcsnicmp(drive.c_str(), rootPath, 2) == 0) {
|
|
30
|
+
DEBUG_LOG("[IsSystemVolume] %ls is a system volume", drive.c_str());
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Modern volume properties check
|
|
36
|
+
DWORD volumeFlags = 0;
|
|
37
|
+
wchar_t fileSystemName[MAX_PATH + 1] = {0};
|
|
38
|
+
|
|
39
|
+
if (GetVolumeInformationW(drive.c_str(), nullptr, 0, nullptr, nullptr,
|
|
40
|
+
&volumeFlags, fileSystemName, MAX_PATH)) {
|
|
41
|
+
|
|
42
|
+
// Check for modern system volume indicators
|
|
43
|
+
if ((volumeFlags & FILE_SUPPORTS_SYSTEM_PATHS) ||
|
|
44
|
+
(volumeFlags & FILE_SUPPORTS_SYSTEM_FILES)) {
|
|
45
|
+
DEBUG_LOG("[IsSystemVolume] %ls has system volume flags (0x%08X)",
|
|
46
|
+
drive.c_str(), volumeFlags);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
DEBUG_LOG("[IsSystemVolume] %ls GetVolumeInformationW failed: %lu",
|
|
51
|
+
drive.c_str(), GetLastError());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
DEBUG_LOG("[IsSystemVolume] %ls is not a system volume", drive.c_str());
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Legacy compatibility overload
|
|
59
|
+
inline bool IsSystemVolume(const WCHAR *drive) {
|
|
60
|
+
return IsSystemVolume(std::wstring(drive));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
} // namespace FSMeta
|