Skip to content

Commit

Permalink
Snapshot from scm.treehouse.uc/fs-v source correlation id:bbe05221-7d…
Browse files Browse the repository at this point in the history
…31-4f3d-ae69-bd652043f4bc; job:5fbafb1d-f36d-4ea2-8af0-a3b72688bb75
  • Loading branch information
unreadablewxy committed May 16, 2020
1 parent 4ac6444 commit cb866fa
Show file tree
Hide file tree
Showing 13 changed files with 610 additions and 65 deletions.
365 changes: 357 additions & 8 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fs-viewer",
"version": "0.1.0",
"version": "0.1.1",
"description": "A tagging small media browser",
"main": "build/index.js",
"scripts": {
Expand Down Expand Up @@ -58,11 +58,13 @@
"@babel/preset-typescript": "^7.9.0",
"@types/enzyme": "^3.10.5",
"@types/jest": "^25.2.1",
"@types/mime": "^2.0.1",
"@types/react": "^16.9.34",
"@types/react-dom": "^16.9.7",
"@types/react-router-dom": "^5.1.5",
"@types/reselect": "^2.2.0",
"babel-loader": "^8.1.0",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.5.3",
"electron": "^8.2.5",
"electron-builder": "^22.6.0",
Expand Down Expand Up @@ -94,6 +96,8 @@
"dependencies": {
"@mdi/js": "^5.1.45",
"@mdi/react": "^1.4.0",
"dbus-native": "^0.4.0",
"mime": "^2.4.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.1.2",
Expand Down
7 changes: 7 additions & 0 deletions src/api/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ type Thumbnailer = "none" | "system" | "mapped";

type ThumbnailSizing = "cover" | "full";

type ThumbnailResolution = "default" | "high";

interface Preferences {
/**
* Number of columns in grid view
Expand All @@ -83,6 +85,11 @@ interface Preferences {
*/
thumbnailPath?: string;

/**
* Resolution of thumbnails generated from the system thumbnailer
*/
thumbnailResolution?: ThumbnailResolution;

/**
* How thumbnail images are sized
*/
Expand Down
6 changes: 4 additions & 2 deletions src/gallery/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ interface Props {
files: FilesView,
index: number,
pathFormat?: string,
thumbnailResolution?: ThumbnailResolution;
}

// This component exists so that we don't need to evaluate regex every time
// a thumbnail needs a repaint.
function renderImage({files, index, pathFormat}: Props) {
function renderImage({files, index, pathFormat, thumbnailResolution}: Props) {
const fileName = files.names[index];

let url: string;
Expand All @@ -35,7 +36,8 @@ function renderImage({files, index, pathFormat}: Props) {
}
});
} else {
url = `thumb://${files.path}/${fileName}`;
const suffix = thumbnailResolution ? `?r=${thumbnailResolution}` : "";
url = `thumb://${files.path}/${fileName}${suffix}`;
}

return <img src={url} alt="" />;
Expand Down
3 changes: 3 additions & 0 deletions src/gallery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface Props {

thumbnailPath?: string;
thumbnailScaling: ThumbnailSizing;
thumbnailResolution?: ThumbnailResolution;
}

interface State {
Expand Down Expand Up @@ -257,6 +258,7 @@ export class Gallery extends React.PureComponent<Props, State> {
overscan,
selected,
thumbnailPath,
thumbnailResolution,
thumbnailScaling,
} = this.props;

Expand Down Expand Up @@ -315,6 +317,7 @@ export class Gallery extends React.PureComponent<Props, State> {
anchor={index === this.state.selectAnchor}
selected={selected?.has(index) || false}
pathFormat={thumbnailPath}
thumbnailResolution={thumbnailResolution}
onClick={this.handleClickThumbnail} />
})}
</ul>
Expand Down
1 change: 1 addition & 0 deletions src/gallery/thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface Props {
files: FilesView;
index: number;
pathFormat?: string;
thumbnailResolution?: ThumbnailResolution;
anchor: boolean;
selected: boolean;
onClick: (index: number, event: React.MouseEvent) => void;
Expand Down
34 changes: 16 additions & 18 deletions src/main/dbus-native.d.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
declare module "dbus-native" {
type EventHandler = () => void;

type EventHandler = () => void;
interface Interface {
on(eventName: string, handler: EventHandler): void;
}

interface Interface {
on(eventName: string, handler: EventHandler): void;
type GetInterfaceCallback<T extends Interface> = (error: Error, interface: T) => void;

Queue(): void;
Dequeue(): void;
GetSupported(): void;
GetFlavors(): void;
}
interface Service {
getInterface<T extends Interface>(
path: string,
id: string,
callback: GetInterfaceCallback<T>): void;
}

type GetInterfaceCallback = (error: Error, interface: Interface) => void;
interface SessionBus {
getService(id: string): Service;
}

interface Service {
getInterface(path: string, id: string, callback: GetInterfaceCallback): void;
}

interface SessionBus {
getService(id: string): Service;
}

export function sessionBus(): SessionBus;
export function sessionBus(): SessionBus;
}
3 changes: 2 additions & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ function createWindow(): void {
}

function onReady(): void {
registerThumbnailProtocol();
createWindow();
}

Expand All @@ -62,3 +61,5 @@ app.on("activate", () => {
if (mainWindow === null)
createWindow();
});

registerThumbnailProtocol();
153 changes: 142 additions & 11 deletions src/main/thumbnail.linux.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,155 @@
import {app, protocol, FilePathWithHeaders, Request} from "electron";
import path from "path";
import {createHash} from "crypto";
import {sessionBus, Interface} from "dbus-native";
import {getType as getMimeType} from "mime";
import {join as joinPath} from "path";
import {setTimeout} from "timers";

const homePath = app.getPath("home");
interface DequeueRequest {
handle: number;
}

type QueueCallback = (err: Error, handle: number) => void;

type ReadySignalHandler = (handle: number, uris: string[]) => void;

type ErrorSignalHandler = (handle: number, uris: string[], errorCode: number, message: string) => void;

interface Thumbnailer extends Interface {
GetSupported(): void;
GetSchedulers(): void;
GetFlavors(): void;
Dequeue(request: DequeueRequest): void;
Queue(
uris: string[],
mimeTypes: string[],
flavor: "normal" | string,
scheduler: "default" | string,
handleToDequeue: 0 | number,
cb: QueueCallback): void;

on(event: "Ready", handler: ReadySignalHandler): void;
on(event: "Error", handler: ErrorSignalHandler): void;
}

const resolutionMapping: {[k in ThumbnailResolution]: string} = {
"default": "normal",
"high": "large",
};

type RequestCallback = (path: string | FilePathWithHeaders) => void;

interface Batch {
paths: string[];
mimeTypes: string[];
callbacks: RequestCallback[];
}

function createBatch(): Batch {
return {
paths: [],
mimeTypes: [],
callbacks: [],
};
}

interface PendingRequest {
resolution: string;
callback: RequestCallback;
}

function hashMD5(text: string): string {
return createHash("md5").update(text).digest("hex");
const pendingRequests: {[uri: string]: PendingRequest} = {};
let unsubmitted: Batch = createBatch();
let batchTimer: NodeJS.Timeout | null = null;

function submitBatch(thumbnailer: Thumbnailer, size: ThumbnailResolution) {
batchTimer = null;
const batch = unsubmitted;
unsubmitted = createBatch();
const resolution = resolutionMapping[size];

function callback(err: Error | null, handle: number) {
if (err)
console.error("Unable to submit thumbnailing request", err);
else for (let n = batch.callbacks.length; n --> 0;)
pendingRequests[batch.paths[n]] = {
resolution,
callback: batch.callbacks[n],
};
}

thumbnailer.Queue(
batch.paths,
batch.mimeTypes,
resolution,
"foreground",
0,
callback);
}

function handleThumbnailRequest(
thumbnailer: Thumbnailer,
request: Request,
done: (response: FilePathWithHeaders) => void,
callback: RequestCallback,
): void {
let requestPath = request.url.slice(8); // len("thumb://")
requestPath = path.normalize(`${__dirname}/${requestPath}`);
const suffixIndex = request.url.indexOf("?", 8);
const size = suffixIndex > 0 && request.url.slice(suffixIndex + 3);
const path = request.url.slice(8 /* len("thumb://") */, suffixIndex);
const mimeType = getMimeType(path);
if (!mimeType) return callback("");

unsubmitted.paths.push(`file://${path}`);
unsubmitted.mimeTypes.push(mimeType);
unsubmitted.callbacks.push(callback);

let file = hashMD5(`file://${requestPath}`);
done({ path: path.join(homePath, `.cache/thumbnails/normal/${file}.png`) });
if (batchTimer == null)
batchTimer = setTimeout(submitBatch, 500, thumbnailer, size);
}

export function registerThumbnailProtocol(): void {
protocol.registerFileProtocol("thumb", handleThumbnailRequest);
function handleResponse(
handle: number,
uris: string[],
process: (uri: string, pr: PendingRequest) => void,
) {
for (let n = uris.length; n --> 0;) {
let path = uris[n];

const pr = pendingRequests[path];
if (!pr) continue

process(path, pr);
}
}

const homePath = app.getPath("home");

function handleReady(handle: number, uris: string[]): void {
handleResponse(handle, uris, (uri, pr) => {
const hash = createHash("md5").update(uri).digest("hex");
const path = `.cache/thumbnails/${pr.resolution}/${hash}.png`;
pr.callback(joinPath(homePath, path));
});
}

function handleError(handle: number, uris: string[], errorCode: number, message: string): void {
handleResponse(handle, uris, (uri, pr) => pr.callback(""));
}

export function registerThumbnailProtocol(): void {
sessionBus()
.getService("org.freedesktop.thumbnails.Thumbnailer1")
.getInterface<Thumbnailer>(
"/org/freedesktop/thumbnails/Thumbnailer1",
"org.freedesktop.thumbnails.Thumbnailer1",
(err, thumbnailer) => {
if (err) {
console.error("Thumbnailer startup failure", err);
} else {
thumbnailer.on("Error", handleError);
thumbnailer.on("Ready", handleReady);

const handler = handleThumbnailRequest.bind(null, thumbnailer);
protocol.registerFileProtocol("thumb", handler);
}
});
}
16 changes: 13 additions & 3 deletions src/main/thumbnail.win32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ import {getImageForPath, flags} from "shell-image-win";

type ResponseCallback = (response: Buffer | number) => void;

const resolutionMapping: {[k in ThumbnailResolution]: number} = {
"default": 256,
"high": 400,
};

function handleThumbnailRequest(request: Request, complete: ResponseCallback): void {
if (!request.url || request.url.length < 10) {
complete(400);
return;
}

let requestPath = request.url.slice(8).replace("/", "\\"); // len("thumb://")
const suffixIndex = request.url.indexOf("?", 8);
const size = suffixIndex > 0 && request.url.slice(suffixIndex + 3);
const resolution = resolutionMapping[size as ThumbnailResolution] || 256;
const requestPath = request.url.slice(8 /* len("thumb://") */, suffixIndex)
.replace("/", "\\");

getImageForPath(requestPath, {
width: 256,
height: 256,
width: resolution,
height: resolution,
flags: flags.BiggerSizeOk,
}).then(complete, () => complete(500));
}
Expand Down
Loading

0 comments on commit cb866fa

Please sign in to comment.