AlistGo/alist · error

expected *os.File, got %T

Error message

expected *os.File, got %T

What it means

PutResult calls file.CacheFullInTempFile(), which returns model.File — an interface, not a concrete *os.File. Per internal/stream/stream.go, the returned value is either the stream's pre-set *os.File tmp file, OR the stream's original Reader when that Reader already implements model.File (e.g. a driver-supplied seekable file or an in-memory File implementation). The unconditional type assertion tempFile.(*os.File) fails for any non-*os.File implementation.

Source

Thrown at drivers/mediafire/driver.go:348

	return nil
}

func (d *Mediafire) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error {
	_, err := d.PutResult(ctx, dstDir, file, up)
	return err
}

func (d *Mediafire) PutResult(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {

	tempFile, err := file.CacheFullInTempFile()
	if err != nil {
		return nil, err
	}
	defer tempFile.Close()

	osFile, ok := tempFile.(*os.File)
	if !ok {
		return nil, fmt.Errorf("expected *os.File, got %T", tempFile)
	}

	fileHash, err := d.calculateSHA256(osFile)
	if err != nil {
		return nil, err
	}

	checkResp, err := d.uploadCheck(ctx, file.GetName(), file.GetSize(), fileHash, dstDir.GetID())
	if err != nil {
		return nil, err
	}

	if checkResp.Response.ResumableUpload.AllUnitsReady == "yes" {
		up(100.0)
	}

	if checkResp.Response.HashExists == "yes" && checkResp.Response.InAccount == "yes" {
		up(100.0)

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Change calculateSHA256 to accept io.Reader (SHA-256 needs only sequential reads) and drop the *os.File requirement entirely
  2. Alternatively use the model.File interface and only Rewind/Seek when it exposes it
  3. If an *os.File is truly required (e.g. for upload resumption), wrap non-*os.File values by copying to a temp file first

Example fix

// before: hard type assertion on an interface return
osFile, ok := tempFile.(*os.File)
if !ok {
    return nil, fmt.Errorf("expected *os.File, got %T", tempFile)
}
fileHash, err := d.calculateSHA256(osFile)

// after: hash any reader, no assertion needed
if seeker, ok := tempFile.(io.Seeker); ok {
    _, _ = seeker.Seek(0, io.SeekStart)
}
fileHash, err := d.calculateSHA256(tempFile) // signature: (r io.Reader) (string, error)
Defensive patterns

Strategy: type-guard

Type guard

// accept any model.File; only seek when possible
func toHashReader(f model.File) io.Reader {
    if s, ok := f.(io.Seeker); ok {
        _, _ = s.Seek(0, io.SeekStart)
    }
    return f
}

Try / catch

Replace the panic-prone unconditional assertion with the two-value form and a graceful path: if osFile, ok := tempFile.(*os.File); ok { use it } else { rewind via io.Seeker and hash the reader directly } — the hash never needed *os.File.

Prevention

When it happens

Trigger: Uploading via a FileStreamer whose Reader is a custom model.File (in-memory buffer, another driver's file handle, or a SeekableStream wrapping a non-temp reader) — CacheFullInTempFile returns it as-is without copying to an *os.File, and the assertion panics path fails.

Common situations: Server-side copy between storages, or upload pipelines where the stream originates from another driver rather than an HTTP multipart upload; also unit tests with mock FileStreamers.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/be8f4b1e3ce43328. Report an issue: GitHub.