You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-02 22:49:09 +02:00
Desktop: Download plugins from GitHub release
This commit is contained in:
@@ -4,7 +4,7 @@ import { setupDatabaseAndSynchronizer, switchClient, supportDir, createTempDir }
|
|||||||
|
|
||||||
async function newRepoApi(): Promise<RepositoryApi> {
|
async function newRepoApi(): Promise<RepositoryApi> {
|
||||||
const repo = new RepositoryApi(`${supportDir}/pluginRepo`, await createTempDir());
|
const repo = new RepositoryApi(`${supportDir}/pluginRepo`, await createTempDir());
|
||||||
await repo.loadManifests();
|
await repo.initialize();
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export default function(props: Props) {
|
|||||||
|
|
||||||
let loadError: Error = null;
|
let loadError: Error = null;
|
||||||
try {
|
try {
|
||||||
await repoApi().loadManifests();
|
await repoApi().initialize();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
loadError = error;
|
loadError = error;
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
|
import Logger from '../../Logger';
|
||||||
import shim from '../../shim';
|
import shim from '../../shim';
|
||||||
import { PluginManifest } from './utils/types';
|
import { PluginManifest } from './utils/types';
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const compareVersions = require('compare-versions');
|
const compareVersions = require('compare-versions');
|
||||||
|
|
||||||
|
const logger = Logger.create('RepositoryApi');
|
||||||
|
|
||||||
|
interface ReleaseAsset {
|
||||||
|
name: string;
|
||||||
|
browser_download_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Release {
|
||||||
|
upload_url: string;
|
||||||
|
assets: ReleaseAsset[];
|
||||||
|
}
|
||||||
|
|
||||||
export default class RepositoryApi {
|
export default class RepositoryApi {
|
||||||
|
|
||||||
// As a base URL, this class can support either a remote repository or a
|
// As a base URL, this class can support either a remote repository or a
|
||||||
@@ -14,6 +27,7 @@ export default class RepositoryApi {
|
|||||||
// Later on, other repo types could be supported.
|
// Later on, other repo types could be supported.
|
||||||
private baseUrl_: string;
|
private baseUrl_: string;
|
||||||
private tempDir_: string;
|
private tempDir_: string;
|
||||||
|
private release_: Release = null;
|
||||||
private manifests_: PluginManifest[] = null;
|
private manifests_: PluginManifest[] = null;
|
||||||
|
|
||||||
public constructor(baseUrl: string, tempDir: string) {
|
public constructor(baseUrl: string, tempDir: string) {
|
||||||
@@ -21,7 +35,12 @@ export default class RepositoryApi {
|
|||||||
this.tempDir_ = tempDir;
|
this.tempDir_ = tempDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadManifests() {
|
public async initialize() {
|
||||||
|
await this.loadManifests();
|
||||||
|
await this.loadRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadManifests() {
|
||||||
const manifestsText = await this.fetchText('manifests.json');
|
const manifestsText = await this.fetchText('manifests.json');
|
||||||
try {
|
try {
|
||||||
const manifests = JSON.parse(manifestsText);
|
const manifests = JSON.parse(manifestsText);
|
||||||
@@ -34,6 +53,27 @@ export default class RepositoryApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get githubApiUrl(): string {
|
||||||
|
// https://github.com/joplin/plugins
|
||||||
|
// https://api.github.com/repos/joplin/plugins/releases
|
||||||
|
return this.baseUrl_.replace(/^(https:\/\/)(github\.com\/)(.*)$/, '$1api.$2repos/$3');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadRelease() {
|
||||||
|
this.release_ = null;
|
||||||
|
|
||||||
|
if (this.isLocalRepo) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.githubApiUrl}/releases`);
|
||||||
|
const releases = await response.json();
|
||||||
|
if (!releases.length) throw new Error('No release was found');
|
||||||
|
this.release_ = releases[0];
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Could not load release - files will be downloaded from the repository directly:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private get isLocalRepo(): boolean {
|
private get isLocalRepo(): boolean {
|
||||||
return this.baseUrl_.indexOf('http') !== 0;
|
return this.baseUrl_.indexOf('http') !== 0;
|
||||||
}
|
}
|
||||||
@@ -46,15 +86,34 @@ export default class RepositoryApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fileUrl(relativePath: string): string {
|
private assetFileUrl(pluginId: string): string {
|
||||||
|
if (this.release_) {
|
||||||
|
const asset = this.release_.assets.find(asset => {
|
||||||
|
const s = asset.name.split('@');
|
||||||
|
s.pop();
|
||||||
|
const id = s.join('@');
|
||||||
|
return id === pluginId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (asset) return asset.browser_download_url;
|
||||||
|
|
||||||
|
logger.warn(`Could not get plugin from release: ${pluginId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't get the plugin file from the release, get it directly
|
||||||
|
// from the repository instead.
|
||||||
|
return this.repoFileUrl(`plugins/${pluginId}/plugin.jpl`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private repoFileUrl(relativePath: string): string {
|
||||||
return `${this.contentBaseUrl}/${relativePath}`;
|
return `${this.contentBaseUrl}/${relativePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchText(path: string): Promise<string> {
|
private async fetchText(path: string): Promise<string> {
|
||||||
if (this.isLocalRepo) {
|
if (this.isLocalRepo) {
|
||||||
return shim.fsDriver().readFile(this.fileUrl(path), 'utf8');
|
return shim.fsDriver().readFile(this.repoFileUrl(path), 'utf8');
|
||||||
} else {
|
} else {
|
||||||
return shim.fetchText(this.fileUrl(path));
|
return shim.fetchText(this.repoFileUrl(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +145,7 @@ export default class RepositoryApi {
|
|||||||
const manifest = manifests.find(m => m.id === pluginId);
|
const manifest = manifests.find(m => m.id === pluginId);
|
||||||
if (!manifest) throw new Error(`No manifest for plugin ID "${pluginId}"`);
|
if (!manifest) throw new Error(`No manifest for plugin ID "${pluginId}"`);
|
||||||
|
|
||||||
const fileUrl = this.fileUrl(`plugins/${manifest.id}/plugin.jpl`);
|
const fileUrl = this.assetFileUrl(manifest.id); // this.repoFileUrl(`plugins/${manifest.id}/plugin.jpl`);
|
||||||
const hash = md5(Date.now() + Math.random());
|
const hash = md5(Date.now() + Math.random());
|
||||||
const targetPath = `${this.tempDir_}/${hash}_${manifest.id}.jpl`;
|
const targetPath = `${this.tempDir_}/${hash}_${manifest.id}.jpl`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user