mirror of
https://git.um-react.app/um/cli.git
synced 2026-10-18 12:38:34 +00:00
refactor(#8): update LoadMMKVOrDefault function to use MMKVLoaderOptions and implement platform-specific loading for Windows
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"git.um-react.app/um/cli/algo/common"
|
||||
"git.um-react.app/um/cli/internal/mmkv"
|
||||
"git.um-react.app/um/cli/internal/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// MMKVLoaderOptions holds platform-specific inputs for LoadMMKVOrDefault.
|
||||
type MMKVLoaderOptions struct {
|
||||
CliExe string // qmpc-mmkv-cli path (--qmpc-mmkv-cli-exe); Windows only
|
||||
}
|
||||
|
||||
// LoadUserMMKV loads keys from a user-specified mmkv path. Soft-fails with a warning.
|
||||
func LoadUserMMKV(path string, key string, logger *zap.Logger) common.QMCKeys {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
logger.Debug("LoadUserMMKV: Loading user mmkv", zap.String("path", path), zap.String("key", key))
|
||||
userKeys, err := mmkv.LoadFromPath(path, key, logger)
|
||||
if err != nil {
|
||||
logger.Warn("LoadUserMMKV: could not read user keys", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
logger.Info("Using user mmkv", zap.String("path", path), zap.Int("count", len(userKeys)))
|
||||
return userKeys
|
||||
}
|
||||
|
||||
// parseQmpcMMKVCliOutput reads qmpc-mmkv-cli plaintext output as alternating
|
||||
// filename / ekey lines and builds a QMCKeys map.
|
||||
func parseQmpcMMKVCliOutput(r io.Reader, logger *zap.Logger) common.QMCKeys {
|
||||
sc := bufio.NewScanner(r)
|
||||
result := make(common.QMCKeys)
|
||||
|
||||
var name string
|
||||
nameRead := false
|
||||
for sc.Scan() {
|
||||
line := utils.NormalizeUnicode(strings.TrimSpace(sc.Text()))
|
||||
if !nameRead {
|
||||
name = line
|
||||
} else {
|
||||
if name != "" && line != "" {
|
||||
result[name] = line
|
||||
}
|
||||
}
|
||||
nameRead = !nameRead
|
||||
}
|
||||
|
||||
if err := sc.Err(); err != nil {
|
||||
logger.Warn("LoadMMKVOrDefault: error reading qmpc-mmkv-cli output", zap.Error(err))
|
||||
}
|
||||
if nameRead {
|
||||
logger.Warn("LoadMMKVOrDefault: qmpc-mmkv-cli output has odd number of lines, ignoring last line",
|
||||
zap.String("line", name),
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func LoadMMKVOrDefault(path string, key string, logger *zap.Logger) (result common.QMCKeys, err error) {
|
||||
func LoadMMKVOrDefault(opts MMKVLoaderOptions, logger *zap.Logger) (result common.QMCKeys, err error) {
|
||||
key1, err := qmmac.LoadMacKeysV8(logger)
|
||||
if err != nil {
|
||||
key1 = nil
|
||||
@@ -20,19 +20,7 @@ func LoadMMKVOrDefault(path string, key string, logger *zap.Logger) (result comm
|
||||
logger.Warn("LoadMMKVOrDefault: could not read QQMusic v10.x keys", zap.Error(err))
|
||||
}
|
||||
|
||||
userKeys := make(common.QMCKeys)
|
||||
if path != "" {
|
||||
logger.Info("Using user mmkv")
|
||||
userKeys, err = mmkv.LoadFromPath(path, key, logger)
|
||||
if err != nil {
|
||||
userKeys = nil
|
||||
logger.Warn("LoadMMKVOrDefault: could not read user keys", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
allKeys := mmkv.Merge(key1, key2, userKeys)
|
||||
|
||||
allKeys := mmkv.Merge(key1, key2)
|
||||
logger.Debug("Keys loaded", zap.Any("keys", allKeys), zap.Int("len", len(allKeys)))
|
||||
|
||||
return allKeys, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !darwin
|
||||
//go:build !darwin && !windows
|
||||
|
||||
package qmc
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func LoadMMKVOrDefault(path string, key string, logger *zap.Logger) (result common.QMCKeys, err error) {
|
||||
// Stub: do nothing
|
||||
func LoadMMKVOrDefault(opts MMKVLoaderOptions, logger *zap.Logger) (result common.QMCKeys, err error) {
|
||||
// No platform-default QMC keys on this OS.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestParseQmpcMMKVCliOutput(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
|
||||
t.Run("pairs", func(t *testing.T) {
|
||||
in := "file1.mflac\nkey1\nfile2.mgg\nkey2\n"
|
||||
got := parseQmpcMMKVCliOutput(strings.NewReader(in), logger)
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("len = %d, want 2", len(got))
|
||||
}
|
||||
if got["file1.mflac"] != "key1" {
|
||||
t.Errorf("file1.mflac = %q, want key1", got["file1.mflac"])
|
||||
}
|
||||
if got["file2.mgg"] != "key2" {
|
||||
t.Errorf("file2.mgg = %q, want key2", got["file2.mgg"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("crlf", func(t *testing.T) {
|
||||
in := "a.mflac\r\nKEYA\r\nb.mflac\r\nKEYB\r\n"
|
||||
got := parseQmpcMMKVCliOutput(strings.NewReader(in), logger)
|
||||
if got["a.mflac"] != "KEYA" || got["b.mflac"] != "KEYB" {
|
||||
t.Errorf("got %#v", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("odd line ignored", func(t *testing.T) {
|
||||
in := "file1.mflac\nkey1\norphan\n"
|
||||
got := parseQmpcMMKVCliOutput(strings.NewReader(in), logger)
|
||||
if len(got) != 1 || got["file1.mflac"] != "key1" {
|
||||
t.Errorf("got %#v, want single pair", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty name skipped", func(t *testing.T) {
|
||||
in := "\nkey-for-empty\nfile.mflac\nrealkey\n"
|
||||
got := parseQmpcMMKVCliOutput(strings.NewReader(in), logger)
|
||||
if len(got) != 1 || got["file.mflac"] != "realkey" {
|
||||
t.Errorf("got %#v", got)
|
||||
}
|
||||
if _, ok := got[""]; ok {
|
||||
t.Error("empty name should not be stored")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty key skipped", func(t *testing.T) {
|
||||
in := "empty-key.mflac\n\nfile.mflac\nrealkey\n"
|
||||
got := parseQmpcMMKVCliOutput(strings.NewReader(in), logger)
|
||||
if len(got) != 1 || got["file.mflac"] != "realkey" {
|
||||
t.Errorf("got %#v", got)
|
||||
}
|
||||
if _, ok := got["empty-key.mflac"]; ok {
|
||||
t.Error("empty key should not be stored")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty input", func(t *testing.T) {
|
||||
got := parseQmpcMMKVCliOutput(strings.NewReader(""), logger)
|
||||
if len(got) != 0 {
|
||||
t.Errorf("got %#v, want empty", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no trailing newline", func(t *testing.T) {
|
||||
in := "file.mflac\nkey"
|
||||
got := parseQmpcMMKVCliOutput(strings.NewReader(in), logger)
|
||||
if got["file.mflac"] != "key" {
|
||||
t.Errorf("got %#v", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
|
||||
"git.um-react.app/um/cli/algo/common"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func LoadMMKVOrDefault(opts MMKVLoaderOptions, logger *zap.Logger) (result common.QMCKeys, err error) {
|
||||
cliExe := opts.CliExe
|
||||
if cliExe == "" {
|
||||
cliExe = "qmpc-mmkv-cli.exe"
|
||||
}
|
||||
|
||||
cmd := exec.Command(cliExe, "-f", "plaintext", "-o", "-")
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
logger.Warn("LoadMMKVOrDefault: qmpc-mmkv-cli exited with non-zero status",
|
||||
zap.String("exe", cliExe),
|
||||
zap.Int("exit_code", exitErr.ExitCode()),
|
||||
zap.String("stderr", stderr.String()),
|
||||
)
|
||||
} else {
|
||||
logger.Warn("LoadMMKVOrDefault: failed to run qmpc-mmkv-cli",
|
||||
zap.String("exe", cliExe),
|
||||
zap.Error(err),
|
||||
zap.String("stderr", stderr.String()),
|
||||
)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result = parseQmpcMMKVCliOutput(&stdout, logger)
|
||||
logger.Debug("Keys loaded from qmpc-mmkv-cli", zap.Int("len", len(result)))
|
||||
return result, nil
|
||||
}
|
||||
+7
-2
@@ -24,6 +24,7 @@ import (
|
||||
_ "git.um-react.app/um/cli/algo/xiami"
|
||||
_ "git.um-react.app/um/cli/algo/ximalaya"
|
||||
"git.um-react.app/um/cli/internal/ffmpeg"
|
||||
"git.um-react.app/um/cli/internal/mmkv"
|
||||
"git.um-react.app/um/cli/internal/sniff"
|
||||
"git.um-react.app/um/cli/internal/utils"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
@@ -51,6 +52,7 @@ func main() {
|
||||
&cli.StringFlag{Name: "output", Aliases: []string{"o"}, Usage: "path to output dir", Required: false},
|
||||
&cli.StringFlag{Name: "qmc-mmkv", Aliases: []string{"db"}, Usage: "path to QQMusic mmkv path", Required: false},
|
||||
&cli.StringFlag{Name: "qmc-mmkv-key", Aliases: []string{"key"}, Usage: "QQMusic mmkv password (16 ascii chars)", Required: false},
|
||||
&cli.StringFlag{Name: "qmpc-mmkv-cli-exe", Usage: "path to QMPC MMKV Cli by LSR (v0.1.1+, auto-fetch QMC keys; Windows only)", Required: false, Value: "qmpc-mmkv-cli.exe"},
|
||||
&cli.StringFlag{Name: "kgg-db", Usage: "path to kgg db (win32 kugou v11)", Required: false},
|
||||
&cli.BoolFlag{Name: "remove-source", Aliases: []string{"rs"}, Usage: "remove source file", Required: false, Value: false},
|
||||
&cli.BoolFlag{Name: "skip-noop", Aliases: []string{"n"}, Usage: "skip noop decoder", Required: false, Value: true},
|
||||
@@ -176,11 +178,14 @@ func appMain(c *cli.Context) (err error) {
|
||||
return errors.New("output should be a writable directory")
|
||||
}
|
||||
|
||||
// QMC: Load keys
|
||||
qmcKeys, err := qmc.LoadMMKVOrDefault(c.String("qmc-mmkv"), c.String("qmc-mmkv-key"), logger)
|
||||
// QMC: Load platform keys, then merge user mmkv (--qmc-mmkv) on all platforms.
|
||||
qmcKeys, err := qmc.LoadMMKVOrDefault(qmc.MMKVLoaderOptions{
|
||||
CliExe: c.String("qmpc-mmkv-cli-exe"),
|
||||
}, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qmcKeys = mmkv.Merge(qmcKeys, qmc.LoadUserMMKV(c.String("qmc-mmkv"), c.String("qmc-mmkv-key"), logger))
|
||||
|
||||
kggDbPath := c.String("kgg-db")
|
||||
if kggDbPath == "" {
|
||||
|
||||
Reference in New Issue
Block a user