mirror of
https://github.com/gilbertchen/duplicacy
synced 2025-12-06 00:03:38 +00:00
Fixed a bug that didn't preserve the version bit when copying old snapshots
The version bit should not be set to 1 when encoding a snapshot. Instead, it must be set to 1 on snapshot creation. To correctly process old snapshots encoded incorrectly with version bit set to 1, the first byte of the encoded file list is also checked. If the first byte is `[`, then it must be an old snapshot, since the file list in the new snapshot format always starts with a string encoded in msgpack, the first byte of which can't be `[`.
This commit is contained in:
@@ -15,7 +15,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
"sort"
|
||||
"bytes"
|
||||
|
||||
"github.com/vmihailenco/msgpack"
|
||||
|
||||
@@ -52,6 +51,7 @@ type Snapshot struct {
|
||||
// CreateEmptySnapshot creates an empty snapshot.
|
||||
func CreateEmptySnapshot(id string) (snapshto *Snapshot) {
|
||||
return &Snapshot{
|
||||
Version: 1,
|
||||
ID: id,
|
||||
Revision: 0,
|
||||
StartTime: time.Now().Unix(),
|
||||
@@ -112,22 +112,21 @@ func (snapshot *Snapshot)ListRemoteFiles(config *Config, chunkOperator *ChunkOpe
|
||||
}
|
||||
|
||||
var chunk *Chunk
|
||||
reader := sequenceReader{
|
||||
sequence: snapshot.FileSequence,
|
||||
buffer: new(bytes.Buffer),
|
||||
refillFunc: func(chunkHash string) []byte {
|
||||
if chunk != nil {
|
||||
config.PutChunk(chunk)
|
||||
}
|
||||
chunk = chunkOperator.Download(chunkHash, 0, true)
|
||||
return chunk.GetBytes()
|
||||
},
|
||||
}
|
||||
reader := NewSequenceReader(snapshot.FileSequence, func(chunkHash string) []byte {
|
||||
if chunk != nil {
|
||||
config.PutChunk(chunk)
|
||||
}
|
||||
chunk = chunkOperator.Download(chunkHash, 0, true)
|
||||
return chunk.GetBytes()
|
||||
})
|
||||
|
||||
if snapshot.Version == 0 {
|
||||
// Normally if Version is 0 then the snapshot is created by CLI v2 but unfortunately CLI 3.0.1 does not set the
|
||||
// version bit correctly when copying old backups. So we need to check the first byte -- if it is '[' then it is
|
||||
// the old format. The new format starts with a string encoded in msgpack and the first byte can't be '['.
|
||||
if snapshot.Version == 0 || reader.GetFirstByte() == '['{
|
||||
LOG_INFO("SNAPSHOT_VERSION", "snapshot %s at revision %d is encoded in an old version format", snapshot.ID, snapshot.Revision)
|
||||
files := make([]*Entry, 0)
|
||||
decoder := json.NewDecoder(&reader)
|
||||
decoder := json.NewDecoder(reader)
|
||||
|
||||
// read open bracket
|
||||
_, err := decoder.Token()
|
||||
@@ -156,7 +155,7 @@ func (snapshot *Snapshot)ListRemoteFiles(config *Config, chunkOperator *ChunkOpe
|
||||
}
|
||||
}
|
||||
} else if snapshot.Version == 1 {
|
||||
decoder := msgpack.NewDecoder(&reader)
|
||||
decoder := msgpack.NewDecoder(reader)
|
||||
|
||||
lastEndChunk := 0
|
||||
|
||||
@@ -434,7 +433,7 @@ func (snapshot *Snapshot) MarshalJSON() ([]byte, error) {
|
||||
|
||||
object := make(map[string]interface{})
|
||||
|
||||
object["version"] = 1
|
||||
object["version"] = snapshot.Version
|
||||
object["id"] = snapshot.ID
|
||||
object["revision"] = snapshot.Revision
|
||||
object["options"] = snapshot.Options
|
||||
|
||||
@@ -249,17 +249,27 @@ func (manager *SnapshotManager) DownloadSnapshot(snapshotID string, revision int
|
||||
// the memory before passing them to the json unmarshaller.
|
||||
type sequenceReader struct {
|
||||
sequence []string
|
||||
buffer *bytes.Buffer
|
||||
buffer *bytes.Reader
|
||||
index int
|
||||
refillFunc func(hash string) []byte
|
||||
}
|
||||
|
||||
func NewSequenceReader(sequence []string, refillFunc func(hash string) []byte) *sequenceReader {
|
||||
newData := refillFunc(sequence[0])
|
||||
return &sequenceReader{
|
||||
sequence: sequence,
|
||||
buffer: bytes.NewReader(newData),
|
||||
index: 1,
|
||||
refillFunc: refillFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads a new chunk using the refill function when there is no more data in the buffer
|
||||
func (reader *sequenceReader) Read(data []byte) (n int, err error) {
|
||||
if len(reader.buffer.Bytes()) == 0 {
|
||||
if reader.buffer.Len() == 0 {
|
||||
if reader.index < len(reader.sequence) {
|
||||
newData := reader.refillFunc(reader.sequence[reader.index])
|
||||
reader.buffer.Write(newData)
|
||||
reader.buffer = bytes.NewReader(newData)
|
||||
reader.index++
|
||||
} else {
|
||||
return 0, io.EOF
|
||||
@@ -269,6 +279,16 @@ func (reader *sequenceReader) Read(data []byte) (n int, err error) {
|
||||
return reader.buffer.Read(data)
|
||||
}
|
||||
|
||||
func (reader *sequenceReader) GetFirstByte() byte {
|
||||
b, err := reader.buffer.ReadByte()
|
||||
reader.buffer.UnreadByte()
|
||||
if err != nil {
|
||||
return 0
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SnapshotManager) CreateChunkOperator(resurrect bool, rewriteChunks bool, threads int, allowFailures bool) {
|
||||
if manager.chunkOperator == nil {
|
||||
manager.chunkOperator = CreateChunkOperator(manager.config, manager.storage, manager.snapshotCache, resurrect, rewriteChunks, threads, allowFailures)
|
||||
|
||||
Reference in New Issue
Block a user