Skip to content

Commit

Permalink
Add local folder source
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucki committed Dec 12, 2022
1 parent a43d288 commit 9b9c5ee
Show file tree
Hide file tree
Showing 25 changed files with 1,058 additions and 829 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ Requires [`blueprint-compiler`](https://jwestman.pages.gitlab.gnome.org/blueprin
Run `./build.sh` to compile ui files.

## Adding predefined sources
1. Build UI for settings using the [blueprint-compiler](https://jwestman.pages.gitlab.gnome.org/blueprint-compiler/) language in `…/ui/my_source.blp` - see [Workbench](https://apps.gnome.org/app/re.sonny.Workbench/) for a live preview editor.
1. Build UI for settings using the [blueprint-compiler](https://jwestman.pages.gitlab.gnome.org/blueprint-compiler/) language in `…/ui/mySource.blp` - see [Workbench](https://apps.gnome.org/app/re.sonny.Workbench/) for a live preview editor.
* Add the file to `build.sh`
1. Create a settings layout to the `…/schemas/….gschema.xml`
1. Create your logic hooking the settings in a `…/ui/my_source.js`
1. Add the new source to `…/ui/source_row.js`
* As string for the the ComboRow in `…/ui/source_row.blp`
1. Create a adapter to read the settings and fetching the images and additional information in `…/sourceAdapter.js`
1. Create your logic hooking the settings in a `…/ui/mySource.js`
1. Add the new source to `…/ui/sourceRow.js`
* As string for the the ComboRow in `…/ui/sourceRow.blp`
1. Create a adapter to read the settings and fetching the images and additional information in `…/adapter/myAdapter.js` by extending the `BaseAdapter`.
* Add your adapter to `…/wallpaperController.js`

## Support Me
Expand Down
9 changes: 5 additions & 4 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ glib-compile-schemas "$BASEDIR/schemas/"

# cd "$BASEDIR/ui" || exit 1
blueprint-compiler batch-compile "$BASEDIR/ui" "$BASEDIR/ui" \
"$BASEDIR/ui/generic_json.blp" \
"$BASEDIR/ui/page_general.blp" \
"$BASEDIR/ui/page_sources.blp" \
"$BASEDIR/ui/genericJson.blp" \
"$BASEDIR/ui/localFolder.blp" \
"$BASEDIR/ui/pageGeneral.blp" \
"$BASEDIR/ui/pageSources.blp" \
"$BASEDIR/ui/reddit.blp" \
"$BASEDIR/ui/source_row.blp" \
"$BASEDIR/ui/sourceRow.blp" \
"$BASEDIR/ui/unsplash.blp" \
"$BASEDIR/ui/wallhaven.blp"

Expand Down
110 changes: 110 additions & 0 deletions [email protected]/adapter/baseAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
const Gio = imports.gi.Gio;

const Self = imports.misc.extensionUtils.getCurrentExtension();
const LoggerModule = Self.imports.logger;

/*
libSoup is accessed through the SoupBowl wrapper to support libSoup3 and libSoup2.4 simultaneously in the extension
runtime and in the preferences window.
*/
const SoupBowl = Self.imports.soupBowl;

var BaseAdapter = class {
_wallpaperLocation = null;

constructor(wallpaperLocation) {
this.logger = new LoggerModule.Logger('RWG3', 'BaseAdapter');

this._wallpaperLocation = wallpaperLocation;
}

/**
* Retrieves a new url for an image and calls the given callback with an HistoryEntry as parameter.
* The history element will be null and the error will be set if an error occurred.
*
* @param callback(historyElement, error)
*/
requestRandomImage(callback) {
this._error("requestRandomImage not implemented", callback);
}

fileName(uri) {
while (this._isURIEncoded(uri)) {
uri = decodeURIComponent(uri);
}

let base = uri.substring(uri.lastIndexOf('/') + 1);
if (base.indexOf('?') >= 0) {
base = base.substr(0, base.indexOf('?'));
}
return base;
}

/**
* copy file from uri to local wallpaper directory and calls the given callback with the name and the full filepath
* of the written file as parameter.
* @param uri
* @param callback(name, path, error)
*/
fetchFile(uri, callback) {
//extract the name from the url and
let date = new Date();
let name = date.getTime() + '_' + this.fileName(uri); // timestamp ensures uniqueness

let bowl = new SoupBowl.Bowl();

let file = Gio.file_new_for_path(this._wallpaperLocation + String(name));
let fstream = file.replace(null, false, Gio.FileCreateFlags.NONE, null);

// start the download
let request = bowl.Soup.Message.new('GET', uri);

bowl.send_and_receive(request, (response_data_bytes) => {
if (!response_data_bytes) {
fstream.close(null);

if (callback) {
callback(null, null, 'Not a valid response');
}

return;
}

try {
fstream.write(response_data_bytes, null);

fstream.close(null);

// call callback with the name and the full filepath of the written file as parameter
if (callback) {
callback(name, file.get_path());
}
} catch (e) {
if (callback) {
callback(null, null, e);
}
}
});
}

_isURIEncoded(uri) {
uri = uri || '';

try {
return uri !== decodeURIComponent(uri);
} catch (err) {
this.logger.error(err);
return false;
}
}

_error(err, callback) {
let error = { "error": err };
this.logger.error(JSON.stringify(error));

if (callback) {
callback(null, error);
}
}

};
105 changes: 105 additions & 0 deletions [email protected]/adapter/genericJson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const ByteArray = imports.byteArray;

const Self = imports.misc.extensionUtils.getCurrentExtension();
const HistoryModule = Self.imports.history;
const JSONPath = Self.imports.jsonpath.jsonpath;
const SettingsModule = Self.imports.settings;
const SoupBowl = Self.imports.soupBowl;

const BaseAdapter = Self.imports.adapter.baseAdapter;

const RWG_SETTINGS_SCHEMA_GENERIC_JSON = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.sources.genericJSON';

var GenericJsonAdapter = class extends BaseAdapter.BaseAdapter {
constructor(id, wallpaperLocation) {
super(wallpaperLocation);
this._jsonPathParser = new JSONPath.JSONPathParser();
let path = `/org/gnome/shell/extensions/space-iflow-randomwallpaper/sources/genericJSON/${id}/`;
this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_GENERIC_JSON, path);
this.bowl = new SoupBowl.Bowl();
}

requestRandomImage(callback) {
let url = this._settings.get("request-url", "string");
url = encodeURI(url);
let message = this.bowl.Soup.Message.new('GET', url);
if (message === null) {
this._error("Could not create request.", callback);
return;
}

this.bowl.send_and_receive(message, (response_body_bytes) => {
try {
const response_body = JSON.parse(ByteArray.toString(response_body_bytes));

let imageJSONPath = this._settings.get("image-path", "string");
let postJSONPath = this._settings.get("post-path", "string");
let domainUrl = this._settings.get("domain", "string");
let authorNameJSONPath = this._settings.get("author-name-path", "string");
let authorUrlJSONPath = this._settings.get("author-url-path", "string");

let identifier = this._settings.get("name", "string");
if (identifier === null || identifier === "") {
identifier = 'Generic JSON Source';
}

let rObject = this._jsonPathParser.access(response_body, imageJSONPath);
let imageDownloadUrl = this._settings.get("image-prefix", "string") + rObject.Object;

// '@random' would yield different results so lets make sure the values stay
// the same as long as the path is identical
let samePath = imageJSONPath.substring(0, this.findFirstDifference(imageJSONPath, postJSONPath));

// count occurrences of '@random' to slice the array later
// https://stackoverflow.com/a/4009768
let occurrences = (samePath.match(/@random/g) || []).length;
let slicedRandomElements = rObject.RandomElements.slice(0, occurrences);

let postUrl = this._jsonPathParser.access(response_body, postJSONPath, slicedRandomElements, false).Object;
postUrl = this._settings.get("post-prefix", "string") + postUrl;
if (typeof postUrl !== 'string' || !postUrl instanceof String) {
postUrl = null;
}

let authorName = this._jsonPathParser.access(response_body, authorNameJSONPath, slicedRandomElements, false).Object;
if (typeof authorName !== 'string' || !authorName instanceof String) {
authorName = null;
}

let authorUrl = this._jsonPathParser.access(response_body, authorUrlJSONPath, slicedRandomElements, false).Object;
authorUrl = this._settings.get("author-url-prefix", "string") + authorUrl;
if (typeof authorUrl !== 'string' || !authorUrl instanceof String) {
authorUrl = null;
}

if (callback) {
let historyEntry = new HistoryModule.HistoryEntry(authorName, identifier, imageDownloadUrl);

if (authorUrl !== null && authorUrl !== "") {
historyEntry.source.authorUrl = authorUrl;
}

if (postUrl !== null && postUrl !== "") {
historyEntry.source.imageLinkUrl = postUrl;
}

if (domainUrl !== null && domainUrl !== "") {
historyEntry.source.sourceUrl = domainUrl;
}

callback(historyEntry);
}
} catch (e) {
this._error("Unexpected response. (" + e + ")", callback);
}
});
}

// https://stackoverflow.com/a/32859917
findFirstDifference(jsonPath1, jsonPath2) {
let i = 0;
if (jsonPath1 === jsonPath2) return -1;
while (jsonPath1[i] === jsonPath2[i]) i++;
return i;
}
};
87 changes: 87 additions & 0 deletions [email protected]/adapter/localFolder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const Gio = imports.gi.Gio;

const Self = imports.misc.extensionUtils.getCurrentExtension();
const SettingsModule = Self.imports.settings;
const HistoryModule = Self.imports.history;

const BaseAdapter = Self.imports.adapter.baseAdapter;

const RWG_SETTINGS_SCHEMA_LOCAL_FOLDER = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.sources.localFolder';

var LocalFolderAdapter = class extends BaseAdapter.BaseAdapter {
constructor(id, wallpaperLocation) {
super(wallpaperLocation);

let path = `/org/gnome/shell/extensions/space-iflow-randomwallpaper/sources/localFolder/${id}/`;
this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_LOCAL_FOLDER, path);
}

requestRandomImage(callback) {
const folder = Gio.file_new_for_path(this._settings.get('folder', 'string'));
let files = this._listDirectory(folder);

if (files === null || files.length < 1) {
this._error("Empty array.", callback);
}

let randomFile = files[Math.floor(Math.random() * files.length)].get_path();

let identifier = this._settings.get("name", "string");
if (identifier === null || identifier === "") {
identifier = 'Local Folder';
}

if (callback) {
let historyEntry = new HistoryModule.HistoryEntry(null, identifier, randomFile);
historyEntry.source.sourceUrl = this._wallpaperLocation;
callback(historyEntry);
}
}

fetchFile(path, callback) {
let date = new Date();
let sourceFile = Gio.file_new_for_path(path);
let name = `${date.getTime()}_${sourceFile.get_basename()}`
let targetFile = Gio.file_new_for_path(this._wallpaperLocation + String(name));

// https://gjs.guide/guides/gio/file-operations.html#copying-and-moving-files
sourceFile.copy(targetFile, Gio.FileCopyFlags.NONE, null, null);

if (callback) {
callback(name, targetFile.get_path());
}
}

// https://gjs.guide/guides/gio/file-operations.html#recursively-deleting-a-directory
_listDirectory(directory) {
const iterator = directory.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);

let files = [];
while (true) {
const info = iterator.next_file(null);

if (info === null) {
break;
}

const child = iterator.get_child(info);
const type = info.get_file_type();

switch (type) {
case Gio.FileType.DIRECTORY:
files = files.concat(this._listDirectory(child));
break;

default:
break;
}

let contentType = info.get_content_type();
if (contentType === 'image/png' || contentType === 'image/jpeg') {
files.push(child);
}
}

return files;
}
};
Loading

0 comments on commit 9b9c5ee

Please sign in to comment.