Skip to content

Commit

Permalink
feat: close #3
Browse files Browse the repository at this point in the history
  • Loading branch information
ylc395 committed Oct 18, 2021
1 parent 8bbb41f commit 66c04b6
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 71 deletions.
64 changes: 43 additions & 21 deletions src/domain/service/PublishService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ export enum LocalRepoStatus {
Ready,
Fail,
Initializing,
MissingRepository,
}

export enum GithubClientEvents {
InfoChanged = 'infoChanged',
}

export interface GithubClient extends EventEmitter<GithubClientEvents> {
init(github: Github): void;
createRepository(): Promise<void>;
getRepositoryUrl(): string;
getRepositoryName(): string;
getDefaultRepositoryName(): string;
getGithubInfo(): Readonly<Github>;
}

const PUBLISH_RESULT_MESSAGE: Record<PublishResults, string> = {
Expand All @@ -41,7 +55,7 @@ const PUBLISH_RESULT_MESSAGE: Record<PublishResults, string> = {
};

export interface Git extends EventEmitter<GitEvents> {
init: (githubInfo: Github, dir: string) => Promise<void>;
init: (github: GithubClient, dir: string) => Promise<void>;
push: (files: string[], init: boolean) => Promise<void>;
terminate: () => void;
}
Expand All @@ -53,6 +67,7 @@ export interface Generator extends EventEmitter<GeneratorEvents> {

export const gitClientToken: InjectionToken<Git> = Symbol();
export const generatorToken: InjectionToken<Generator> = Symbol();
export const githubClientToken: InjectionToken<GithubClient> = Symbol();
export const token: InjectionKey<PublishService> = Symbol();

@singleton()
Expand All @@ -62,8 +77,17 @@ export class PublishService {
private readonly appService = container.resolve(AppService);
private readonly generator = container.resolve(generatorToken);
private readonly git = container.resolve(gitClientToken);
private readonly github = container.resolve(githubClientToken);
private files: string[] = [];
readonly localRepoStatus: Ref<LocalRepoStatus> = ref(LocalRepoStatus.Initializing);
private readonly localRepoStatus: Ref<LocalRepoStatus> = ref(LocalRepoStatus.Initializing);
readonly repositoryName = ref('');

readonly isRepositoryMissing = computed(
() => this.localRepoStatus.value === LocalRepoStatus.MissingRepository,
);
readonly isDefaultRepository = computed(
() => this.repositoryName.value === this.github.getDefaultRepositoryName(),
);
readonly githubInfo: Ref<Github | null> = ref(null);
readonly isGenerating = ref(false);
readonly isPublishing = ref(false);
Expand All @@ -81,6 +105,7 @@ export class PublishService {

private async init() {
this.outputDir.value = await this.generator.getOutputDir();
this.git.init(this.github, this.outputDir.value).catch(noop);

this.git.on(GitEvents.Progress, this.refreshPublishingProgress.bind(this));
this.git.on(GitEvents.Message, (message) => this.refreshPublishingProgress({ message }));
Expand All @@ -93,9 +118,15 @@ export class PublishService {
...(await this.pluginDataRepository.getGithubInfo()),
};

if (this.isGithubInfoValid.value) {
this.git.init(toRaw(this.githubInfo.value), this.outputDir.value).catch(noop);
this.initGithubClient();
}
private async initGithubClient() {
if (!this.isGithubInfoValid.value || !this.githubInfo.value) {
return;
}

this.github.init(toRaw(this.githubInfo.value));
this.repositoryName.value = this.github.getRepositoryName();
}

private handleLocalRepoStatusChanged(status: LocalRepoStatus) {
Expand All @@ -116,26 +147,12 @@ export class PublishService {
return Object.keys(keyInfos).length === requiredKeys.length && !some(keyInfos, isEmpty);
});

isDefaultRepository = computed(() => {
if (!this.githubInfo.value) {
return true;
}

const { repositoryName, userName } = this.githubInfo.value;
return !repositoryName || repositoryName === `${userName}.github.io`;
});

async saveGithubInfo(githubInfo: Partial<Github>) {
const githubInfo_ = omit(githubInfo, ['token']);

Object.assign(this.githubInfo.value, githubInfo_);
await this.pluginDataRepository.saveGithubInfo(omit(toRaw(this.githubInfo.value), ['token']));

if (!this.isGithubInfoValid.value || !this.githubInfo.value) {
return;
}

this.git.init(toRaw(this.githubInfo.value), this.outputDir.value).catch(noop);
this.initGithubClient();
}

async generateSite() {
Expand Down Expand Up @@ -172,7 +189,7 @@ export class PublishService {
this.git.terminate();
}

async publish(isRetry = false) {
async publish(isRetry = false, needToCreateRepo = false) {
if (this.isPublishing.value) {
return;
}
Expand All @@ -181,7 +198,8 @@ export class PublishService {
throw new Error('invalid github info');
}

const needToInit = isRetry || this.localRepoStatus.value === LocalRepoStatus.Fail;
const needToInit =
isRetry || needToCreateRepo || this.localRepoStatus.value === LocalRepoStatus.Fail;

if (needToInit) {
this.refreshPublishingProgress();
Expand All @@ -190,6 +208,10 @@ export class PublishService {
this.isPublishing.value = true;

try {
if (needToCreateRepo && this.isRepositoryMissing.value) {
await this.github.createRepository();
}

await this.git.push(this.files, needToInit);
this.publishingProgress.result = PublishResults.Success;
} catch (error) {
Expand Down
66 changes: 30 additions & 36 deletions src/driver/git/webviewApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ import {
pickBy,
isTypedArray,
isObjectLike,
cloneDeep,
isError,
noop,
} from 'lodash';
import { wrap, Remote, releaseProxy, expose } from 'comlink';
import fs from 'driver/fs/webviewApi';
import type { FsWorkerCallRequest, FsWorkerCallResponse } from 'driver/fs/type';
import { gitClientToken, GitEvents, LocalRepoStatus } from 'domain/service/PublishService';
import { Github, PublishError, PublishResults } from 'domain/model/Publishing';
import {
gitClientToken,
GitEvents,
LocalRepoStatus,
GithubClient,
GithubClientEvents,
} from 'domain/service/PublishService';
import { PublishError, PublishResults } from 'domain/model/Publishing';
import { joplinToken } from 'domain/service/AppService';
import type { GitEventHandler, WorkerGit } from './type';

Expand All @@ -28,44 +35,25 @@ declare const webviewApi: {
class Git extends EventEmitter<GitEvents> {
private readonly joplin = container.resolve(joplinToken);
private static readonly remote = 'github';
private static getRemoteUrl(userName: string, repoName?: string) {
const repoName_ = repoName || `${userName}.github.io`;
return `https://github.com/${userName}/${repoName_}.git`;
}
private static getGitRepositoryDir() {
return webviewApi.postMessage<string>({ event: 'getGitRepositoryDir' });
}
private dir?: string;
private gitdir?: string;
private installDir?: string;
private githubInfo?: Github;
private github?: GithubClient;
private worker?: Worker;
private workerGit?: Remote<WorkerGit>;
private initRepoPromise?: Promise<void>;
private rejectInitRepoPromise?: (reason?: unknown) => void;
private isPushing = false;

async init(githubInfo: Github, dir: string) {
async init(github: GithubClient, dir: string) {
this.dir = dir;

if (!this.installDir) {
this.installDir = await this.joplin.getInstallationDir();
}

if (!this.gitdir) {
this.gitdir = await Git.getGitRepositoryDir();
}

const oldGithubInfo = this.githubInfo;
this.githubInfo = cloneDeep(githubInfo);

if (
oldGithubInfo?.userName === githubInfo.userName &&
oldGithubInfo?.token === githubInfo.token &&
(oldGithubInfo?.repositoryName || undefined) === (githubInfo.repositoryName || undefined)
) {
return;
}
this.github = github;
this.installDir = await this.joplin.getInstallationDir();
this.gitdir = await Git.getGitRepositoryDir();
this.github.on(GithubClientEvents.InfoChanged, () => this.initRepo().catch(noop));

return this.initRepo();
}
Expand All @@ -74,15 +62,21 @@ class Git extends EventEmitter<GitEvents> {
// always init worker when init repo
this.initWorker();

const { gitdir, githubInfo, dir, workerGit } = this;
const { gitdir, dir, workerGit, github } = this;

if (!workerGit || !gitdir || !githubInfo || !dir) {
if (!workerGit || !gitdir || !dir || !github) {
throw new Error('cannot init repo');
}

this.initRepoPromise = new Promise((resolve, reject) => {
const reject_ = (e?: unknown) => {
this.emit(GitEvents.LocalRepoStatusChanged, LocalRepoStatus.Fail);
this.emit(
GitEvents.LocalRepoStatusChanged,
isError(e) && e.message.includes('404')
? LocalRepoStatus.MissingRepository
: LocalRepoStatus.Fail,
);

reject(new PublishError(PublishResults.Fail, e));
};

Expand All @@ -95,11 +89,11 @@ class Git extends EventEmitter<GitEvents> {
this.emit(GitEvents.LocalRepoStatusChanged, LocalRepoStatus.Initializing);
workerGit
.initRepo({
githubInfo,
githubInfo: github.getGithubInfo(),
gitInfo: {
dir,
gitdir,
url: Git.getRemoteUrl(githubInfo.userName, githubInfo.repositoryName),
url: github.getRepositoryUrl(),
remote: Git.remote,
},
})
Expand Down Expand Up @@ -160,17 +154,17 @@ class Git extends EventEmitter<GitEvents> {
throw new Error('worker init failed');
}

if (!this.githubInfo || !this.dir || !this.gitdir) {
if (!this.dir || !this.gitdir || !this.github) {
throw new Error('git info is not prepared');
}

return this.workerGit.publish({
files,
githubInfo: this.githubInfo,
githubInfo: this.github.getGithubInfo(),
gitInfo: {
dir: this.dir,
gitdir: this.gitdir,
url: Git.getRemoteUrl(this.githubInfo.userName, this.githubInfo.repositoryName),
url: this.github.getRepositoryUrl(),
remote: Git.remote,
},
});
Expand Down
95 changes: 95 additions & 0 deletions src/driver/github/webviewApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { container } from 'tsyringe';
import { EventEmitter } from 'eventemitter3';
import { cloneDeep } from 'lodash';
import { GithubClient, GithubClientEvents, githubClientToken } from 'domain/service/PublishService';
import { Github as GithubInfo, PublishError, PublishResults } from 'domain/model/Publishing';

export class Github extends EventEmitter<GithubClientEvents> implements GithubClient {
private githubInfo?: GithubInfo;

private async request(method: 'GET' | 'POST', path: string, payload?: Record<string, string>) {
if (!this.githubInfo?.token) {
throw new Error('no github info');
}

const response = await fetch(
`https://api.github.com${path}${
method === 'GET' && payload ? `?${new URLSearchParams(payload).toString()}` : ''
}`,
{
method,
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `token ${this.githubInfo.token}`,
'User-Agent': 'Joplin Pages Publisher',
...(method === 'POST' ? { 'Content-Type': 'application/json' } : null),
},
body: method === 'POST' && payload ? JSON.stringify(payload) : null,
},
);

return response.ok ? response.json() : Promise.reject(response.status);
}

getRepositoryName() {
if (!this.githubInfo) {
throw new Error('no github info');
}

return this.githubInfo.repositoryName || this.getDefaultRepositoryName();
}

getDefaultRepositoryName() {
if (!this.githubInfo?.userName) {
throw new Error('no github info');
}

return `${this.githubInfo.userName}.github.io`;
}

init(githubInfo: GithubInfo) {
const oldGithubInfo = this.githubInfo;
this.githubInfo = cloneDeep(githubInfo);

if (
oldGithubInfo?.userName === githubInfo.userName &&
oldGithubInfo?.token === githubInfo.token &&
(oldGithubInfo?.repositoryName || '') === (githubInfo.repositoryName || '')
) {
return;
}

this.emit(GithubClientEvents.InfoChanged, githubInfo);
this.githubInfo = githubInfo;
}

async createRepository() {
try {
await this.request('POST', '/user/repos', {
name: this.getRepositoryName(),
description: 'Blog Website By Joplin Pages Publisher',
});
} catch (error) {
throw new PublishError(PublishResults.Fail, error);
}
}

getGithubInfo() {
if (!this.githubInfo) {
throw new Error('no github info');
}
return this.githubInfo;
}

getRepositoryUrl() {
if (!this.githubInfo) {
throw new Error('no github info');
}

const { userName } = this.githubInfo;
const repoName = this.getRepositoryName();
return `https://github.com/${userName}/${repoName}.git`;
}
}

container.registerSingleton(githubClientToken, Github);

0 comments on commit 66c04b6

Please sign in to comment.