Skip to content

Commit

Permalink
规范文件系统接口
Browse files Browse the repository at this point in the history
  • Loading branch information
movsb committed Apr 20, 2024
1 parent f848c43 commit b6a4a3c
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 271 deletions.
58 changes: 58 additions & 0 deletions modules/utils/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,37 @@ package utils

import (
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"time"

"github.com/movsb/taoblog/protocols"
)

// walk returns the file list for a directory.
// Directories are omitted.
// Returned paths are related to dir.
// 返回的所有路径都是相对于 dir 而言的。
func ListFiles(dir string) ([]*protocols.FileSpec, error) {
bfs, err := ListBackupFiles(dir)
if err != nil {
return nil, err
}
fs := make([]*protocols.FileSpec, 0, len(bfs))
for _, f := range bfs {
fs = append(fs, &protocols.FileSpec{
Path: f.Path,
Mode: f.Mode,
Size: f.Size,
Time: f.Time,
})
}
return fs, nil
}

// Deprecated. 用 ListFiles。
func ListBackupFiles(dir string) ([]*protocols.BackupFileSpec, error) {
dir, err := filepath.Abs(dir)
if err != nil {
Expand Down Expand Up @@ -47,3 +72,36 @@ func ListBackupFiles(dir string) ([]*protocols.BackupFileSpec, error) {

return files, nil
}

// 安全写文件。
// TODO 不应该引入专用的 FileSpec 定义。
// path 中包含的目录必须存在,否则失败。
// TODO 没移除失败的文件。
func WriteFile(path string, mode fs.FileMode, modified time.Time, size int64, r io.Reader) error {
tmp, err := os.CreateTemp(filepath.Dir(path), `taoblog-*`)
if err != nil {
return err
}

if n, err := io.Copy(tmp, r); err != nil || n != size {
return fmt.Errorf(`write error: %d %v`, n, err)
}

if err := tmp.Chmod(mode); err != nil {
return err
}

if err := tmp.Close(); err != nil {
return err
}

if err := os.Chtimes(tmp.Name(), modified, modified); err != nil {
return err
}

if err := os.Rename(tmp.Name(), path); err != nil {
return err
}

return nil
}
102 changes: 6 additions & 96 deletions service/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ package service
import (
"bytes"
"errors"
"fmt"
"io"
fspkg "io/fs"
"log"
"os"
"path/filepath"
"time"

"github.com/movsb/taoblog/protocols"
"github.com/movsb/taoblog/service/modules/storage"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
Expand All @@ -23,7 +19,7 @@ func (s *Service) FileSystem(srv protocols.Management_FileSystemServer) error {

initialized := false

var fs _FileSystem
var fs storage.FileSystem

for {
req, err := srv.Recv()
Expand All @@ -48,7 +44,7 @@ func (s *Service) FileSystem(srv protocols.Management_FileSystemServer) error {
} else if initReq != nil {
initialized = true
if init := initReq.GetPost(); init != nil {
fs, err = s.fileSystemForPost(init.Id)
fs, err = s.FileSystemForPost(init.Id)
}
if err != nil {
return status.Error(codes.Internal, err.Error())
Expand Down Expand Up @@ -110,93 +106,7 @@ func (s *Service) FileSystem(srv protocols.Management_FileSystemServer) error {
}
}

type _FileSystem interface {
ListFiles() ([]*protocols.FileSpec, error)
DeleteFile(path string) error
WriteFile(spec *protocols.FileSpec, r io.Reader) error
}

type _FileSystemForPost struct {
s *Service
id int64
}

func (fs *_FileSystemForPost) ListFiles() ([]*protocols.FileSpec, error) {
filePaths, err := fs.s.Store().List(fs.id)
if err != nil {
return nil, err
}

files := []*protocols.FileSpec{}
for _, path := range filePaths {
spec, err := func() (*protocols.FileSpec, error) {
fp, err := fs.s.Store().Open(fs.id, path)
if err != nil {
return nil, err
}
defer fp.Close()
stat, err := fp.Stat()
if err != nil {
return nil, err
}
return &protocols.FileSpec{
Path: path,
Mode: uint32(stat.Mode()),
Size: uint32(stat.Size()),
Time: uint32(stat.ModTime().Unix()),
}, nil
}()
if err != nil {
return nil, err
}
files = append(files, spec)
}

return files, nil
}

func (fs *_FileSystemForPost) DeleteFile(path string) error {
return fs.s.Store().Remove(fs.id, path)
}

func (fs *_FileSystemForPost) WriteFile(spec *protocols.FileSpec, r io.Reader) error {
finalPath, _ := fs.s.Store().PathOf(fs.id, spec.Path)

tmp, err := os.CreateTemp(filepath.Dir(finalPath), `taoblog-*`)
if err != nil {
return err
}

if n, err := io.Copy(tmp, r); err != nil || n != int64(spec.Size) {
return fmt.Errorf(`write error: %d %v`, n, err)
}

if err := tmp.Chmod(fspkg.FileMode(spec.Mode)); err != nil {
return err
}

if err := tmp.Close(); err != nil {
return err
}

t := time.Unix(int64(spec.Time), 0)

if err := os.Chtimes(tmp.Name(), t, t); err != nil {
return err
}

if err := os.Rename(tmp.Name(), finalPath); err != nil {
return err
}

return nil
}

func (s *Service) fileSystemForPost(id int64) (*_FileSystemForPost, error) {
_ = s.MustGetPost(id)

return &_FileSystemForPost{
s: s,
id: id,
}, nil
func (s *Service) FileSystemForPost(id int64) (*storage.Local, error) {
// _ = s.MustGetPost(id)
return storage.NewLocal(s.cfg.Data.File.Path, id), nil
}
14 changes: 0 additions & 14 deletions service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
commentgeo "github.com/movsb/taoblog/service/modules/comment_geo"
"github.com/movsb/taoblog/service/modules/comment_notify"
"github.com/movsb/taoblog/service/modules/search"
"github.com/movsb/taoblog/service/modules/storage"
"github.com/movsb/taoblog/service/modules/storage/local"
"github.com/movsb/taoblog/theme/modules/canonical"
"github.com/movsb/taorm/taorm"
"google.golang.org/grpc"
Expand All @@ -34,7 +32,6 @@ type Service struct {
auth *auth.Auth
cmtntf *comment_notify.CommentNotifier
cmtgeo *commentgeo.CommentGeo
store storage.Store
cache *memory_cache.MemoryCache

avatarCache *AvatarCache
Expand All @@ -52,17 +49,11 @@ type Service struct {

// NewService ...
func NewService(cfg *config.Config, db *sql.DB, auther *auth.Auth) *Service {
localStorage, err := local.NewLocal(cfg.Data.File.Path)
if err != nil {
panic(err)
}

s := &Service{
cfg: cfg,
db: db,
tdb: taorm.NewDB(db),
auth: auther,
store: localStorage,
cache: memory_cache.NewMemoryCache(time.Minute * 10),
cmtgeo: commentgeo.NewCommentGeo(context.TODO()),

Expand Down Expand Up @@ -141,11 +132,6 @@ func (s *Service) Config() *config.Config {
return s.cfg
}

// Store ...
func (s *Service) Store() storage.Store {
return s.store
}

// MustTxCall ...
func (s *Service) MustTxCall(callback func(txs *Service) error) {
if err := s.TxCall(callback); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion service/modules/renderers/if.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ type Renderer interface {
}

type PathResolver interface {
Resolve(path string) (string, error)
Resolve(path string) string
}
9 changes: 2 additions & 7 deletions service/modules/renderers/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,13 +288,8 @@ func (me *_Markdown) renderImage(w util.BufWriter, source []byte, node ast.Node,
// 看起来是文章内的相对链接?
// 如果是的话,需要 resolve 到相对应的目录。
if !url.IsAbs() && !strings.HasPrefix(url.Path, `/`) && me.pathResolver != nil {
pathRelative, err := me.pathResolver.Resolve(url.Path)
if err != nil {
log.Println(`解析图片相对路径失败`)
// fallthrough
} else {
url.Path = pathRelative
}
pathRelative := me.pathResolver.Resolve(url.Path)
url.Path = pathRelative
}

width, height := size(url)
Expand Down
87 changes: 76 additions & 11 deletions service/modules/storage/if.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package storage

import (
"fmt"
"io"
fspkg "io/fs"
"os"
"path/filepath"
"time"

"github.com/movsb/taoblog/modules/utils"
"github.com/movsb/taoblog/protocols"
)

// File ...
Expand All @@ -14,16 +21,74 @@ type File interface {
Stat() (os.FileInfo, error)
}

// Store exposes interfaces to manage post files.
type Store interface {
List(id int64) ([]string, error)
// path is cleaned.
Open(id int64, path string) (File, error)
// path is cleaned.
Create(id int64, path string) (File, error)
// path is cleaned.
Remove(id int64, path string) error
// 文件子系统接口。
// 所谓“子”,就是针对某篇文章。
// TODO 应该用标准的接口。
type FileSystem interface {
ListFiles() ([]*protocols.FileSpec, error)
DeleteFile(path string) error
OpenFile(path string) (File, error)
WriteFile(spec *protocols.FileSpec, r io.Reader) error
Resolve(path string) string
}

// 针对某篇文章的文件系统实现类。
// 目录结构:配置的文章附件根目录/文章编号/附件路径。
// TODO 改成全局一个实例统一管理所有文章的文件。
type Local struct {
root string
id int64
dir string
}

var _ FileSystem = (*Local)(nil)

func NewLocal(root string, id int64) *Local {
return &Local{
root: root,
id: id,
dir: filepath.Join(root, fmt.Sprint(id)),
}
}

func (fs *Local) pathOf(path string) string {
if path == "" {
panic("path cannot be empty")
}
return filepath.Join(fs.dir, filepath.Clean(path))
}

func (fs *Local) ListFiles() ([]*protocols.FileSpec, error) {
files, err := utils.ListFiles(fs.dir)
if err != nil {
if os.IsNotExist(err) {
err = nil
}
return nil, err
}
return files, nil
}

func (fs *Local) DeleteFile(path string) error {
path = filepath.Clean(path)
path = fs.pathOf(path)
return os.Remove(path)
}

func (fs *Local) OpenFile(path string) (File, error) {
path = fs.pathOf(path)
return os.Open(path)
}

func (fs *Local) WriteFile(spec *protocols.FileSpec, r io.Reader) error {
path := fs.pathOf(spec.Path)
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
return utils.WriteFile(path, fspkg.FileMode(spec.Mode), time.Unix(int64(spec.Time), 0), int64(spec.Size), r)
}

// tmp
PathOf(id int64, path string) (string, error)
func (fs *Local) Resolve(path string) string {
return fs.pathOf(path)
}
Loading

0 comments on commit b6a4a3c

Please sign in to comment.