~whereswaldon/gio-x

e4e582af919bea5fb06e02463f369e6cee47a122 — Sebastien Binet 4 months ago abf2811
explorer: implement ChooseFiles for Linux

This CL implements ChooseFiles for Linux on top of DBus' org.freedesktop.portal.FileChooser.OpenFile.

Signed-off-by: Sebastien Binet <binet@cern.ch>
1 files changed, 63 insertions(+), 2 deletions(-)

M explorer/explorer_linux.go
M explorer/explorer_linux.go => explorer/explorer_linux.go +63 -2
@@ 277,6 277,67 @@ func (e *Explorer) importFile(extensions ...string) (io.ReadCloser, error) {
	return os.Open(filepath)
}

func (e *Explorer) importFiles(_ ...string) ([]io.ReadCloser, error) {
	return nil, ErrNotAvailable
// importFiles opens a multi-file picker to choose multiple files.
func (e *Explorer) importFiles(extensions ...string) ([]io.ReadCloser, error) {
	var filepaths []string
	if err := e.withDesktopPortal(func(conn *dbus.Conn, desktopPortal dbus.BusObject, config config) error {
		// Invoke the OpenFile method.
		requestHandle := ""
		options := map[string]dbus.Variant{
			"handle_token": dbus.MakeVariant(config.handleToken),
			"multiple":     dbus.MakeVariant(true),
		}
		if len(extensions) > 0 {
			options["filters"] = makeFilter(extensions)
		}
		err := desktopPortal.Call("org.freedesktop.portal.FileChooser.OpenFile", 0, config.parentWindow, "Choose File", options).Store(&requestHandle)
		if err != nil {
			return fmt.Errorf("failed to call OpenFile: %w", err)
		}

		// Make sure we got the request object's path right. Update our subscription otherwise.
		if requestHandle != config.expectedRequestHandle {
			if err := conn.AddMatchSignal(dbus.WithMatchObjectPath(dbus.ObjectPath(requestHandle))); err != nil {
				return fmt.Errorf("failed to subscribe to request: %w", err)
			}
			// Reset signal handling.
			signals := make(chan *dbus.Signal, 1)
			conn.Signal(signals)
			config.signals = signals
		}

		// Wait for the response from the file dialog.
		response := <-config.signals
		uris := extractURIsFromSignal(response)

		// Error if no files were selected.
		if len(uris) < 1 {
			return ErrUserDecline
		}

		filepaths = make([]string, len(uris))
		for i, uri := range uris {
			// Remove the protocol from the URI.
			parsedURL, err := url.Parse(uri)
			if err != nil {
				return fmt.Errorf("failed parsing file path %s: %w", uri, err)
			}
			filepaths[i] = parsedURL.Path
		}
		return nil
	}); err != nil {
		return nil, err
	}
	rcs := make([]io.ReadCloser, 0, len(filepaths))
	for _, fname := range filepaths {
		rc, err := os.Open(fname)
		if err != nil {
			for _, rc := range rcs {
				_ = rc.Close()
			}
			return nil, err
		}
		rcs = append(rcs, rc)
	}
	return rcs, nil
}