[gjs: 1/2] Gio: Add overrides for File.set_attribute and FileInfo.set_attribute




commit 0a28bf36b1fb073f4b01400a57d88dbd75f8c8f3
Author: Philip Chimento <philip chimento gmail com>
Date:   Mon Aug 8 23:56:43 2022 -0700

    Gio: Add overrides for File.set_attribute and FileInfo.set_attribute
    
    These functions can crash if used from JS, because they take a raw
    pointer. Add an override that calls the appropriate type-safe method
    instead, on a best-effort basis; not all of these exist. In any case, the
    crashes should be prevented.
    
    Closes: #496

 installed-tests/js/testGio.js | 79 +++++++++++++++++++++++++++++++++++++++++++
 modules/core/overrides/Gio.js | 61 +++++++++++++++++++++++++++++++++
 2 files changed, 140 insertions(+)
---
diff --git a/installed-tests/js/testGio.js b/installed-tests/js/testGio.js
index fc94bb587..c2281faec 100644
--- a/installed-tests/js/testGio.js
+++ b/installed-tests/js/testGio.js
@@ -341,3 +341,82 @@ describe('Gio.FileEnumerator overrides', function () {
         expect(count).toBeGreaterThan(0);
     });
 });
+
+describe('Non-introspectable file attribute overrides', function () {
+    let numExpectedWarnings, file, info;
+    const flags = [Gio.FileQueryInfoFlags.NONE, null];
+
+    function expectWarnings(count) {
+        numExpectedWarnings = count;
+        for (let c = 0; c < count; c++) {
+            GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+                '*not introspectable*');
+        }
+    }
+
+    function assertWarnings(testName) {
+        for (let c = 0; c < numExpectedWarnings; c++) {
+            GLib.test_assert_expected_messages_internal('Gjs', 'testGio.js', 0,
+                `test Gio.${testName}`);
+        }
+        numExpectedWarnings = 0;
+    }
+
+    beforeEach(function () {
+        numExpectedWarnings = 0;
+        [file] = Gio.File.new_tmp('XXXXXX');
+        info = file.query_info('standard::*', ...flags);
+    });
+
+    it('invalid means unsetting the attribute', function () {
+        expectWarnings(2);
+        expect(() =>
+            file.set_attribute('custom::remove', Gio.FileAttributeType.INVALID, null, ...flags))
+            .toThrowError(/not introspectable/);
+        expect(() => info.set_attribute('custom::remove', Gio.FileAttributeType.INVALID)).not.toThrow();
+        assertWarnings();
+    });
+
+    it('works for boolean', function () {
+        expectWarnings(2);
+        expect(() =>
+            file.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, Gio.FileAttributeType.BOOLEAN, false, 
...flags))
+            .toThrowError(/not introspectable/);
+        expect(() => info.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, 
Gio.FileAttributeType.BOOLEAN, false))
+            .not.toThrow();
+        assertWarnings();
+    });
+
+    it('works for uint32', function () {
+        expectWarnings(2);
+        expect(() => file.set_attribute(Gio.FILE_ATTRIBUTE_TIME_MODIFIED_USEC, Gio.FileAttributeType.UINT32, 
123456, ...flags))
+            .not.toThrow();
+        expect(() => info.set_attribute(Gio.FILE_ATTRIBUTE_TIME_MODIFIED_USEC, Gio.FileAttributeType.UINT32, 
654321))
+            .not.toThrow();
+        assertWarnings();
+    });
+
+    it('works for uint64', function () {
+        expectWarnings(2);
+        expect(() => file.set_attribute(Gio.FILE_ATTRIBUTE_TIME_MODIFIED, Gio.FileAttributeType.UINT64, 
Date.now() / 1000, ...flags))
+            .not.toThrow();
+        expect(() => info.set_attribute(Gio.FILE_ATTRIBUTE_TIME_MODIFIED, Gio.FileAttributeType.UINT64, 
Date.now() / 1000))
+            .not.toThrow();
+        assertWarnings();
+    });
+
+    it('works for object', function () {
+        expectWarnings(2);
+        const icon = Gio.ThemedIcon.new_from_names(['list-add-symbolic']);
+        expect(() =>
+            file.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileAttributeType.OBJECT, icon, 
...flags))
+            .toThrowError(/not introspectable/);
+        expect(() => info.set_attribute(Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileAttributeType.OBJECT, 
icon))
+            .not.toThrow();
+        assertWarnings();
+    });
+
+    afterEach(function () {
+        file.delete_async(GLib.PRIORITY_DEFAULT, null, (obj, res) => obj.delete_finish(res));
+    });
+});
diff --git a/modules/core/overrides/Gio.js b/modules/core/overrides/Gio.js
index 6303b8023..37df69d9c 100644
--- a/modules/core/overrides/Gio.js
+++ b/modules/core/overrides/Gio.js
@@ -462,6 +462,14 @@ function _promisify(proto, asyncFunc, finishFunc = undefined) {
     };
 }
 
+function _notIntrospectableError(funcName, replacement) {
+    return new Error(`${funcName} is not introspectable. Use ${replacement} instead.`);
+}
+
+function _warnNotIntrospectable(funcName, replacement) {
+    logError(_notIntrospectableError(funcName, replacement));
+}
+
 function _init() {
     Gio = this;
 
@@ -545,6 +553,59 @@ function _init() {
         return this.replace_contents_bytes_async(contents, etag, make_backup, flags, cancellable, callback);
     };
 
+    // Best-effort attempt to replace set_attribute(), which is not
+    // introspectable due to the pointer argument
+    Gio.File.prototype.set_attribute = function set_attribute(attribute, type, value, flags, cancellable) {
+        _warnNotIntrospectable('Gio.File.prototype.set_attribute', 'set_attribute_{type}');
+
+        switch (type) {
+        case Gio.FileAttributeType.STRING:
+            return this.set_attribute_string(attribute, value, flags, cancellable);
+        case Gio.FileAttributeType.BYTE_STRING:
+            return this.set_attribute_byte_string(attribute, value, flags, cancellable);
+        case Gio.FileAttributeType.UINT32:
+            return this.set_attribute_uint32(attribute, value, flags, cancellable);
+        case Gio.FileAttributeType.INT32:
+            return this.set_attribute_int32(attribute, value, flags, cancellable);
+        case Gio.FileAttributeType.UINT64:
+            return this.set_attribute_uint64(attribute, value, flags, cancellable);
+        case Gio.FileAttributeType.INT64:
+            return this.set_attribute_int64(attribute, value, flags, cancellable);
+        case Gio.FileAttributeType.INVALID:
+        case Gio.FileAttributeType.BOOLEAN:
+        case Gio.FileAttributeType.OBJECT:
+        case Gio.FileAttributeType.STRINGV:
+            throw _notIntrospectableError('This attribute type', 'Gio.FileInfo');
+        }
+    };
+
+    Gio.FileInfo.prototype.set_attribute = function set_attribute(attribute, type, value) {
+        _warnNotIntrospectable('Gio.FileInfo.prototype.set_attribute', 'set_attribute_{type}');
+
+        switch (type) {
+        case Gio.FileAttributeType.INVALID:
+            return this.remove_attribute(attribute);
+        case Gio.FileAttributeType.STRING:
+            return this.set_attribute_string(attribute, value);
+        case Gio.FileAttributeType.BYTE_STRING:
+            return this.set_attribute_byte_string(attribute, value);
+        case Gio.FileAttributeType.BOOLEAN:
+            return this.set_attribute_boolean(attribute, value);
+        case Gio.FileAttributeType.UINT32:
+            return this.set_attribute_uint32(attribute, value);
+        case Gio.FileAttributeType.INT32:
+            return this.set_attribute_int32(attribute, value);
+        case Gio.FileAttributeType.UINT64:
+            return this.set_attribute_uint64(attribute, value);
+        case Gio.FileAttributeType.INT64:
+            return this.set_attribute_int64(attribute, value);
+        case Gio.FileAttributeType.OBJECT:
+            return this.set_attribute_object(attribute, value);
+        case Gio.FileAttributeType.STRINGV:
+            return this.set_attribute_stringv(attribute, value);
+        }
+    };
+
     Gio.FileEnumerator.prototype[Symbol.iterator] = function* FileEnumeratorIterator() {
         while (true) {
             try {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]