Skip to content

Commit

Permalink
fix #37 Avoid syntax highlighting for binary files or too large files
Browse files Browse the repository at this point in the history
  • Loading branch information
dmstern committed Nov 26, 2018
1 parent 114d326 commit 26ca8fc
Show file tree
Hide file tree
Showing 10 changed files with 437 additions and 163 deletions.
398 changes: 280 additions & 118 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"express": "^4.16.3",
"express-serve-static-core": "^0.1.1",
"fs-extra": "^7.0.0",
"morgan": "^1.9.0",
"mime": "^2.3.1",
"morgan": "^1.9.1",
"node-emoji": "^1.8.1",
"parse-authors": "^0.2.4",
"pm2": "^3.0.3",
Expand Down
11 changes: 8 additions & 3 deletions server/artifactory-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,11 @@ function getDistTags({ scope, packageName }: PackageId): AxiosPromise<any> {
: axios.get(`/-/package/${name2url({ scope, packageName })}/dist-tags`);
}

async function getFileContent(packageId: PackageId, filepath: string): Promise<string> {
const versionResponse = await getDistTags(packageId);
async function getFileContent(
packageId: PackageId,
filepath: string,
format: string = 'string',
): Promise<string | Buffer> {
const absPath = path.join(
tmpDir,
packageId.scope,
Expand All @@ -213,7 +216,9 @@ async function getFileContent(packageId: PackageId, filepath: string): Promise<s
'package',
filepath,
);
return fs.readFileSync(absPath).toString();
const fileContent = fs.readFileSync(absPath);
console.log(format === 'string');
return format === 'string' ? fileContent.toString() : fileContent;
}

export default {
Expand Down
6 changes: 5 additions & 1 deletion server/fileLister.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { promisify } from 'util';
import { resolve } from 'path';
import * as fs from 'fs';
import * as mime from 'mime';

const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);
Expand All @@ -24,7 +25,8 @@ export default async function getFiles(
return await Promise.all(
subdirs.map(async subdir => {
const res = resolve(dir, subdir);
return (await stat(res)).isDirectory()
const stats = await stat(res);
return stats.isDirectory()
? {
id: generateId(),
name: subdir,
Expand All @@ -34,6 +36,8 @@ export default async function getFiles(
id: generateId(),
name: subdir,
path: recursive ? sub.substring(sub.indexOf('/') + 1, sub.length) : '',
size: stats.size,
type: mime.getType(subdir),
};
}),
);
Expand Down
1 change: 1 addition & 0 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ app.get('/packageDetail/:scope/:packageName/:version/files/:path', (req, res) =>
version: req.params.version,
},
req.params.path,
req.query.format,
)
.then(response => {
res.send(response);
Expand Down
12 changes: 10 additions & 2 deletions src/services/BackendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export default class BackendApi {
return this.instance || (this.instance = new this());
}

public getBaseURL(): string {
return this.baseURL;
}

public getPackages(): AxiosPromise<PackagesResponse> {
return this.get('packages');
}
Expand All @@ -44,11 +48,15 @@ export default class BackendApi {
return this.get(`packageDetail/${scope}/${packageName}${version ? `/${version}` : ''}`);
}

public getFileContent(packageId: PackageId, filepath: string): AxiosPromise<string> {
public getFileContent(
packageId: PackageId,
filepath: string,
format: string = 'string',
): AxiosPromise<string> {
return this.get(
`packageDetail/${packageId.scope}/${packageId.packageName}${
packageId.version ? `/${packageId.version}` : ''
}/files/${filepath}`,
}/files/${filepath}?format=${format}`,
);
}

Expand Down
7 changes: 6 additions & 1 deletion src/services/DataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ export default class DataStore {
});
}

public async getFileContent(packageId: PackageId, filepath: string): Promise<string> {
public async getFileContent(
packageId: PackageId,
filepath: string,
format: string = 'string',
): Promise<string> {
const key = `${packageId.scope}${packageId.packageName}${packageId.version}${filepath}`;
return this.fileContentCache[key]
? new Promise<string>(resolve => {
Expand All @@ -185,6 +189,7 @@ export default class DataStore {
(this.fileContentPromises[key] = BackendApi.Instance.getFileContent(
packageId,
filepath,
format,
).then(response => {
this.fileContentCache[key] = response.data;
return response.data;
Expand Down
30 changes: 30 additions & 0 deletions src/util/Util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default {
fileSize(bytes: number): string {
const out =
bytes < 1000
? {
value: bytes,
prefix: '',
}
: bytes < 1000000
? {
value: bytes / 1000,
prefix: 'k',
}
: bytes < 1000000000
? {
value: bytes / 1000000,
prefix: 'M',
}
: bytes < 1000000000
? {
value: bytes / 1000,
prefix: 'G',
}
: {
value: bytes,
prefix: '',
};
return `${Math.round(out.value)} ${out.prefix}B`;
},
};
130 changes: 93 additions & 37 deletions src/views/PackageDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,36 @@
{{ getFileIcon(item.name) }}
</v-icon>
</template>
<template slot="append" slot-scope="{ item }" v-if="item.size !== undefined">
<v-list-tile-action>
{{ fileSize(item.size) }}
</v-list-tile-action>
</template>
</v-treeview>
</div>
<v-alert
:value="data.activeTreeItem && data.activeTreeItem.size && (
(
(data.activeTreeItem.size > maxSizeToShowContent) ||
!isHighlightableType(data.activeTreeItem)
)
)"
type="warning"
>
<h3>
<span
v-if="data.activeTreeItem.size > maxSizeToShowContent"
>
This file is too big to show it's content (> {{maxSizeToShowContent / 1000}} kB).
</span>
<span v-if="!isHighlightableType(data.activeTreeItem)">This filetype ({{data.activeTreeItem.type}}) is not supported.</span>
</h3>
But you can download it here: <ExternalLink :href="`${baseUrl}/packageDetail/${
data.currentPackage ? data.currentPackage.scope : undefined
}/${$router.currentRoute.params.packageName}/${
data.currentPackage ? data.currentPackage.version : undefined
}/files/${encodeURIComponent(`${data.activeTreeItem.path}/${data.activeTreeItem.name}`)}?format=file`"></ExternalLink>
</v-alert>
<LoadingSpinner
class="transition"
:class="isLoadingCode? 'visible' : 'hidden'"
Expand All @@ -103,6 +131,7 @@
:class="(data.activeTreeItem.name && !isLoadingCode)? 'visible' : 'hidden'"
:key="data.activeTreeItem.id"
:language="getLanguage(data.activeTreeItem.name)"
v-if="data.activeCode !== undefined && data.activeTreeItem.type && isHighlightableType(data.activeTreeItem)"
></CodeBlock>
</div>
</v-card-text>
Expand Down Expand Up @@ -326,6 +355,8 @@ import Searchable from '../../types/Searchable';
import { icons } from '../plugins/vuetify';
import { close } from 'fs';
import TreeItem from '../../types/TreeItem';
import Util from '../util/Util';
import BackendApi from '../services/BackendApi';
@Component({
components: {
Expand Down Expand Up @@ -395,6 +426,10 @@ export default class PackageDetail extends Vue {
this.init();
}
private get baseUrl(): string {
return BackendApi.Instance.getBaseURL();
}
private resetModel(): void {
this.activeTab = 0;
this.resetCurrentPackage();
Expand Down Expand Up @@ -425,6 +460,10 @@ export default class PackageDetail extends Vue {
});
}
private get maxSizeToShowContent(): number {
return 50000;
}
private selectCode(event: Event): void {
const target = event.target as HTMLElement;
const label = target.classList.contains('v-icon')
Expand All @@ -442,44 +481,58 @@ export default class PackageDetail extends Vue {
if (this.data.packageDetail && this.data.packageDetail.fileList) {
const currentFile = this.findFile(this.data.packageDetail.fileList, id);
if (currentFile && !currentFile.children && currentFile.name === clickedLabel) {
this.toggleLoading(true);
const timeout = global.setTimeout(() => {
if (!this.data.activeCode) {
this.toggleLoading(false);
EventBus.$emit(Errors.TIMEOUT_ERROR, new Error('Timeout Error. No code found.'));
}
}, 30000);
const code = DataStore.Instance.getFileContent(
{
scope: this.data.currentPackage ? this.data.currentPackage.scope : undefined,
packageName: Router.currentRoute.params.packageName,
version: this.data.currentPackage ? this.data.currentPackage.version : undefined,
},
encodeURIComponent(`${currentFile.path}/${currentFile.name}`),
)
.then(content => {
if (typeof content === 'string') {
this.data.activeCode = content;
} else {
try {
this.data.activeCode = JSON.stringify(content, null, 2);
} catch (error) {
this.data.activeCode = 'Error: Could not display file content.';
}
if (currentFile.size !== undefined && currentFile.size > this.maxSizeToShowContent) {
this.data.activeTreeItem = currentFile;
this.data.activeCode = undefined;
} else {
this.toggleLoading(true);
const timeout = global.setTimeout(() => {
if (!this.data.activeCode) {
this.toggleLoading(false);
EventBus.$emit(Errors.TIMEOUT_ERROR, new Error('Timeout Error. No code found.'));
}
this.data.activeTreeItem = currentFile;
this.toggleLoading(false);
global.clearTimeout(timeout);
})
.catch(error => {
this.toggleLoading(false);
EventBus.$emit(Errors.SERVER_ERROR, error);
global.clearTimeout(timeout);
});
}, 30000);
const code = DataStore.Instance.getFileContent(
{
scope: this.data.currentPackage ? this.data.currentPackage.scope : undefined,
packageName: Router.currentRoute.params.packageName,
version: this.data.currentPackage ? this.data.currentPackage.version : undefined,
},
encodeURIComponent(`${currentFile.path}/${currentFile.name}`),
)
.then(content => {
if (typeof content === 'string') {
this.data.activeCode = content;
} else {
try {
this.data.activeCode = JSON.stringify(content, null, 2);
} catch (error) {
this.data.activeCode = 'Error: Could not display file content.';
}
}
this.data.activeTreeItem = currentFile;
this.toggleLoading(false);
global.clearTimeout(timeout);
})
.catch(error => {
this.toggleLoading(false);
EventBus.$emit(Errors.SERVER_ERROR, error);
global.clearTimeout(timeout);
});
}
}
}
}
private isHighlightableType(item: TreeItem): boolean {
return item.type !== undefined &&
(
item.type.endsWith('json') ||
item.type.endsWith('application/javascript') ||
item.type.startsWith('text')
);
}
private resetActiveCode(): void {
this.data.activeTreeItem = {
id: '',
Expand Down Expand Up @@ -641,7 +694,11 @@ export default class PackageDetail extends Vue {
return languages[extension];
}
}
return 'javascript';
return 'plaintext';
}
private fileSize(bytes: number): string {
return Util.fileSize(bytes);
}
private isOld(): boolean | undefined {
Expand Down Expand Up @@ -708,9 +765,8 @@ export default class PackageDetail extends Vue {
padding: 0 !important;
}
.file-content,
.loading-spinner {
margin-top: 2rem;
.v-treeview {
margin-bottom: 2rem;
}
pre code.hljs {
Expand Down
2 changes: 2 additions & 0 deletions types/TreeItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export default interface TreeItem {
readonly name: string;
readonly path: string;
readonly children?: TreeItem[];
readonly size?: number;
readonly type?: string;
}

0 comments on commit 26ca8fc

Please sign in to comment.