mirror of
https://github.com/rclone/rclone.git
synced 2025-04-19 18:31:10 +08:00
Merge 1c460a38536faedf0d7a8318fb9a616a28d35dc7 into 4d38424e6cbb32d07c74d3b4d760af7afb35742d
This commit is contained in:
commit
8ddac2b7b3
@ -50,6 +50,10 @@ func init() {
|
||||
Advanced: true,
|
||||
Default: fs.SizeSuffix(0),
|
||||
Help: "Auto-update checksum for files smaller than this size (disabled by default).",
|
||||
}, {
|
||||
Name: "read_only",
|
||||
Default: false,
|
||||
Help: "Set the db in read only mode",
|
||||
}},
|
||||
})
|
||||
}
|
||||
@ -60,6 +64,7 @@ type Options struct {
|
||||
Hashes fs.CommaSepList `config:"hashes"`
|
||||
AutoSize fs.SizeSuffix `config:"auto_size"`
|
||||
MaxAge fs.Duration `config:"max_age"`
|
||||
ReadOnly bool `config:"read_only"`
|
||||
}
|
||||
|
||||
// Fs represents a wrapped fs.Fs
|
||||
|
@ -4,10 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fs/config/configmap"
|
||||
"github.com/rclone/rclone/fs/config/obscure"
|
||||
"github.com/rclone/rclone/fs/object"
|
||||
"github.com/rclone/rclone/fs/operations"
|
||||
"github.com/rclone/rclone/fstest"
|
||||
"github.com/rclone/rclone/fstest/fstests"
|
||||
@ -75,6 +79,38 @@ func (f *Fs) InternalTest(t *testing.T) {
|
||||
t.Skip("hasher is not supported on this OS")
|
||||
}
|
||||
t.Run("UploadFromCrypt", f.testUploadFromCrypt)
|
||||
t.Run("ReadOnlyFlag", f.testReadOnlyFlag)
|
||||
}
|
||||
|
||||
var _ fstests.InternalTester = (*Fs)(nil)
|
||||
type testGetter struct{}
|
||||
|
||||
func (s *testGetter) Get(key string) (value string, ok bool) {
|
||||
switch key {
|
||||
case "remote":
|
||||
return "/hasher-test", true
|
||||
case "hashes":
|
||||
return "md5", true
|
||||
case "max_age":
|
||||
return "off", true
|
||||
case "auto_size":
|
||||
return "0", true
|
||||
case "read_only":
|
||||
return "true", true
|
||||
default:
|
||||
return key, true
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Fs) testReadOnlyFlag(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mapper := configmap.New()
|
||||
mapper.AddGetter(&testGetter{}, 1)
|
||||
hasherFs, err := NewFs(ctx, "hasher-test", "/hasher-test", mapper)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, hasherFs)
|
||||
|
||||
fileInfo := object.NewStaticObjectInfo("hasher-test", time.Now(), 128, true, nil, nil)
|
||||
_, err = hasherFs.Put(ctx, strings.NewReader("dogs and cats"), fileInfo)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "read-only file system")
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fs/hash"
|
||||
"github.com/rclone/rclone/fs/operations"
|
||||
"github.com/rclone/rclone/lib/kv"
|
||||
)
|
||||
|
||||
// obtain hash for an object
|
||||
@ -58,12 +59,17 @@ func (o *Object) putHashes(ctx context.Context, rawHashes hashMap) error {
|
||||
|
||||
// set hashes for a path without any validation
|
||||
func (f *Fs) putRawHashes(ctx context.Context, key, fp string, hashes operations.HashSums) error {
|
||||
return f.db.Do(true, &kvPut{
|
||||
err := f.db.Do(true, &kvPut{
|
||||
key: key,
|
||||
fp: fp,
|
||||
hashes: hashes,
|
||||
age: time.Duration(f.opt.MaxAge),
|
||||
})
|
||||
if f.opt.ReadOnly && errors.Is(err, kv.ErrReadOnly) {
|
||||
fs.Debugf(nil, "database in read only mode")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Hash returns the selected checksum of the file or "" if unavailable.
|
||||
|
@ -33,6 +33,7 @@ type DB struct {
|
||||
refs int
|
||||
bolt *bbolt.DB
|
||||
mu sync.Mutex
|
||||
readOnly bool
|
||||
canWrite bool
|
||||
queue chan *request
|
||||
lockTime time.Duration
|
||||
@ -138,6 +139,10 @@ func (db *DB) open(ctx context.Context, forWrite bool) (err error) {
|
||||
}
|
||||
_ = db.close()
|
||||
|
||||
if db.readOnly && forWrite {
|
||||
return ErrReadOnly
|
||||
}
|
||||
|
||||
db.canWrite = forWrite
|
||||
if !forWrite {
|
||||
// mitigate https://github.com/etcd-io/bbolt/issues/98
|
||||
@ -228,6 +233,11 @@ func (db *DB) Do(write bool, op Op) error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// ReadOnly setter the db.readOnly
|
||||
func (db *DB) ReadOnly(b bool) {
|
||||
db.readOnly = b
|
||||
}
|
||||
|
||||
// request encapsulates a synchronous operation and its results
|
||||
type request struct {
|
||||
op Op
|
||||
|
@ -66,3 +66,27 @@ func TestKvExit(t *testing.T) {
|
||||
Exit()
|
||||
assert.Equal(t, 0, len(dbMap))
|
||||
}
|
||||
|
||||
func TestDbReadOnly(t *testing.T) {
|
||||
require.Equal(t, 0, len(dbMap), "no databases can be started initially")
|
||||
ctx := context.Background()
|
||||
|
||||
db, err := Start(ctx, "test", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, db)
|
||||
|
||||
assert.False(t, db.readOnly)
|
||||
|
||||
// set db in read only
|
||||
db.ReadOnly(true)
|
||||
|
||||
assert.True(t, db.readOnly)
|
||||
|
||||
// write op is not allowed in read only mode, should throw error
|
||||
err = db.Do(true, nil)
|
||||
assert.ErrorIs(t, err, ErrReadOnly)
|
||||
|
||||
db.ReadOnly(false)
|
||||
err = db.Do(true, &opStop{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ var (
|
||||
ErrEmpty = errors.New("database empty")
|
||||
ErrInactive = errors.New("database stopped")
|
||||
ErrUnsupported = errors.New("unsupported on this OS")
|
||||
ErrReadOnly = errors.New("database in read-only mode")
|
||||
)
|
||||
|
||||
// Op represents a database operation
|
||||
|
Loading…
x
Reference in New Issue
Block a user