From 76a7674dae069e0be15bed6af8450ad214f8dde2 Mon Sep 17 00:00:00 2001 From: Keuin Date: Sun, 2 Jul 2023 16:12:13 +0800 Subject: Refactor: distinct task errors with type enum. --- bilibili/errors/errors.go | 43 ------------- bilibili/errors/taskerror.go | 146 +++++++++++++++++++++++++++++++++++++++++++ bilibili/streaming.go | 4 +- 3 files changed, 148 insertions(+), 45 deletions(-) delete mode 100644 bilibili/errors/errors.go create mode 100644 bilibili/errors/taskerror.go (limited to 'bilibili') diff --git a/bilibili/errors/errors.go b/bilibili/errors/errors.go deleted file mode 100644 index 2b810ed..0000000 --- a/bilibili/errors/errors.go +++ /dev/null @@ -1,43 +0,0 @@ -package errors - -import ( - "fmt" -) - -/* -Task errors. -*/ - -type RecoverableTaskError struct { - err error - message string -} - -func (e *RecoverableTaskError) Error() string { - return fmt.Sprintf("%v: %v", e.message, e.err) -} - -func (e *RecoverableTaskError) Unwrap() error { - return e.err -} - -func NewRecoverableTaskError(message string, err error) error { - return &RecoverableTaskError{message: message, err: err} -} - -type UnrecoverableTaskError struct { - err error - message string -} - -func (e *UnrecoverableTaskError) Error() string { - return fmt.Sprintf("%v: %v", e.message, e.err) -} - -func (e *UnrecoverableTaskError) Unwrap() error { - return e.err -} - -func NewUnrecoverableTaskError(message string, err error) error { - return &UnrecoverableTaskError{message: message, err: err} -} diff --git a/bilibili/errors/taskerror.go b/bilibili/errors/taskerror.go new file mode 100644 index 0000000..644a794 --- /dev/null +++ b/bilibili/errors/taskerror.go @@ -0,0 +1,146 @@ +package errors + +import ( + "fmt" + "github.com/samber/lo" + "strings" +) + +type Type int + +const ( + // GetRoomInfo means failed to read live room information + GetRoomInfo Type = iota + // GetLiveInfo means failed to get live information + GetLiveInfo + // StreamCopy means an error occurred while reading stream video data + StreamCopy + // LiveEnded means the live is ended + LiveEnded + // DanmakuServerConnection means failed to connect to danmaku server, which provides danmaku and other control data + DanmakuServerConnection + // Heartbeat means an error occurred while sending heartbeat message, which may indicate a broken network connection + Heartbeat + // InitialLiveStatus means failed to get the live status for the first time, which happens in the early stage + InitialLiveStatus + // DanmakuExchangeRead means an error occurred while reading danmaku datagram from the server, + // which may indicate a broken network connection + DanmakuExchangeRead + // GetDanmakuServerInfo means failed to get danmaku server info + GetDanmakuServerInfo + // RecoverLiveStatusChecker means failed to restart live status checker + RecoverLiveStatusChecker + + // FileCreation means failed to create a file + FileCreation + // InvalidLiveInfo means the live info is insufficient for recording + InvalidLiveInfo + // LiveStatusWatch means the live status watcher encountered an unrecoverable error + LiveStatusWatch + // Unknown means the error type is unexpected and is not suitable to retry for safety + Unknown + // InvalidAuthProtocol means authentication failed because the protocol is invalid, + // which may indicate the protocol implementation is outdated + InvalidAuthProtocol + // MessageDecompression means the message cannot be decompressed, and we cannot understand its content + MessageDecompression + // JsonDecode means we cannot decode a datum which is expected to be a JSON object string + JsonDecode +) + +var recoverableErrors = []Type{ + GetRoomInfo, + GetLiveInfo, + StreamCopy, + LiveEnded, + DanmakuServerConnection, + Heartbeat, + InitialLiveStatus, + DanmakuExchangeRead, + GetDanmakuServerInfo, + RecoverLiveStatusChecker, +} + +var errorStrings = map[Type]string{ + GetRoomInfo: "failed to get living room information", + GetLiveInfo: "failed to get live info", + StreamCopy: "stream copy was unexpectedly interrupted", + LiveEnded: "live is ended", + DanmakuServerConnection: "failed to connect to danmaku server", + Heartbeat: "heartbeat failed", + InitialLiveStatus: "check initial live status failed", + DanmakuExchangeRead: "failed to read exchange from server", + GetDanmakuServerInfo: "cannot get notification server info", + RecoverLiveStatusChecker: "when recovering from a previous error, another error occurred", + FileCreation: "failed to create file", + InvalidLiveInfo: "invalid live info", + LiveStatusWatch: "failed to watch live status", + Unknown: "unexpected error type", + InvalidAuthProtocol: "authentication failed, invalid protocol", + MessageDecompression: "failed to decompress server message", + JsonDecode: "invalid JSON response from server", +} + +func (t Type) String() string { + if s, ok := errorStrings[t]; ok { + return s + } + return fmt.Sprintf("", int(t)) +} + +type taskError struct { + typ Type + err []error +} + +func (e *taskError) Message() string { + //TODO implement me + panic("implement me") +} + +func (e *taskError) IsRecoverable() bool { + return lo.Contains(recoverableErrors, e.typ) +} + +func (e *taskError) Unwrap() []error { + return e.err +} + +func (e taskError) Error() string { + sb := strings.Builder{} + if e.IsRecoverable() { + sb.WriteString("recoverable task error") + } else { + sb.WriteString("unrecoverable task error") + } + sb.WriteString(": ") + sb.WriteString(fmt.Sprintf("%v", e.typ)) + if len(e.err) > 0 { + sb.WriteString(", ") + for i := range e.err { + sb.WriteString(fmt.Sprintf("%v", e.err[i])) + if i != len(e.err)-1 { + sb.WriteString(", ") + } + } + } + return sb.String() +} + +type TaskError interface { + // IsRecoverable reports if this task error is safe to retry. + IsRecoverable() bool + // Unwrap returns the underneath errors which cause the task error. + Unwrap() []error + // Message returns the detailed task-specific error message. + Message() string + // Error returns this error as string + Error() string +} + +func NewError(typ Type, err ...error) TaskError { + return &taskError{ + typ: typ, + err: err, + } +} diff --git a/bilibili/streaming.go b/bilibili/streaming.go index d081a8f..b985bbb 100644 --- a/bilibili/streaming.go +++ b/bilibili/streaming.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - errors2 "github.com/keuin/slbr/bilibili/errors" + errs "github.com/keuin/slbr/bilibili/errors" "github.com/keuin/slbr/common" "io" "net/http" @@ -71,7 +71,7 @@ func (b Bilibili) CopyLiveStream( out, err = fileCreator() if err != nil { b.logger.Error("Cannot open file for writing: %v", err) - err = errors2.NewUnrecoverableTaskError("failed to create file", err) + err = errs.NewError(errs.FileCreation, err) return } _, err = out.Write(initBytes) -- cgit v1.2.3