{"record":{"id":"6bea81bb675b6fc4","repo":"GopeedLab/gopeed","slug":"ed2k-link-is-empty","errorCode":null,"errorMessage":"ed2k link is empty","messagePattern":"ed2k link is empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/protocol/ed2k/fetcher.go","lineNumber":312,"sourceCode":"\t}\n\tclient := f.manager.currentClient()\n\tif client == nil {\n\t\treturn f.handle\n\t}\n\thash, err := f.hash()\n\tif err != nil {\n\t\treturn f.handle\n\t}\n\thandle := client.FindTransfer(hash)\n\tif handle.IsValid() {\n\t\tf.handle = handle\n\t}\n\treturn f.handle\n}\n\nfunc (f *Fetcher) hash() (gprotocol.Hash, error) {\n\tif f.meta == nil || f.meta.Req == nil {\n\t\treturn gprotocol.Invalid, errors.New(\"ed2k link is empty\")\n\t}\n\tlink, err := parseLink(f.meta.Req.URL)\n\tif err != nil {\n\t\treturn gprotocol.Invalid, err\n\t}\n\treturn link.Hash, nil\n}\n\ntype FetcherManager struct {\n\tmu         sync.Mutex\n\tclient     *goed2k.Client\n\tstateStore *clientStateStore\n}\n\nfunc (fm *FetcherManager) SetStateStore(store fetcher.ProtocolStateStore) {\n\tfm.mu.Lock()\n\tdefer fm.mu.Unlock()\n","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/protocol/ed2k/fetcher.go#L294-L330","documentation":"Fetcher.hash (internal/protocol/ed2k/fetcher.go:310-313) returns this when f.meta or f.meta.Req is nil — the fetcher has no request at all, so there is no ed2k link to derive a transfer hash from. Methods that need the hash (currentHandle, Pause, Stats, Close paths) fail with it when the fetcher was never given a request.","triggerScenarios":"Using a zero-value Fetcher{} instead of constructing via FetcherManager; calling Pause/Stats before Resolve has stored meta.Req; code that builds the fetcher manually (protocol registry bypass) and skips setting fetcher.Meta().Req; calling hash-dependent methods after a failed/aborted initialization left meta nil.","commonSituations":"Unit tests instantiating ed2k.Fetcher directly; a manager bug that hands out a fetcher before Setup/Resolve completes; API-level calls arriving for a task whose fetcher was restored without its request metadata.","solutions":["Always create ed2k fetchers through FetcherManager (it wires meta and client state)","Ensure Resolve/setup completed successfully before calling Start/Pause/Stats","If you construct the fetcher yourself, set f.meta = &fetcher.FetcherMeta{Req: req} before use","Guard callers: if fetcher.Meta() == nil || fetcher.Meta().Req == nil, skip hash-dependent operations"],"exampleFix":"// before\nf := &ed2k.Fetcher{}\n_ = f.Pause() // \"ed2k link is empty\"\n\n// after\nf := manager.BuildFetcher(url) // via FetcherManager\n// or manually:\nf.Meta().Req = &base.Request{URL: \"ed2k://|file|...|/\"}","handlingStrategy":"validation","validationCode":"// Ensure the fetcher has a request before hash-dependent calls:\nmeta := fetcher.Meta()\nif meta == nil || meta.Req == nil || meta.Req.URL == \"\" {\n    return errors.New(\"ed2k fetcher not initialized\")\n}\nerr := fetcher.Pause()","typeGuard":"func ed2kReady(f *ed2k.Fetcher) bool {\n    m := f.Meta()\n    return m != nil && m.Req != nil && m.Req.URL != \"\"\n}","tryCatchPattern":"if err := fetcher.Pause(); err != nil {\n    if strings.Contains(err.Error(), \"ed2k link is empty\") {\n        // initialization order bug: run Resolve/Setup first\n    }\n}","preventionTips":["Always build ed2k fetchers via FetcherManager, never struct literals","Complete Resolve before Start/Pause/Stats","In tests, set fetcher.Meta().Req before exercising hash paths"],"tags":["ed2k","initialization","null-request"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}