siyuan-note/siyuan · critical
unzip [ ] failed
Error message
unzip [%s] failed: %s
What it means
The mobile gomobile binding Unzip extracts a zip archive via gulu.Zip.Unzip and, on any error, logs it and panics. The panic message reports the source archive path and the underlying unzip error. On mobile, a panic aborts the calling native bridge call.
Solutions
- Verify the zip file exists and is complete (re-download/re-transfer the archive) before calling Unzip
- Ensure the destination directory exists and the app has write permission to it
- Check the underlying error text in the log line "unzip [...] failed: ..." for the specific cause (e.g. CRC mismatch vs. open failure)
- Catch/recover the panic on the native side and surface a user-facing failure instead of crashing
- Free up device storage if the error indicates write failure
Example fix
// before
Unzip("/sdcard/backup.zip", "/data/data/app/workspace") // panics if file truncated
// after
if fileExistsAndValid("/sdcard/backup.zip") && dirWritable("/data/data/app/workspace") {
Unzip("/sdcard/backup.zip", "/data/data/app/workspace")
} else { showError() } Defensive patterns
Strategy: try-catch
Validate before calling
func safeUnzip(zipPath, dest string) (err error) {
if _, err = os.Stat(zipPath); err != nil { return fmt.Errorf("zip missing: %w", err) }
if err = os.MkdirAll(dest, 0755); err != nil { return fmt.Errorf("dest unwritable: %w", err) }
return nil
} Type guard
null
Try / catch
// Kotlin/Java side: wrap the binding call
try {
SiYuanKernel.Unzip(zipPath, dest)
} catch (e: Throwable) {
Log.e(TAG, "unzip failed", e)
showRestoreFailedDialog()
} Prevention
- Verify archive integrity (size/checksum) after download before unzipping
- Pre-create the destination directory and check free disk space
- Never rely on Unzip returning — it panics; always guard the native call
- Read the paired log line for the underlying cause
When it happens
Trigger: Calling the exported Unzip(zipFilePath, destination) from native mobile code when the zip is missing, corrupt, password-protected in an unsupported way, or the destination is unwritable.
Common situations: Interrupted downloads producing truncated zips, restoring workspace/snapshot archives on Android/iOS, insufficient storage space, destination path not created or permission-restricted by the OS sandbox.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/a79466e34d42c4cc.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mobile/kernel.go:381
return filepath.Base(path)
}
func FilterUploadFileName(name string) string {
return util.FilterUploadFileName(name)
}
func AssetName(name string) string {
return util.AssetName(name, ast.NewNodeID())
}
func HTML2Markdown(html string) string {
return util.NewLute().HTML2Md(html)
}
func Unzip(zipFilePath, destination string) {
if err := gulu.Zip.Unzip(zipFilePath, destination); nil != err {
logging.LogErrorf("unzip [%s] failed: %s", zipFilePath, err)
panic(err)
}
}
// GetExportFilePath 解析导出文件绝对路径,绕过 HTTP 层以避免锁屏密码拦截。
// exportPath 格式为 "/export/xxx.zip" 或 "assets/xxx"。
// 返回文件在磁盘上的绝对路径,以便原生端分块拷贝,避免大文件内存溢出。
// 解析失败返回空字符串。
func GetExportFilePath(exportPath string) (ret string) {
var absPath string
if after, ok := strings.CutPrefix(exportPath, "/export/"); ok {
fileName := after
if decoded, err := url.PathUnescape(fileName); err == nil {
fileName = decoded
}
fileName = filepath.Clean(fileName)
if strings.HasPrefix(fileName, "..") {
logging.LogWarnf("get export file path [%s] blocked: path traversal attempt [%s]", exportPath, fileName)
returnView on GitHub (pinned to 9f775e8a12)