Skip to content

Commit 7edb083

Browse files
committed
FsZip: fixed open directory inside zip file
Also fixed FileDescriptor.name which was containing full file path.
1 parent 6952b2d commit 7edb083

File tree

2 files changed

+48
-116
lines changed

2 files changed

+48
-116
lines changed

src/services/plugins/FsZip.ts

Lines changed: 47 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -24,45 +24,64 @@ export const checkDirectoryName = (dirName: string) => !!!dirName.match(invalidD
2424

2525
export interface ZipMethods {
2626
isZipRoot: (path: string) => boolean
27-
getEntries: () => Promise<ZipEntry[]>
27+
getEntries: (path: string) => Promise<ZipEntry[]>
28+
getRelativePath: (path: string) => string
29+
prepareEntries: () => Promise<void>
30+
getFileDescriptor: (entry: ZipEntry) => FileDescriptor
31+
isDir: (path: string) => boolean
2832
}
2933

30-
export class Zip {
34+
export class Zip implements ZipMethods {
3135
ready = false
3236
zip: StreamZipAsync
3337
zipEntries: ZipEntry[]
3438
zipPath: string
3539
zipFilename: string
3640

3741
constructor(path: string) {
38-
this.zip = new StreamZip.async({ file: path })
42+
this.zipPath = path.replace(/(?<=\.zip).*/i, '')
43+
this.zip = new StreamZip.async({ file: this.zipPath })
3944
this.zipEntries = []
40-
this.zipPath = path.replace(/(\/$)*/, '')
4145
this.zipFilename = ''
4246
}
4347

4448
isZipRoot(path: string) {
4549
return true
4650
}
4751

48-
async getEntries(path: string) {
52+
/**
53+
* Get path relative to zip path
54+
*/
55+
getRelativePath(path: string) {
56+
return path.replace(this.zipPath, '').replace(/^\//, '')
57+
}
58+
59+
async prepareEntries() {
4960
if (!this.ready) {
5061
const entries = await this.zip.entries()
5162
this.zipEntries = Object.values(entries)
5263
this.ready = true
5364
}
65+
}
66+
67+
async getEntries(path: string) {
68+
const pathInZip = this.getRelativePath(path)
69+
// const isZipRoot = !pathInZip.length
5470

55-
const pathInZip = path.replace(this.zipPath, '')
56-
const isZipRoot = !!!pathInZip
71+
// const longestPath = pathInZip.replace(/([^\/]*)$/, '')
72+
const regExp = pathInZip.length ? new RegExp(`^${pathInZip}\/([^\/]+)[\/]?$`, 'g') : /^([^\/])*[\/]?$/g
73+
return this.zipEntries.filter((entry) => !!entry.name.match(regExp))
74+
}
5775

58-
// TODO: get path inside zip
59-
return this.zipEntries.filter((entry) => {
60-
if (isZipRoot) {
61-
const name = entry.name.replace(/(\/$)*/g, '')
62-
// root: include files in root zip
63-
return !name.match(/\//)
64-
}
65-
})
76+
isDir(path: string) {
77+
const pathInZip = this.getRelativePath(path)
78+
const longestPath = pathInZip.replace(/([^\/]*)$/, '')
79+
80+
if (!longestPath.length) {
81+
return true
82+
} else {
83+
return this.zipEntries.some((entry) => entry.isDirectory && !!entry.name.match(pathInZip))
84+
}
6685
}
6786

6887
getFileDescriptor(entry: ZipEntry) {
@@ -72,10 +91,9 @@ export class Zip {
7291
const mDate = new Date(entry.time)
7392
const mode = entry.attr ? ((entry.attr >>> 0) | 0) >> 16 : 0
7493

75-
// TODO: build file
7694
const file = {
7795
dir: path.parse(`${this.zipPath}/${name}`).dir,
78-
fullname: name,
96+
fullname: parsed.base,
7997
name: parsed.name,
8098
extension,
8199
cDate: mDate,
@@ -145,20 +163,27 @@ export class ZipApi implements FsApi {
145163
}
146164

147165
resolve(newPath: string): string {
148-
// TODO:
149166
// gh#44: replace ~ with userpath
150-
debugger
151167
const dir = newPath.replace(/^~/, HOME_DIR)
152168
return path.resolve(dir)
153169
}
154170

155171
async cd(path: string, transferId = -1): Promise<string> {
156172
const resolvedPath = this.resolve(path)
173+
174+
try {
175+
await this.zip.prepareEntries()
176+
} catch (e) {
177+
console.error('error getting zip file entries', e)
178+
throw { code: 'ENOTDIR' }
179+
}
180+
157181
try {
158182
const isDir = await this.isDir(resolvedPath)
159183
if (isDir) {
160184
return resolvedPath
161185
} else {
186+
debugger
162187
throw { code: 'ENOTDIR' }
163188
}
164189
} catch {}
@@ -286,7 +311,7 @@ export class ZipApi implements FsApi {
286311

287312
async isDir(path: string, transferId = -1): Promise<boolean> {
288313
console.warn('TODO: FsZip.isDir => check that file inside dir is a directory')
289-
return true
314+
return this.zip.isDir(path)
290315
}
291316

292317
async exists(path: string, transferId = -1): Promise<boolean> {
@@ -356,102 +381,9 @@ export class ZipApi implements FsApi {
356381

357382
async list(dir: string, watchDir = false, transferId = -1): Promise<FileDescriptor[]> {
358383
const entries = await this.zip.getEntries(dir)
359-
// return []
360-
return entries.map((entry) => this.zip.getFileDescriptor(entry))
361-
debugger
362-
throw 'TODO: FsZip.list not implemented'
363-
// try {
364-
// await this.isDir(dir)
365-
// return new Promise<FileDescriptor[]>((resolve, reject) => {
366-
// vol.readdir(dir, (err, items) => {
367-
// if (err) {
368-
// reject(err)
369-
// } else {
370-
// const dirPath = path.resolve(dir)
371-
372-
// const files: FileDescriptor[] = []
373-
374-
// for (let i = 0; i < items.length; i++) {
375-
// const file = ZipApi.fileFromPath(path.join(dirPath, items[i] as string))
376-
// files.push(file)
377-
// }
378-
379-
// watchDir && this.onList(dirPath)
380384

381-
// resolve(files)
382-
// }
383-
// })
384-
// })
385-
// } catch (err) {
386-
// throw {
387-
// code: err.code,
388-
// message: `Could not access path: ${dir}`,
389-
// }
390-
// }
391-
}
392-
393-
static fileFromPath(fullPath: string): FileDescriptor {
394-
const format = path.parse(fullPath)
395-
const name = fullPath
396-
const stats: Partial<BigIntStats> = null
397-
398-
debugger
399-
400-
return {
401-
name: `${fullPath}`,
402-
} as FileDescriptor
403-
// try {
404-
// // do not follow symlinks first
405-
// stats = vol.lstatSync(fullPath, { bigint: true })
406-
// if (stats.isSymbolicLink()) {
407-
// // get link target path first
408-
// name = vol.readlinkSync(fullPath) as string
409-
// targetStats = vol.statSync(fullPath, { bigint: true })
410-
// }
411-
// } catch (err) {
412-
// console.warn('error getting stats for', fullPath, err)
413-
414-
// const isDir = stats ? stats.isDirectory() : false
415-
// const isSymLink = stats ? stats.isSymbolicLink() : false
416-
417-
// stats = {
418-
// ctime: new Date(),
419-
// mtime: new Date(),
420-
// birthtime: new Date(),
421-
// size: stats ? stats.size : 0n,
422-
// isDirectory: (): boolean => isDir,
423-
// mode: -1n,
424-
// isSymbolicLink: (): boolean => isSymLink,
425-
// ino: 0n,
426-
// dev: 0n,
427-
// }
428-
// }
429-
430-
// const extension = path.parse(name).ext.toLowerCase()
431-
// const mode = targetStats ? targetStats.mode : stats.mode
432-
433-
// const file: FileDescriptor = {
434-
// dir: format.dir,
435-
// fullname: format.base,
436-
// name: format.name,
437-
// extension: extension,
438-
// cDate: stats.ctime,
439-
// mDate: stats.mtime,
440-
// bDate: stats.birthtime,
441-
// length: Number(stats.size),
442-
// mode: Number(mode),
443-
// isDir: targetStats ? targetStats.isDirectory() : stats.isDirectory(),
444-
// readonly: false,
445-
// type:
446-
// (!(targetStats ? targetStats.isDirectory() : stats.isDirectory()) &&
447-
// filetype(Number(mode), 0, 0, extension)) ||
448-
// '',
449-
// isSym: stats.isSymbolicLink(),
450-
// target: (stats.isSymbolicLink() && name) || null,
451-
// id: MakeId({ ino: stats.ino, dev: stats.dev }),
452-
// }
453-
454-
// return file
385+
return entries.map((entry) => this.zip.getFileDescriptor(entry))
386+
// FIXME: what should we do about watch dir?
455387
}
456388

457389
isRoot(path: string): boolean {

src/state/fileState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export class FileState {
255255

256256
// only create a new FS if supported FS is different
257257
// than current one
258-
if (!this.fs || newfs.name !== this.fs.name) {
258+
if (!this.fs || (newfs && newfs.name !== this.fs.name)) {
259259
!skipContext && this.api && this.saveContext()
260260

261261
// we need to free events in any case

0 commit comments

Comments
 (0)