Skip to content

Commit

Permalink
FsZip: fixed open directory inside zip file
Browse files Browse the repository at this point in the history
Also fixed FileDescriptor.name which was containing full file path.
  • Loading branch information
warpdesign committed Feb 24, 2023
1 parent 6952b2d commit 7edb083
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 116 deletions.
162 changes: 47 additions & 115 deletions src/services/plugins/FsZip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,64 @@ export const checkDirectoryName = (dirName: string) => !!!dirName.match(invalidD

export interface ZipMethods {
isZipRoot: (path: string) => boolean
getEntries: () => Promise<ZipEntry[]>
getEntries: (path: string) => Promise<ZipEntry[]>
getRelativePath: (path: string) => string
prepareEntries: () => Promise<void>
getFileDescriptor: (entry: ZipEntry) => FileDescriptor
isDir: (path: string) => boolean
}

export class Zip {
export class Zip implements ZipMethods {
ready = false
zip: StreamZipAsync
zipEntries: ZipEntry[]
zipPath: string
zipFilename: string

constructor(path: string) {
this.zip = new StreamZip.async({ file: path })
this.zipPath = path.replace(/(?<=\.zip).*/i, '')
this.zip = new StreamZip.async({ file: this.zipPath })
this.zipEntries = []
this.zipPath = path.replace(/(\/$)*/, '')
this.zipFilename = ''
}

isZipRoot(path: string) {
return true
}

async getEntries(path: string) {
/**
* Get path relative to zip path
*/
getRelativePath(path: string) {
return path.replace(this.zipPath, '').replace(/^\//, '')
}

async prepareEntries() {
if (!this.ready) {
const entries = await this.zip.entries()
this.zipEntries = Object.values(entries)
this.ready = true
}
}

async getEntries(path: string) {
const pathInZip = this.getRelativePath(path)
// const isZipRoot = !pathInZip.length

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

// TODO: get path inside zip
return this.zipEntries.filter((entry) => {
if (isZipRoot) {
const name = entry.name.replace(/(\/$)*/g, '')
// root: include files in root zip
return !name.match(/\//)
}
})
isDir(path: string) {
const pathInZip = this.getRelativePath(path)
const longestPath = pathInZip.replace(/([^\/]*)$/, '')

if (!longestPath.length) {
return true
} else {
return this.zipEntries.some((entry) => entry.isDirectory && !!entry.name.match(pathInZip))
}
}

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

// TODO: build file
const file = {
dir: path.parse(`${this.zipPath}/${name}`).dir,
fullname: name,
fullname: parsed.base,
name: parsed.name,
extension,
cDate: mDate,
Expand Down Expand Up @@ -145,20 +163,27 @@ export class ZipApi implements FsApi {
}

resolve(newPath: string): string {
// TODO:
// gh#44: replace ~ with userpath
debugger
const dir = newPath.replace(/^~/, HOME_DIR)
return path.resolve(dir)
}

async cd(path: string, transferId = -1): Promise<string> {
const resolvedPath = this.resolve(path)

try {
await this.zip.prepareEntries()
} catch (e) {
console.error('error getting zip file entries', e)
throw { code: 'ENOTDIR' }
}

try {
const isDir = await this.isDir(resolvedPath)
if (isDir) {
return resolvedPath
} else {
debugger
throw { code: 'ENOTDIR' }
}
} catch {}
Expand Down Expand Up @@ -286,7 +311,7 @@ export class ZipApi implements FsApi {

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

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

async list(dir: string, watchDir = false, transferId = -1): Promise<FileDescriptor[]> {
const entries = await this.zip.getEntries(dir)
// return []
return entries.map((entry) => this.zip.getFileDescriptor(entry))
debugger
throw 'TODO: FsZip.list not implemented'
// try {
// await this.isDir(dir)
// return new Promise<FileDescriptor[]>((resolve, reject) => {
// vol.readdir(dir, (err, items) => {
// if (err) {
// reject(err)
// } else {
// const dirPath = path.resolve(dir)

// const files: FileDescriptor[] = []

// for (let i = 0; i < items.length; i++) {
// const file = ZipApi.fileFromPath(path.join(dirPath, items[i] as string))
// files.push(file)
// }

// watchDir && this.onList(dirPath)

// resolve(files)
// }
// })
// })
// } catch (err) {
// throw {
// code: err.code,
// message: `Could not access path: ${dir}`,
// }
// }
}

static fileFromPath(fullPath: string): FileDescriptor {
const format = path.parse(fullPath)
const name = fullPath
const stats: Partial<BigIntStats> = null

debugger

return {
name: `${fullPath}`,
} as FileDescriptor
// try {
// // do not follow symlinks first
// stats = vol.lstatSync(fullPath, { bigint: true })
// if (stats.isSymbolicLink()) {
// // get link target path first
// name = vol.readlinkSync(fullPath) as string
// targetStats = vol.statSync(fullPath, { bigint: true })
// }
// } catch (err) {
// console.warn('error getting stats for', fullPath, err)

// const isDir = stats ? stats.isDirectory() : false
// const isSymLink = stats ? stats.isSymbolicLink() : false

// stats = {
// ctime: new Date(),
// mtime: new Date(),
// birthtime: new Date(),
// size: stats ? stats.size : 0n,
// isDirectory: (): boolean => isDir,
// mode: -1n,
// isSymbolicLink: (): boolean => isSymLink,
// ino: 0n,
// dev: 0n,
// }
// }

// const extension = path.parse(name).ext.toLowerCase()
// const mode = targetStats ? targetStats.mode : stats.mode

// const file: FileDescriptor = {
// dir: format.dir,
// fullname: format.base,
// name: format.name,
// extension: extension,
// cDate: stats.ctime,
// mDate: stats.mtime,
// bDate: stats.birthtime,
// length: Number(stats.size),
// mode: Number(mode),
// isDir: targetStats ? targetStats.isDirectory() : stats.isDirectory(),
// readonly: false,
// type:
// (!(targetStats ? targetStats.isDirectory() : stats.isDirectory()) &&
// filetype(Number(mode), 0, 0, extension)) ||
// '',
// isSym: stats.isSymbolicLink(),
// target: (stats.isSymbolicLink() && name) || null,
// id: MakeId({ ino: stats.ino, dev: stats.dev }),
// }

// return file
return entries.map((entry) => this.zip.getFileDescriptor(entry))
// FIXME: what should we do about watch dir?
}

isRoot(path: string): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/state/fileState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export class FileState {

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

// we need to free events in any case
Expand Down

0 comments on commit 7edb083

Please sign in to comment.