Skip to content

Commit

Permalink
Merge pull request #2511 from calebccff/postmarketos
Browse files Browse the repository at this point in the history
plugins: initial postmarketOS support
  • Loading branch information
Flohack74 authored Sep 7, 2022
2 parents 5463ab0 + 75a8675 commit 766d324
Show file tree
Hide file tree
Showing 7 changed files with 483 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/core/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class Core {
}

readConfigFile(file) {
log.verbose("Reading config file: " + file);
return file
? Promise.resolve(
path.isAbsolute(file) ? file : path.join(process.cwd(), file)
Expand Down
3 changes: 3 additions & 0 deletions src/core/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const { CancelablePromise } = require("cancelable-promise");
const AdbPlugin = require("./adb/plugin.js");
const AsteroidOsPlugin = require("./asteroid_os/plugin.js");
const LineageOSPlugin = require("./lineage_os/plugin.js");
const PostmarketOSPlugin = require("./postmarketos/plugin.js");
const CorePlugin = require("./core/plugin.js");
const FastbootPlugin = require("./fastboot/plugin.js");
const HeimdallPlugin = require("./heimdall/plugin.js");
Expand All @@ -34,6 +35,7 @@ const SystemimagePlugin = require("./systemimage/plugin.js");
* @property {AdbPlugin} plugins.adb adb plugin
* @property {AsteroidOsPlugin} plugins.asteroid_os AteroidOS plugin
* @property {LineageOSPlugin} plugins.lineage_os LineageOS plugin
* @property {PostmarketOSPlugin} plugins.postmarketos postmarketOS plugin
* @property {CorePlugin} plugins.core core plugin
* @property {FastbootPlugin} plugins.fastboot fastboot plugin
* @property {HeimdallPlugin} plugins.heimdall heimdall plugin
Expand All @@ -50,6 +52,7 @@ class PluginIndex {
adb: new AdbPlugin(...pluginArgs),
asteroid_os: new AsteroidOsPlugin(...pluginArgs),
lineage_os: new LineageOSPlugin(...pluginArgs),
postmarketos: new PostmarketOSPlugin(...pluginArgs),
core: new CorePlugin(...pluginArgs),
fastboot: new FastbootPlugin(...pluginArgs),
heimdall: new HeimdallPlugin(...pluginArgs),
Expand Down
2 changes: 1 addition & 1 deletion src/core/plugins/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe("PluginIndex", () => {
});
describe("getPluginMappable()", () => {
it("should return plugin array", () =>
expect(pluginIndex.getPluginMappable()).toHaveLength(7));
expect(pluginIndex.getPluginMappable()).toHaveLength(8));
});
["init", "kill"].forEach(target =>
describe(`${target}()`, () => {
Expand Down
107 changes: 107 additions & 0 deletions src/core/plugins/postmarketos/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use strict";

/*
* Copyright (C) 2020-2021 UBports Foundation <[email protected]>
* Copyright (c) 2022 Caleb Connolly <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

const axios = require("axios");

/** @module postmarketOS */

const baseURL = "https://images.postmarketos.org";

const api = axios.create({ baseURL, timeout: 15000 });

/**
* get interfaces from api
* @param {String} device device codename
* @returns {Promise<Array<String>>} interfaces
* @throws {Error} message "unsupported" if 404 not found
*/
const getInterfaces = device =>
api
.get("/bpo/index.json")
.then(({ data }) => {
const devices = data.releases.find(c => c.name === "edge").devices;
const interfaces = devices
.find(d => d.name.includes(device))
.interfaces.map(i => i.name);
return interfaces.map(i => {
if (i === "phosh") return { value: i, label: "Phosh" };
if (i === "plasma-mobile") return { value: i, label: "Plasma Mobile" };
if (i === "sxmo-de-sway") return { value: i, label: "SXMO Sway" };
return { value: i, label: i };
});
})
.catch(error => {
if (error?.response?.status === 404) throw new Error("404");
throw error;
});

/**
* get images from api
* @param {String} release release
* @param {String} ui user interface
* @param {String} device device codename
* @returns {Promise<Array<Object>>} images array
* @throws {Error} message "no network" if request failed
*/
const getImages = (release, ui, device) =>
api
.get("/bpo/index.json")
.then(({ data }) => {
const rel = data.releases.find(c => c.name === release);
const dev = rel.devices.find(d => d.name.includes(device));
const images = dev.interfaces.find(i => i.name === ui).images;
// The first two are the latest rootfs and boot image
const ts_latest = images[0].timestamp;
return images
.filter(i => i.timestamp === ts_latest)
.map(i => ({
url: i.url,
checksum: {
sum: i.sha256,
algorithm: "sha256"
}
}));
})
.catch(error => {
if (error?.response?.status === 404) throw new Error("404");
throw error;
});

/**
* get releases from api
* @param {String} device device codename
* @returns {Promise<Array<String>>} releases
* @throws {Error} message "unsupported" if 404 not found
*/
const getReleases = device =>
api
.get("/bpo/index.json")
.then(({ data }) => {
const releases = data.releases;
return releases
.filter(release => release.devices.find(d => d.name.includes(device)))
.map(release => release.name);
})
.catch(error => {
if (error?.response?.status === 404) throw new Error("404");
throw error;
});

module.exports = { getInterfaces, getImages, getReleases };
132 changes: 132 additions & 0 deletions src/core/plugins/postmarketos/api.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
const axios = require("axios");
jest.mock("axios");
axios.create.mockReturnValue(axios);
const api = require("./api.js");

const MOCK_DATA = {
releases: [
{
name: "edge",
devices: [
{
name: "somedevice",
interfaces: [
{ name: "phosh" },
{
name: "plasma-mobile",
images: [
{
timestamp: 0,
url: "someurl",
sha256: "sha256-first"
},
{
timestamp: 0,
url: "someurl2",
sha256: "sha256-other"
}
]
},
{ name: "sxmo-de-sway" },
{ name: "other" }
]
}
]
}
]
};

describe("postmarketos api", () => {
beforeEach(() => {
axios.get.mockResolvedValueOnce({
data: MOCK_DATA
});
});

describe("getInterfaces()", () => {
it("should resolve interfaces", async () => {
const result = await api.getInterfaces("somedevice");
expect(result).toContainEqual({
value: "phosh",
label: "Phosh"
});
expect(result).toContainEqual({
value: "plasma-mobile",
label: "Plasma Mobile"
});
expect(result).toContainEqual({
value: "sxmo-de-sway",
label: "SXMO Sway"
});
expect(result).toContainEqual({
value: "other",
label: "other"
});
});

it("should reject on errors", async () => {
axios.get.mockReset();
axios.get.mockRejectedValueOnce({
response: {
status: 404
}
});

const test = () => api.getInterfaces("nonexistent");
await expect(test).rejects.toThrow("404");

axios.get.mockRejectedValueOnce(new Error("other"));
await expect(test).rejects.toThrow("other");
});
});

describe("getImages()", () => {
it("should resolve images", async () => {
const result = await api.getImages("edge", "plasma-mobile", "somedevice");
expect(result).toContainEqual({
url: "someurl",
checksum: {
sum: "sha256-first",
algorithm: "sha256"
}
});
});

it("should throw on 404", async () => {
axios.get.mockReset();
axios.get.mockRejectedValueOnce({
response: {
status: 404
}
});

const test = () => api.getImages("non", "existent", "stuff");
await expect(test).rejects.toThrow("404");

axios.get.mockRejectedValueOnce(new Error("other"));
await expect(test).rejects.toThrow("other");
});
});

describe("getReleases()", () => {
it("should resolve releases", async () => {
const result = await api.getReleases("somedevice");
expect(result).toEqual(["edge"]);
});

it("should throw on 404", async () => {
axios.get.mockReset();
axios.get.mockRejectedValueOnce({
response: {
status: 404
}
});

const test = () => api.getReleases("nonexistent");
await expect(test).rejects.toThrow("404");

axios.get.mockRejectedValueOnce(new Error("other"));
await expect(test).rejects.toThrow("other");
});
});
});
Loading

0 comments on commit 766d324

Please sign in to comment.