@photostructure/fs-metadata 0.0.1

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.
@@ -0,0 +1,87 @@
1
+ // src/linux/gio_worker.cpp
2
+ #ifdef ENABLE_GIO
3
+
4
+ #include "gio_worker.h"
5
+ #include <gio/gio.h>
6
+ #include <memory>
7
+ #include <stdexcept>
8
+
9
+ namespace FSMeta {
10
+ namespace gio {
11
+
12
+ GioMountPointsWorker::GioMountPointsWorker(
13
+ const Napi::Promise::Deferred &deferred)
14
+ : Napi::AsyncWorker(deferred.Env()), deferred_(deferred) {}
15
+
16
+ GioMountPointsWorker::~GioMountPointsWorker() { mountPoints.clear(); }
17
+
18
+ void GioMountPointsWorker::Execute() {
19
+ try {
20
+ GVolumeMonitor *monitor = g_volume_monitor_get();
21
+ if (!monitor) {
22
+ throw std::runtime_error("Failed to get GVolumeMonitor");
23
+ }
24
+
25
+ GList *mounts = g_volume_monitor_get_mounts(monitor);
26
+ if (!mounts) {
27
+ return;
28
+ }
29
+
30
+ for (GList *l = mounts; l != nullptr; l = l->next) {
31
+ GMount *mount = G_MOUNT(l->data);
32
+ if (!G_IS_MOUNT(mount)) {
33
+ continue;
34
+ }
35
+
36
+ GFile *root = g_mount_get_root(mount);
37
+ if (!G_IS_FILE(root)) {
38
+ continue;
39
+ }
40
+
41
+ char *path = g_file_get_path(root);
42
+ char *fs_type = g_mount_get_name(mount);
43
+
44
+ if (path && fs_type) {
45
+ MountPoint point{};
46
+ point.mountPoint = path;
47
+ point.fstype = fs_type;
48
+ mountPoints.push_back(point);
49
+ }
50
+
51
+ if (path) {
52
+ g_free(path);
53
+ }
54
+ if (fs_type) {
55
+ g_free(fs_type);
56
+ }
57
+ g_object_unref(root);
58
+ }
59
+
60
+ g_list_free_full(mounts, g_object_unref);
61
+ } catch (const std::exception &e) {
62
+ SetError(e.what());
63
+ }
64
+ }
65
+
66
+ void GioMountPointsWorker::OnOK() {
67
+ Napi::HandleScope scope(Env());
68
+ Napi::Array result = Napi::Array::New(Env());
69
+
70
+ for (size_t i = 0; i < mountPoints.size(); i++) {
71
+ Napi::Object point = Napi::Object::New(Env());
72
+ point.Set("mountPoint", mountPoints[i].mountPoint);
73
+ point.Set("fstype", mountPoints[i].fstype);
74
+ result.Set(i, point);
75
+ }
76
+
77
+ deferred_.Resolve(result);
78
+ }
79
+
80
+ void GioMountPointsWorker::OnError(const Napi::Error &error) {
81
+ deferred_.Reject(error.Value());
82
+ }
83
+
84
+ } // namespace gio
85
+ } // namespace FSMeta
86
+
87
+ #endif // ENABLE_GIO
@@ -0,0 +1,32 @@
1
+ // src/linux/gio_worker.h
2
+
3
+ #pragma once
4
+
5
+ #ifdef ENABLE_GIO
6
+
7
+ #include "../common/mount_point.h"
8
+ #include <napi.h>
9
+ #include <string>
10
+ #include <vector>
11
+
12
+ namespace FSMeta {
13
+ namespace gio {
14
+
15
+ class GioMountPointsWorker : public Napi::AsyncWorker {
16
+ public:
17
+ explicit GioMountPointsWorker(const Napi::Promise::Deferred &deferred);
18
+ ~GioMountPointsWorker() override; // Add override specifier
19
+
20
+ void Execute() override;
21
+ void OnOK() override;
22
+ void OnError(const Napi::Error &error) override;
23
+
24
+ private:
25
+ std::vector<MountPoint> mountPoints;
26
+ Napi::Promise::Deferred deferred_;
27
+ };
28
+
29
+ } // namespace gio
30
+ } // namespace FSMeta
31
+
32
+ #endif // ENABLE_GIO
@@ -0,0 +1,92 @@
1
+ // src/linux/volume_metadata.cpp
2
+ #include "../common/volume_metadata.h"
3
+ #include "../common/error_utils.h"
4
+ #include "../common/metadata_worker.h"
5
+ #include "blkid_cache.h"
6
+ #include <memory>
7
+ #include <sys/statvfs.h>
8
+
9
+ #ifdef ENABLE_GIO
10
+ #include "gio_utils.h"
11
+ #endif
12
+
13
+ namespace FSMeta {
14
+
15
+ class LinuxMetadataWorker : public MetadataWorkerBase {
16
+ public:
17
+ LinuxMetadataWorker(const std::string &path,
18
+ const VolumeMetadataOptions &options,
19
+ const Napi::Promise::Deferred &deferred)
20
+ : MetadataWorkerBase(path, deferred), options_(options) {}
21
+
22
+ void Execute() override {
23
+ try {
24
+ struct statvfs vfs;
25
+ if (statvfs(mountPoint.c_str(), &vfs) != 0) {
26
+ throw FSException(CreateErrorMessage("statvfs", errno));
27
+ }
28
+
29
+ uint64_t blockSize = vfs.f_frsize ? vfs.f_frsize : vfs.f_bsize;
30
+ metadata.remote = false;
31
+ metadata.size = static_cast<double>(blockSize) * vfs.f_blocks;
32
+ metadata.available = static_cast<double>(blockSize) * vfs.f_bavail;
33
+ metadata.used =
34
+ metadata.size - (static_cast<double>(blockSize) * vfs.f_bfree);
35
+
36
+ #ifdef ENABLE_GIO
37
+ try {
38
+ gio::addMountMetadata(mountPoint, metadata);
39
+ } catch (const std::exception &e) {
40
+ metadata.status = std::string("GIO warning: ") + e.what();
41
+ }
42
+ #endif
43
+
44
+ if (!options_.device.empty()) {
45
+ try {
46
+ BlkidCache cache;
47
+ char *uuid =
48
+ blkid_get_tag_value(cache.get(), "UUID", options_.device.c_str());
49
+ if (uuid) {
50
+ metadata.uuid = uuid;
51
+ free(uuid);
52
+ }
53
+
54
+ char *label = blkid_get_tag_value(cache.get(), "LABEL",
55
+ options_.device.c_str());
56
+ if (label) {
57
+ metadata.label = label;
58
+ free(label);
59
+ }
60
+ } catch (const std::exception &e) {
61
+ metadata.status = std::string("Blkid warning: ") + e.what();
62
+ }
63
+ }
64
+ } catch (const std::exception &e) {
65
+ SetError(e.what());
66
+ }
67
+ }
68
+
69
+ private:
70
+ VolumeMetadataOptions options_;
71
+ };
72
+
73
+ Napi::Value GetVolumeMetadata(const Napi::Env &env,
74
+ const std::string &mountPoint,
75
+ const Napi::Object &options) {
76
+ auto deferred = Napi::Promise::Deferred::New(env);
77
+
78
+ VolumeMetadataOptions opts;
79
+ opts.timeoutMs =
80
+ options.Has("timeoutMs")
81
+ ? options.Get("timeoutMs").As<Napi::Number>().Uint32Value()
82
+ : 5000;
83
+ opts.device = options.Has("device")
84
+ ? options.Get("device").As<Napi::String>().Utf8Value()
85
+ : "";
86
+
87
+ auto *worker = new LinuxMetadataWorker(mountPoint, opts, deferred);
88
+ worker->Queue();
89
+ return deferred.Promise();
90
+ }
91
+
92
+ } // namespace FSMeta
@@ -0,0 +1,22 @@
1
+ // src/windows/error_utils.h
2
+ #pragma once
3
+ #include <sstream>
4
+ #include <stdexcept>
5
+ #include <string>
6
+ #include <windows.h>
7
+
8
+ namespace FSMeta {
9
+
10
+ class FSException : public std::runtime_error {
11
+ public:
12
+ explicit FSException(const std::string &message)
13
+ : std::runtime_error(message) {}
14
+ };
15
+
16
+ inline std::string CreateErrorMessage(const char *operation, DWORD error) {
17
+ std::ostringstream oss;
18
+ oss << operation << " failed with error: " << error;
19
+ return oss.str();
20
+ }
21
+
22
+ } // namespace FSMeta
@@ -0,0 +1,43 @@
1
+ // src/windows/fs_meta.h
2
+ #pragma once
3
+ #include "../common/volume_metadata.h"
4
+ #include "../common/volume_mount_points.h"
5
+ #include "./error_utils.h"
6
+ #include <napi.h>
7
+
8
+ #include <string>
9
+
10
+ namespace FSMeta {
11
+
12
+ constexpr size_t ERROR_BUFFER_SIZE = 256;
13
+ constexpr DWORD BUFFER_SIZE = MAX_PATH + 1;
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
+ }
42
+
43
+ } // namespace FSMeta
@@ -0,0 +1,194 @@
1
+ // src/windows/volume_metadata.cpp
2
+ #include "../common/metadata_worker.h"
3
+ #include "../common/mount_point.h"
4
+ #include "./error_utils.h"
5
+ #include "./fs_meta.h"
6
+ #include <iomanip>
7
+ #include <sstream>
8
+ #include <windows.h>
9
+ #include <winnetwk.h>
10
+
11
+ namespace FSMeta {
12
+
13
+ inline std::string FormatVolumeUUID(DWORD serialNumber) {
14
+ std::stringstream ss;
15
+ ss << std::uppercase << std::hex << std::setfill('0') << std::setw(8)
16
+ << serialNumber;
17
+ return ss.str();
18
+ }
19
+
20
+ inline void ValidatePath(const std::string &path) {
21
+ if (path.empty() || path.length() >= MAX_PATH) {
22
+ throw FSException("Invalid path length");
23
+ }
24
+ }
25
+
26
+ // Drive status determination with performance optimization
27
+ DriveStatus GetDriveStatus(const std::string &path) {
28
+ UINT driveType = GetDriveTypeA(path.c_str());
29
+
30
+ // First check if drive is accessible
31
+ std::string mountPoint = path;
32
+ if (mountPoint.back() != '\\') {
33
+ mountPoint += '\\';
34
+ }
35
+
36
+ // Use stack-allocated arrays for better performance
37
+ char volumeName[BUFFER_SIZE] = {0};
38
+ char fileSystem[BUFFER_SIZE] = {0};
39
+ DWORD serialNumber = 0;
40
+ DWORD maxComponentLen = 0;
41
+ DWORD fsFlags = 0;
42
+
43
+ bool isAccessible = GetVolumeInformationA(
44
+ mountPoint.c_str(), volumeName, BUFFER_SIZE, &serialNumber,
45
+ &maxComponentLen, &fsFlags, fileSystem, BUFFER_SIZE);
46
+
47
+ switch (driveType) {
48
+ case DRIVE_UNKNOWN:
49
+ return Unknown;
50
+ case DRIVE_NO_ROOT_DIR:
51
+ return Unavailable;
52
+ case DRIVE_REMOVABLE:
53
+ return isAccessible ? Healthy : Disconnected;
54
+ case DRIVE_FIXED:
55
+ return isAccessible ? Healthy : Error;
56
+ case DRIVE_REMOTE:
57
+ if (!isAccessible) {
58
+ DWORD result =
59
+ WNetGetConnectionA(path.substr(0, 2).c_str(), nullptr, nullptr);
60
+ return (result == ERROR_NOT_CONNECTED) ? Disconnected : Error;
61
+ }
62
+ return Healthy;
63
+ case DRIVE_CDROM:
64
+ return isAccessible ? Healthy : NoMedia;
65
+ case DRIVE_RAMDISK:
66
+ return isAccessible ? Healthy : Error;
67
+ default:
68
+ return Unknown;
69
+ }
70
+ }
71
+
72
+ class GetVolumeMetadataWorker : public FSMeta::MetadataWorkerBase {
73
+ public:
74
+ GetVolumeMetadataWorker(const std::string &mountPoint,
75
+ const Napi::Promise::Deferred &deferred)
76
+ : MetadataWorkerBase(mountPoint, deferred), mountPoint(mountPoint),
77
+ deferred_(deferred) // Initialize deferred_ member
78
+ {
79
+ ValidatePath(mountPoint); // Use mountPoint instead of path
80
+ }
81
+
82
+ void Execute() override {
83
+ try {
84
+ // Get drive status first
85
+ DriveStatus status = GetDriveStatus(mountPoint);
86
+ metadata.status = DriveStatusToString(status);
87
+
88
+ // If drive is not accessible, skip further checks
89
+ if (status == Disconnected || status == Unavailable || status == Error ||
90
+ status == NoMedia) {
91
+ throw FSException("Unhealthy drive status: " +
92
+ std::string(metadata.status));
93
+ }
94
+
95
+ // Use stack-allocated arrays for better performance
96
+ char volumeName[BUFFER_SIZE] = {0};
97
+ char fileSystem[BUFFER_SIZE] = {0};
98
+ DWORD serialNumber = 0;
99
+ DWORD maxComponentLen = 0;
100
+ DWORD fsFlags = 0;
101
+
102
+ if (!GetVolumeInformationA(mountPoint.c_str(), volumeName, BUFFER_SIZE,
103
+ &serialNumber, &maxComponentLen, &fsFlags,
104
+ fileSystem, BUFFER_SIZE)) {
105
+ throw FSException(
106
+ CreateErrorMessage("GetVolumeInformation", GetLastError()));
107
+ }
108
+
109
+ metadata.label = volumeName;
110
+ metadata.fileSystem = fileSystem;
111
+ metadata.uuid = FormatVolumeUUID(serialNumber);
112
+
113
+ // Get disk space information
114
+ ULARGE_INTEGER totalBytes;
115
+ ULARGE_INTEGER freeBytes;
116
+ ULARGE_INTEGER totalFreeBytes;
117
+
118
+ if (!GetDiskFreeSpaceExA(mountPoint.c_str(), &freeBytes, &totalBytes,
119
+ &totalFreeBytes)) {
120
+ throw FSException(
121
+ CreateErrorMessage("GetDiskFreeSpaceEx", GetLastError()));
122
+ }
123
+
124
+ metadata.size = static_cast<double>(totalBytes.QuadPart);
125
+ metadata.available = static_cast<double>(freeBytes.QuadPart);
126
+ metadata.used = metadata.size - metadata.available;
127
+
128
+ // Check if drive is remote
129
+ metadata.remote = (GetDriveTypeA(mountPoint.c_str()) == DRIVE_REMOTE);
130
+
131
+ // Get network path if the drive is remote
132
+ if (metadata.remote) {
133
+ char remoteName[BUFFER_SIZE] = {0};
134
+ DWORD length = BUFFER_SIZE;
135
+ DWORD result = WNetGetConnectionA(mountPoint.substr(0, 2).c_str(),
136
+ remoteName, &length);
137
+
138
+ if (result == NO_ERROR) {
139
+ metadata.mountFrom = remoteName;
140
+ }
141
+ }
142
+ } catch (const std::exception &e) {
143
+ SetError(e.what());
144
+ }
145
+ }
146
+
147
+ void OnOK() override
148
+
149
+ // TODO: use the base worker's OnOK instead (but currently that results in
150
+ // incorrect numerical values)
151
+ {
152
+ auto env = Env();
153
+
154
+ Napi::HandleScope scope(env);
155
+ Napi::Object result = Napi::Object::New(env);
156
+
157
+ result.Set("label", metadata.label.empty()
158
+ ? env.Null()
159
+ : Napi::String::New(env, metadata.label));
160
+ result.Set("fileSystem", metadata.fileSystem.empty()
161
+ ? env.Null()
162
+ : Napi::String::New(env, metadata.fileSystem));
163
+ result.Set("size", Napi::Number::New(env, metadata.size));
164
+ result.Set("used", Napi::Number::New(env, metadata.used));
165
+ result.Set("available", Napi::Number::New(env, metadata.available));
166
+ result.Set("uuid", metadata.uuid.empty()
167
+ ? env.Null()
168
+ : Napi::String::New(env, metadata.uuid));
169
+ result.Set("remote", Napi::Boolean::New(env, metadata.remote));
170
+ result.Set("status", Napi::String::New(env, metadata.status));
171
+
172
+ if (!metadata.mountFrom.empty()) {
173
+ result.Set("mountFrom", Napi::String::New(env, metadata.mountFrom));
174
+ }
175
+
176
+ deferred_.Resolve(result);
177
+ }
178
+
179
+ private:
180
+ std::string mountPoint;
181
+ Napi::Promise::Deferred deferred_;
182
+ VolumeMetadata metadata;
183
+ };
184
+
185
+ Napi::Value GetVolumeMetadata(const Napi::Env &env,
186
+ const std::string &mountPoint,
187
+ const Napi::Object &options) {
188
+ auto deferred = Napi::Promise::Deferred::New(env);
189
+ auto *worker = new GetVolumeMetadataWorker(mountPoint, deferred);
190
+ worker->Queue();
191
+ return deferred.Promise();
192
+ }
193
+
194
+ } // namespace FSMeta
@@ -0,0 +1,62 @@
1
+ // src/windows/volume_mount_points.cpp
2
+ #include "../common/metadata_worker.h" // Add this include
3
+ #include "./fs_meta.h"
4
+ #include <sstream>
5
+ #include <windows.h>
6
+ #include <winnetwk.h>
7
+
8
+ namespace FSMeta {
9
+
10
+ class GetVolumeMountPointsWorker
11
+ : public MetadataWorkerBase { // Inherit from base class
12
+ public:
13
+ explicit GetVolumeMountPointsWorker(const Napi::Promise::Deferred &deferred)
14
+ : MetadataWorkerBase("", deferred) {
15
+ } // Pass empty mountPoint since we list all
16
+
17
+ void Execute() override {
18
+ try {
19
+ DWORD length = GetLogicalDriveStringsA(0, nullptr);
20
+ if (length == 0) {
21
+ throw FSException(
22
+ CreateErrorMessage("GetLogicalDriveStrings", GetLastError()));
23
+ }
24
+
25
+ std::vector<char> driveStrings(length);
26
+ if (GetLogicalDriveStringsA(length, driveStrings.data()) == 0) {
27
+ throw FSException(
28
+ CreateErrorMessage("GetLogicalDriveStrings data", GetLastError()));
29
+ }
30
+
31
+ for (const char *drive = driveStrings.data(); *drive;
32
+ drive += strlen(drive) + 1) {
33
+ mountPoints.push_back(std::string(drive));
34
+ }
35
+ } catch (const std::exception &e) {
36
+ SetError(e.what());
37
+ }
38
+ }
39
+
40
+ void OnOK() override {
41
+ Napi::HandleScope scope(Env());
42
+ auto result = Napi::Array::New(Env(), mountPoints.size());
43
+
44
+ for (size_t i = 0; i < mountPoints.size(); i++) {
45
+ result.Set(i, Napi::String::New(Env(), mountPoints[i]));
46
+ }
47
+
48
+ deferred_.Resolve(result);
49
+ }
50
+
51
+ private:
52
+ std::vector<std::string> mountPoints;
53
+ };
54
+
55
+ Napi::Value GetVolumeMountPoints(Napi::Env env) {
56
+ auto deferred = Napi::Promise::Deferred::New(env);
57
+ auto *worker = new GetVolumeMountPointsWorker(deferred);
58
+ worker->Queue();
59
+ return deferred.Promise();
60
+ }
61
+
62
+ } // namespace FSMeta