Skip to content

Commit 6df396c

Browse files
authored
feat(image-extract): improvements for image file resolving (#108)
Details of improvements for resolving image file: - Try decode the file path if not found - Try resolve the image file in current workspace folders if not found
1 parent de1454e commit 6df396c

File tree

2 files changed

+57
-30
lines changed

2 files changed

+57
-30
lines changed

src/commands/extract-images.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const extractImages = async (
4848
const failedImages = await window.withProgress(
4949
{ title: '提取图片', location: ProgressLocation.Notification },
5050
async progress => {
51-
extractor.onProgress = (idx, images) => {
51+
extractor.progressHook = (idx, images) => {
5252
const total = images.length;
5353
const image = images[idx];
5454
progress.report({
@@ -98,12 +98,13 @@ export const extractImages = async (
9898
}
9999
);
100100
if (failedImages && failedImages.length > 0) {
101-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
102-
window.showErrorMessage(
103-
`${failedImages.length}张图片提取失败\n${failedImages
104-
.map(x => [x.symbol, extractor.errors.find(y => y[0] === x.symbol)?.[1] ?? ''].join(': '))
105-
.join('\n')}`
106-
);
101+
window
102+
.showErrorMessage(
103+
`${failedImages.length}张图片提取失败\n${failedImages
104+
.map(x => [x.symbol, extractor.errors.find(y => y[0] === x.symbol)?.[1] ?? ''].join(': '))
105+
.join('\n')}`
106+
)
107+
.then(undefined, console.warn);
107108
}
108109
}
109110
}

src/services/images-extractor.service.ts

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import path from 'path';
22
import fs from 'fs';
3-
import { Uri } from 'vscode';
3+
import { Uri, workspace } from 'vscode';
44
import { imageService } from './image.service';
55
import { isErrorResponse } from '../models/error-response';
6+
import { promisify } from 'util';
7+
import { Stream } from 'stream';
68

79
export interface MarkdownImage {
810
link: string;
@@ -43,12 +45,15 @@ export class MarkdownImagesExtractor {
4345
private _status: 'pending' | 'extracting' | 'extracted' = 'pending';
4446
private _errors: [symbol: string, message: string][] = [];
4547
private _images: MarkdownImages | null | undefined = null;
48+
private readonly _workspaceDirs: string[] | undefined;
4649

4750
constructor(
48-
private markdown: string,
49-
private filePath: Uri,
50-
public onProgress?: (index: number, images: MarkdownImages) => void
51-
) {}
51+
private readonly markdown: string,
52+
private readonly targetFileUri: Uri,
53+
public progressHook?: (index: number, images: MarkdownImages) => void
54+
) {
55+
this._workspaceDirs = workspace.workspaceFolders?.map(({ uri: { fsPath } }) => fsPath);
56+
}
5257

5358
get status() {
5459
return this._status;
@@ -63,12 +68,13 @@ export class MarkdownImagesExtractor {
6368
let idx = 0;
6469
const result: ReturnType<MarkdownImagesExtractor['extract']> extends Promise<infer U> ? U : never = [];
6570
for (const image of sourceImages) {
66-
this.onProgress?.call(this, idx++, sourceImages);
71+
this.progressHook?.call(this, idx++, sourceImages);
6772
let newImageLink = result.find(x => x[1] != null && x[0].link === image.link)?.[1]?.link;
68-
const imageFile = newImageLink ? newImageLink : await this.resolveImageFile(image);
69-
if (imageFile !== false) {
73+
const imageStream = newImageLink ? newImageLink : await this.resolveImageFile(image);
74+
if (imageStream != null) {
7075
try {
71-
newImageLink = typeof imageFile === 'string' ? imageFile : await imageService.upload(imageFile);
76+
newImageLink =
77+
typeof imageStream === 'string' ? imageStream : await imageService.upload(imageStream);
7278
} catch (ex) {
7379
this._errors.push([
7480
image.symbol,
@@ -108,29 +114,49 @@ export class MarkdownImagesExtractor {
108114
).filter(x => createImageTypeFilter(this.imageType).call(null, x));
109115
}
110116

111-
private async resolveImageFile(image: MarkdownImage) {
117+
private async resolveImageFile(image: MarkdownImage): Promise<Stream | undefined | null> {
112118
const { link, symbol, alt, title } = image;
113119
if (webImageFilter(image)) {
114120
const imageStream = await imageService.download(link, alt ?? title);
115121
if (!(imageStream instanceof Array)) {
116122
return imageStream;
117123
} else {
118124
this._errors.push([symbol, `无法下载网络图片, ${imageStream[0]} - ${imageStream[2]}`]);
119-
return false;
125+
return undefined;
120126
}
121127
} else {
122-
const triedPathed: string[] = [];
123-
const createReadStream = (file: string) => {
124-
triedPathed.push(file);
125-
return fs.existsSync(file) ? fs.createReadStream(file) : false;
126-
};
127-
let stream = createReadStream(link);
128-
129-
stream =
130-
stream === false ? createReadStream(path.resolve(path.dirname(this.filePath.fsPath), link)) : stream;
131-
if (stream === false) this._errors.push([symbol, `本地图片文件不存在(${triedPathed.join(', ')})`]);
132-
133-
return stream;
128+
const checkReadAccess = (filePath: string) =>
129+
promisify(fs.access)(filePath).then(
130+
() => true,
131+
() => false
132+
);
133+
134+
let iPath: string | undefined | null = link,
135+
iDir = 0,
136+
searchingDirs: string[] | undefined | null,
137+
triedPath: string[] | undefined,
138+
isEncodedPath = false;
139+
140+
while (iPath != null) {
141+
if (await checkReadAccess(iPath)) {
142+
return fs.createReadStream(iPath);
143+
} else {
144+
(triedPath ??= []).push(iPath);
145+
146+
if (!isEncodedPath) {
147+
iPath = decodeURIComponent(iPath);
148+
isEncodedPath = true;
149+
continue;
150+
}
151+
}
152+
153+
searchingDirs ??= [path.dirname(this.targetFileUri.fsPath), ...(this._workspaceDirs ?? [])];
154+
iPath = iDir >= 0 && searchingDirs.length > iDir ? path.resolve(searchingDirs[iDir], link) : undefined;
155+
iDir++;
156+
isEncodedPath = false;
157+
}
158+
159+
return undefined;
134160
}
135161
}
136162
}

0 commit comments

Comments
 (0)