Skip to content

Commit

Permalink
feature: add file store
Browse files Browse the repository at this point in the history
  • Loading branch information
李昌 committed Feb 6, 2024
1 parent edea16d commit 16e0da8
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 39 deletions.
30 changes: 19 additions & 11 deletions cmds/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import (
"text/template"

"github.com/spf13/cobra"
"github.com/yangchnet/pm/remote"
"github.com/yangchnet/pm/utils"
)

func InitCmd() *cobra.Command {
var (
onlyLocal bool
storeType string
remoteType string
)

var initCmd = &cobra.Command{
Expand All @@ -36,23 +36,26 @@ func InitCmd() *cobra.Command {
utils.CreateDirIfNotExist(pmDir) // 创建.pm主目录
utils.CreateDirIfNotExist(storeDir) // 创建存储目录

var remoteType string = "git"
if len(args) > 0 {
remoteType = args[0]
remote, err := NewRemote(cmd.Context(), remoteType, nil)
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}

if onlyLocal {
remoteType = "empty"
// remote 初始化
remoteConfigStr, err := remote.Init(cmd.Context())
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}

remote, err := remote.NewRemote(cmd.Context(), remoteType, nil)
localStore, err := NewStore(cmd.Context(), storeType, nil)
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}

// remote 初始化
remoteConfigStr, err := remote.Init(cmd.Context())
storeConfigStr, err := localStore.Init(cmd.Context())
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
Expand All @@ -61,6 +64,7 @@ func InitCmd() *cobra.Command {
// 写入配置文件
params := map[string]string{
"remoteConfig": remoteConfigStr,
"storeConfig": storeConfigStr,
"localPath": filepath.Join(home, ".pm/store"),
"userKeyPath": userKeyPath,
}
Expand Down Expand Up @@ -90,14 +94,18 @@ func InitCmd() *cobra.Command {
},
}

initCmd.Flags().BoolVarP(&onlyLocal, "only-local", "", false, "only use local storage")
initCmd.Flags().StringVarP(&storeType, "store", "s", "file", "store type: [file, sqlite], default file")

initCmd.Flags().StringVarP(&remoteType, "remote", "r", "empty", "remote type: [git, empty], default empty")

return initCmd
}

var confTmpl = `
{{.remoteConfig}}
{{.storeConfig}}
local:
path: {{.localPath}}
Expand Down
1 change: 1 addition & 0 deletions cmds/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func GenerateCmd() *cobra.Command {
}

clipboard.WriteAll(password)
fmt.Println("密码已经复制到剪贴板")
},
PostRun: func(cmd *cobra.Command, args []string) {
service, err := NewService(cmd.Context())
Expand Down
47 changes: 45 additions & 2 deletions cmds/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import (

"github.com/yangchnet/pm/config"
"github.com/yangchnet/pm/remote"
"github.com/yangchnet/pm/remote/empty"
gitremote "github.com/yangchnet/pm/remote/git"
"github.com/yangchnet/pm/store"
filestore "github.com/yangchnet/pm/store/file-store"
sqlitestore "github.com/yangchnet/pm/store/sqlite-store"
)

type service struct {
Expand All @@ -20,12 +24,51 @@ func NewService(ctx context.Context) (*service, error) {
return nil, fmt.Errorf("remote not found")
}

remote, err := remote.NewRemote(ctx, remoteMap["type"].(string), remoteMap)
remote, err := NewRemote(ctx, remoteMap["type"].(string), remoteMap)
if err != nil {
return nil, err
}

storeMap := config.GetStringMap("store")
if len(storeMap) <= 0 {
return nil, fmt.Errorf("store not found")
}

store, err := NewStore(ctx, storeMap["type"].(string), storeMap)
if err != nil {
return nil, err
}

return &service{
store: store.NewSqliteStore(ctx),
store: store,
remote: remote,
}, nil
}

func NewStore(ctx context.Context, storeType string, storeConfig map[string]any) (store.Store, error) {
var localStore store.Store
switch storeType {
case "sqlite":
localStore = sqlitestore.NewSqliteStore(ctx)
case "file":
localStore = filestore.NewFileStore(ctx)
default:
return nil, fmt.Errorf("未知的store类型: %s", storeType)
}

return localStore, nil
}

func NewRemote(ctx context.Context, remoteType string, remoteMap map[string]any) (remote.Remote, error) {
var remote remote.Remote
switch remoteType {
case "git":
remote = gitremote.NewGitRemote(ctx, remoteMap)
case "empty":
remote = empty.NewEmptyRemote()
default:
return nil, fmt.Errorf("未知的remote类型: %s", remoteType)
}

return remote, nil
}
18 changes: 0 additions & 18 deletions remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ package remote

import (
"context"
"fmt"

"github.com/yangchnet/pm/remote/empty"
gitremote "github.com/yangchnet/pm/remote/git"
)

type Remote interface {
Expand All @@ -18,17 +14,3 @@ type Remote interface {
// Init 初始化remote,返回remote配置信息
Init(ctx context.Context) (string, error)
}

func NewRemote(ctx context.Context, remoteType string, remoteMap map[string]any) (Remote, error) {
var remote Remote
switch remoteType {
case "git":
remote = gitremote.NewGitRemote(ctx, remoteMap)
case "empty":
remote = empty.NewEmptyRemote()
default:
return nil, fmt.Errorf("未知的remote类型: %s", remoteType)
}

return remote, nil
}
8 changes: 8 additions & 0 deletions store/err.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package store

import "errors"

var (
ErrAlreadyExists = errors.New("already exists")
ErrNotFound = errors.New("not found")
)
138 changes: 138 additions & 0 deletions store/file-store/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package filestore

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/yangchnet/pm/config"
"github.com/yangchnet/pm/store"
)

type FileStore struct {
localPath string
}

func NewFileStore(ctx context.Context) *FileStore {
return &FileStore{
localPath: config.GetString("local.path"),
}
}

var _ store.Store = &FileStore{}

func (s *FileStore) Init(ctx context.Context) (string, error) {
return `store:
type: file`, nil
}

// Save 在使用cryptFunc对密码密文进行存储
func (s *FileStore) Save(ctx context.Context, passwd *store.Passwd) error {
files, err := readAllPasswd(s.localPath)
if err != nil {
return err
}

_, ok := files[passwd.Name+".passwd"]
if ok {
return store.ErrAlreadyExists
}

f, err := os.Create(filepath.Join(s.localPath, passwd.Name+".passwd"))
if err != nil {
return err
}
defer f.Close()

passwdByte, err := json.Marshal(passwd)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

encoded := base64.StdEncoding.EncodeToString(passwdByte)
_, err = f.WriteString(encoded)
if err != nil {
return err
}

return nil
}

// Get 获取密码
func (s *FileStore) Get(ctx context.Context, name string) (*store.Passwd, error) {
files, err := readAllPasswd(s.localPath)
if err != nil {
return nil, err
}

path, ok := files[name+".passwd"]
if !ok {
return nil, store.ErrNotFound
}

f, err := os.ReadFile(path)
if err != nil {
return nil, err
}

decoded, err := base64.StdEncoding.DecodeString(string(f))
if err != nil {
return nil, err
}

var passwd store.Passwd
if err := json.Unmarshal(decoded, &passwd); err != nil {
return nil, err
}

return &passwd, nil
}

// SearchName 根据名称进行搜索并给出名称列表
func (s *FileStore) SearchName(ctx context.Context, name string) ([]string, error) {
files, err := readAllPasswd(s.localPath)
if err != nil {
return nil, err
}

var names []string
for k, _ := range files {
list := strings.Split(k, ".")
if len(list) < 1 {
continue
}

if strings.Contains(strings.ToLower(list[0]), strings.ToLower(name)) {
names = append(names, list[0])
}
}

return names, nil
}

// Delete 删除一个记录
func (s *FileStore) Delete(ctx context.Context, name string) error {
return os.Remove(filepath.Join(s.localPath, name+".passwd"))
}

func readAllPasswd(dir string) (map[string]string, error) {
files := make(map[string]string)
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Ext(path) == ".passwd" {
files[info.Name()] = path
}
return nil
})
if err != nil {
return nil, err
}
return files, nil
}
3 changes: 3 additions & 0 deletions store/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
)

type Store interface {
// Init 初始化存储
Init(ctx context.Context) (string, error)

// Save 在使用cryptFunc对密码密文进行存储
Save(ctx context.Context, passwd *Passwd) error

Expand Down
Loading

0 comments on commit 16e0da8

Please sign in to comment.