Implement endpoint for getting recent directories

This commit is contained in:
Asher 2020-02-18 14:13:22 -06:00
parent 16bcf59cb0
commit 8793110941
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
6 changed files with 89 additions and 11 deletions

View File

@ -10,8 +10,17 @@ body {
background: #272727; background: #272727;
color: #f4f4f4; color: #f4f4f4;
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", font-family:
"Segoe UI Emoji", "Segoe UI Symbol"; -apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Helvetica,
Arial,
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol";
overflow: hidden; overflow: hidden;
} }

View File

@ -33,8 +33,11 @@ export interface SessionResponse {
} }
export interface RecentResponse { export interface RecentResponse {
readonly recent: ReadonlyArray<Application> readonly paths: string[]
readonly running: ReadonlyArray<Application> }
export interface RunningResponse {
readonly applications: ReadonlyArray<Application>
} }
export interface HealthRequest { export interface HealthRequest {

View File

@ -19,6 +19,7 @@ export enum ApiEndpoint {
applications = "/applications", applications = "/applications",
recent = "/recent", recent = "/recent",
run = "/run", run = "/run",
running = "/running",
session = "/session", session = "/session",
status = "/status", status = "/status",
} }

View File

@ -1,13 +1,17 @@
import { field, logger } from "@coder/logger" import { field, logger } from "@coder/logger"
import * as cp from "child_process" import * as cp from "child_process"
import * as fs from "fs-extra"
import * as http from "http" import * as http from "http"
import * as net from "net" import * as net from "net"
import * as path from "path"
import * as url from "url"
import * as WebSocket from "ws" import * as WebSocket from "ws"
import { import {
Application, Application,
ApplicationsResponse, ApplicationsResponse,
ClientMessage, ClientMessage,
RecentResponse, RecentResponse,
RunningResponse,
ServerMessage, ServerMessage,
SessionError, SessionError,
SessionResponse, SessionResponse,
@ -22,6 +26,12 @@ interface ServerSession {
readonly app: Application readonly app: Application
} }
interface VsRecents {
[key: string]: (string | object)[]
}
type VsSettings = [string, string][]
/** /**
* API HTTP provider. * API HTTP provider.
*/ */
@ -29,7 +39,11 @@ export class ApiHttpProvider extends HttpProvider {
private readonly ws = new WebSocket.Server({ noServer: true }) private readonly ws = new WebSocket.Server({ noServer: true })
private readonly sessions = new Map<string, ServerSession>() private readonly sessions = new Map<string, ServerSession>()
public constructor(options: HttpProviderOptions, private readonly server: HttpServer) { public constructor(
options: HttpProviderOptions,
private readonly server: HttpServer,
private readonly dataDir?: string,
) {
super(options) super(options)
} }
@ -60,6 +74,11 @@ export class ApiHttpProvider extends HttpProvider {
return { return {
content: await this.recent(), content: await this.recent(),
} as HttpResponse<RecentResponse> } as HttpResponse<RecentResponse>
case ApiEndpoint.running:
this.ensureMethod(request)
return {
content: await this.running(),
} as HttpResponse<RunningResponse>
} }
return undefined return undefined
} }
@ -280,12 +299,58 @@ export class ApiHttpProvider extends HttpProvider {
} }
/** /**
* Return recent sessions. * Return VS Code's recent paths.
*/ */
public async recent(): Promise<RecentResponse> { public async recent(): Promise<RecentResponse> {
try {
if (!this.dataDir) {
throw new Error("data directory is not set")
}
const state: VsSettings = JSON.parse(await fs.readFile(path.join(this.dataDir, "User/state/global.json"), "utf8"))
const setting = Array.isArray(state) && state.find((item) => item[0] === "recently.opened")
if (!setting) {
throw new Error("settings appear malformed")
}
const paths: { [key: string]: Promise<string> } = {}
Object.values(JSON.parse(setting[1]) as VsRecents).forEach((recents) => {
recents
.filter((recent) => typeof recent === "string")
.forEach((recent) => {
try {
const pathname = url.parse(recent as string).pathname
if (pathname && !paths[pathname]) {
paths[pathname] = new Promise<string>((resolve) => {
fs.stat(pathname)
.then(() => resolve(pathname))
.catch(() => resolve())
})
}
} catch (error) {
logger.debug("invalid path", field("path", recent))
}
})
})
return {
paths: await Promise.all(Object.values(paths)),
}
} catch (error) {
if (error.code !== "ENOENT") {
throw error
}
}
return { paths: [] }
}
/**
* Return running sessions.
*/
public async running(): Promise<RunningResponse> {
return { return {
recent: [], // TODO applications: Array.from(this.sessions).map(([sessionId, session]) => ({
running: Array.from(this.sessions).map(([sessionId, session]) => ({
...session.app, ...session.app,
sessionId, sessionId,
})), })),

View File

@ -90,14 +90,14 @@ export class MainHttpProvider extends HttpProvider {
} }
public async getRoot(route: Route): Promise<HttpResponse> { public async getRoot(route: Route): Promise<HttpResponse> {
const recent = await this.api.recent() const running = await this.api.running()
const apps = await this.api.installedApplications() const apps = await this.api.installedApplications()
const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/home.html") const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/home.html")
response.content = response.content response.content = response.content
.replace(/{{COMMIT}}/g, this.options.commit) .replace(/{{COMMIT}}/g, this.options.commit)
.replace(/{{BASE}}/g, this.base(route)) .replace(/{{BASE}}/g, this.base(route))
.replace(/{{UPDATE:NAME}}/, await this.getUpdate()) .replace(/{{UPDATE:NAME}}/, await this.getUpdate())
.replace(/{{APP_LIST:RUNNING}}/, this.getAppRows(recent.running)) .replace(/{{APP_LIST:RUNNING}}/, this.getAppRows(running.applications))
.replace( .replace(
/{{APP_LIST:EDITORS}}/, /{{APP_LIST:EDITORS}}/,
this.getAppRows(apps.filter((app) => app.categories && app.categories.includes("Editor"))), this.getAppRows(apps.filter((app) => app.categories && app.categories.includes("Editor"))),

View File

@ -44,7 +44,7 @@ const main = async (args: Args): Promise<void> => {
} }
const httpServer = new HttpServer(options) const httpServer = new HttpServer(options)
const api = httpServer.registerHttpProvider("/api", ApiHttpProvider, httpServer) const api = httpServer.registerHttpProvider("/api", ApiHttpProvider, httpServer, args["user-data-dir"])
const update = httpServer.registerHttpProvider("/update", UpdateHttpProvider, !args["disable-updates"]) const update = httpServer.registerHttpProvider("/update", UpdateHttpProvider, !args["disable-updates"])
httpServer.registerHttpProvider("/vscode", VscodeHttpProvider, args) httpServer.registerHttpProvider("/vscode", VscodeHttpProvider, args)
httpServer.registerHttpProvider("/login", LoginHttpProvider) httpServer.registerHttpProvider("/login", LoginHttpProvider)