mirror of
https://github.com/gilbertchen/duplicacy
synced 2025-12-06 00:03:38 +00:00
Compare commits
13 Commits
azure_retr
...
v2.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc35ddf7d1 | ||
|
|
6efcd37c5c | ||
|
|
58558b8a2f | ||
|
|
045be3905b | ||
|
|
4da7f7b6f9 | ||
|
|
41668d4bbd | ||
|
|
9d4ac34f4b | ||
|
|
eba5aa6eea | ||
|
|
47c4c25d8b | ||
|
|
37781f9540 | ||
|
|
282fe4edd2 | ||
|
|
33c71ca5f8 | ||
|
|
6e7d45caac |
18
.github/ISSUE_TEMPLATE.md
vendored
18
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,5 +1,17 @@
|
|||||||
Please submit an issue for bug reports or feature requests. If you have any questions please post them on https://forum.duplicacy.com.
|
---
|
||||||
|
name: Please use the official forum
|
||||||
|
about: Please use the official forum instead of Github
|
||||||
|
title: 'Please use the official forum'
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
When you're reporting a bug, please specify the OS, version, command line arguments, or any info that you think is helpful for the diagnosis. If Duplicacy reports an error, please post the program output here.
|
---
|
||||||
|
|
||||||
Note that this repository hosts the CLI version of Duplicacy only. If you're reporting anything related to the GUI version, please visit https://forum.duplicacy.com.
|
|
||||||
|
Please **use the [Duplicacy Forum](https://forum.duplicacy.com/)** when reporting bugs, making feature requests, asking for help or simply praising Duplicacy for its ease of use.
|
||||||
|
|
||||||
|
We strongly encourage you to create an account on the forum and use that platform for discussion as there is a higher chance that someone there will talk to you.
|
||||||
|
|
||||||
|
There is a handful of people watching the Github Issues and we are in the process of moving **all** of them to the forum as well. Most likely you will not receive an answer here or it will be very slow and you will be pointed to the forum.
|
||||||
|
|
||||||
|
We have already created a comprehensive [Guide](https://forum.duplicacy.com/t/duplicacy-user-guide/1197), and a [How-To](https://forum.duplicacy.com/c/how-to) category which stores more wisdom than these issues on Github.
|
||||||
|
|||||||
2
Gopkg.lock
generated
2
Gopkg.lock
generated
@@ -71,7 +71,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/gilbertchen/go.dbus"
|
name = "github.com/gilbertchen/go.dbus"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "9e442e6378618c083fd3b85b703ffd202721fb17"
|
revision = "8591994fa32f1dbe3fa9486bc6f4d4361ac16649"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
Copyright © 2017 Acrosync LLC
|
Copyright © 2017 Acrosync LLC
|
||||||
|
|
||||||
* Free for personal use or commercial trial
|
* Free for personal use or commercial trial
|
||||||
* Non-trial commercial use requires per-user CLI licenses available from [duplicacy.com](https://duplicacy.com/buy) at a cost of $20 per year
|
* Non-trial commercial use requires per-computer CLI licenses available from [duplicacy.com](https://duplicacy.com/buy.html) at a cost of $50 per year
|
||||||
* A user is defined as the computer account that creates or edits the files to be backed up; if a backup contains files created or edited by multiple users for commercial purposes, one CLI license is required for each user
|
|
||||||
* The computer with a valid commercial license for the GUI version may run the CLI version without a CLI license
|
* The computer with a valid commercial license for the GUI version may run the CLI version without a CLI license
|
||||||
* CLI licenses are not required to restore or manage backups; only the backup command requires valid CLI licenses
|
* CLI licenses are not required to restore or manage backups; only the backup command requires valid CLI licenses
|
||||||
* Modification and redistribution are permitted, but commercial use of derivative works is subject to the same requirements of this license
|
* Modification and redistribution are permitted, but commercial use of derivative works is subject to the same requirements of this license
|
||||||
|
|||||||
@@ -90,8 +90,7 @@ The following table compares the feature lists of all these backup tools:
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
* Free for personal use or commercial trial
|
* Free for personal use or commercial trial
|
||||||
* Non-trial commercial use requires per-user CLI licenses available from [duplicacy.com](https://duplicacy.com/buy) at a cost of $20 per year
|
* Non-trial commercial use requires per-computer CLI licenses available from [duplicacy.com](https://duplicacy.com/buy.html) at a cost of $50 per year
|
||||||
* A user is defined as the computer account that creates or edits the files to be backed up; if a backup contains files created or edited by multiple users for commercial purposes, one CLI license is required for each user
|
|
||||||
* The computer with a valid commercial license for the GUI version may run the CLI version without a CLI license
|
* The computer with a valid commercial license for the GUI version may run the CLI version without a CLI license
|
||||||
* CLI licenses are not required to restore or manage backups; only the backup command requires valid CLI licenses
|
* CLI licenses are not required to restore or manage backups; only the backup command requires valid CLI licenses
|
||||||
* Modification and redistribution are permitted, but commercial use of derivative works is subject to the same requirements of this license
|
* Modification and redistribution are permitted, but commercial use of derivative works is subject to the same requirements of this license
|
||||||
|
|||||||
@@ -1981,7 +1981,7 @@ func main() {
|
|||||||
app.Name = "duplicacy"
|
app.Name = "duplicacy"
|
||||||
app.HelpName = "duplicacy"
|
app.HelpName = "duplicacy"
|
||||||
app.Usage = "A new generation cloud backup tool based on lock-free deduplication"
|
app.Usage = "A new generation cloud backup tool based on lock-free deduplication"
|
||||||
app.Version = "2.2.0" + " (" + GitCommit + ")"
|
app.Version = "2.2.2" + " (" + GitCommit + ")"
|
||||||
|
|
||||||
// If the program is interrupted, call the RunAtError function.
|
// If the program is interrupted, call the RunAtError function.
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ type B2Client struct {
|
|||||||
Threads int
|
Threads int
|
||||||
MaximumRetries int
|
MaximumRetries int
|
||||||
TestMode bool
|
TestMode bool
|
||||||
|
|
||||||
|
LastAuthorizationTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL encode the given path but keep the slashes intact
|
// URL encode the given path but keep the slashes intact
|
||||||
@@ -253,8 +255,12 @@ func (client *B2Client) call(threadIndex int, requestURL string, method string,
|
|||||||
if requestURL == B2AuthorizationURL {
|
if requestURL == B2AuthorizationURL {
|
||||||
return nil, nil, 0, fmt.Errorf("Authorization failure")
|
return nil, nil, 0, fmt.Errorf("Authorization failure")
|
||||||
}
|
}
|
||||||
client.AuthorizeAccount(threadIndex)
|
|
||||||
continue
|
// Attempt authorization again. If authorization is actually not done, run the random backoff
|
||||||
|
_, allowed := client.AuthorizeAccount(threadIndex)
|
||||||
|
if allowed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
} else if response.StatusCode == 403 {
|
} else if response.StatusCode == 403 {
|
||||||
if !client.TestMode {
|
if !client.TestMode {
|
||||||
return nil, nil, 0, fmt.Errorf("B2 cap exceeded")
|
return nil, nil, 0, fmt.Errorf("B2 cap exceeded")
|
||||||
@@ -291,13 +297,18 @@ type B2AuthorizeAccountOutput struct {
|
|||||||
DownloadURL string
|
DownloadURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *B2Client) AuthorizeAccount(threadIndex int) (err error) {
|
func (client *B2Client) AuthorizeAccount(threadIndex int) (err error, allowed bool) {
|
||||||
client.Lock.Lock()
|
client.Lock.Lock()
|
||||||
defer client.Lock.Unlock()
|
defer client.Lock.Unlock()
|
||||||
|
|
||||||
|
// Don't authorize if the previous one was done less than 30 seconds ago
|
||||||
|
if client.LastAuthorizationTime != 0 && client.LastAuthorizationTime > time.Now().Unix() - 30 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
readCloser, _, _, err := client.call(threadIndex, B2AuthorizationURL, http.MethodPost, nil, make(map[string]string))
|
readCloser, _, _, err := client.call(threadIndex, B2AuthorizationURL, http.MethodPost, nil, make(map[string]string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err, true
|
||||||
}
|
}
|
||||||
|
|
||||||
defer readCloser.Close()
|
defer readCloser.Close()
|
||||||
@@ -305,7 +316,7 @@ func (client *B2Client) AuthorizeAccount(threadIndex int) (err error) {
|
|||||||
output := &B2AuthorizeAccountOutput{}
|
output := &B2AuthorizeAccountOutput{}
|
||||||
|
|
||||||
if err = json.NewDecoder(readCloser).Decode(&output); err != nil {
|
if err = json.NewDecoder(readCloser).Decode(&output); err != nil {
|
||||||
return err
|
return err, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// The account id may be different from the application key id so we're getting the account id from the returned
|
// The account id may be different from the application key id so we're getting the account id from the returned
|
||||||
@@ -317,7 +328,9 @@ func (client *B2Client) AuthorizeAccount(threadIndex int) (err error) {
|
|||||||
client.DownloadURL = output.DownloadURL
|
client.DownloadURL = output.DownloadURL
|
||||||
client.IsAuthorized = true
|
client.IsAuthorized = true
|
||||||
|
|
||||||
return nil
|
client.LastAuthorizationTime = time.Now().Unix()
|
||||||
|
|
||||||
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListBucketOutput struct {
|
type ListBucketOutput struct {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func TestB2Client(t *testing.T) {
|
|||||||
|
|
||||||
b2Client.TestMode = true
|
b2Client.TestMode = true
|
||||||
|
|
||||||
err := b2Client.AuthorizeAccount(0)
|
err, _ := b2Client.AuthorizeAccount(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to authorize the b2 account: %v", err)
|
t.Errorf("Failed to authorize the b2 account: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func CreateB2Storage(accountID string, applicationKey string, bucket string, sto
|
|||||||
|
|
||||||
client := NewB2Client(accountID, applicationKey, storageDir, threads)
|
client := NewB2Client(accountID, applicationKey, storageDir, threads)
|
||||||
|
|
||||||
err = client.AuthorizeAccount(0)
|
err, _ = client.AuthorizeAccount(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -807,6 +807,7 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
if compare == 0 {
|
if compare == 0 {
|
||||||
i++
|
i++
|
||||||
if quickMode && local.IsSameAs(entry) {
|
if quickMode && local.IsSameAs(entry) {
|
||||||
|
LOG_TRACE("RESTORE_SKIP", "File %s unchanged (by size and timestamp)", local.Path)
|
||||||
skipped = true
|
skipped = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -898,7 +899,8 @@ func (manager *BackupManager) Restore(top string, revision int, inPlace bool, qu
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = os.MkdirAll(path.Dir(fullPath), 0744)
|
parent, _ := SplitDir(fullPath)
|
||||||
|
err = os.MkdirAll(parent, 0744)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("DOWNLOAD_MKDIR", "Failed to create directory: %v", err)
|
LOG_ERROR("DOWNLOAD_MKDIR", "Failed to create directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,6 +197,16 @@ func (downloader *ChunkDownloader) Reclaim(chunkIndex int) {
|
|||||||
downloader.lastChunkIndex = chunkIndex
|
downloader.lastChunkIndex = chunkIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the chunk last downloaded and its hash
|
||||||
|
func (downloader *ChunkDownloader) GetLastDownloadedChunk() (chunk *Chunk, chunkHash string) {
|
||||||
|
if downloader.lastChunkIndex >= len(downloader.taskList) {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
task := downloader.taskList[downloader.lastChunkIndex]
|
||||||
|
return task.chunk, task.chunkHash
|
||||||
|
}
|
||||||
|
|
||||||
// WaitForChunk waits until the specified chunk is ready
|
// WaitForChunk waits until the specified chunk is ready
|
||||||
func (downloader *ChunkDownloader) WaitForChunk(chunkIndex int) (chunk *Chunk) {
|
func (downloader *ChunkDownloader) WaitForChunk(chunkIndex int) (chunk *Chunk) {
|
||||||
|
|
||||||
|
|||||||
@@ -1194,7 +1194,6 @@ func (manager *SnapshotManager) RetrieveFile(snapshot *Snapshot, file *Entry, ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var chunk *Chunk
|
var chunk *Chunk
|
||||||
currentHash := ""
|
|
||||||
|
|
||||||
for i := file.StartChunk; i <= file.EndChunk; i++ {
|
for i := file.StartChunk; i <= file.EndChunk; i++ {
|
||||||
start := 0
|
start := 0
|
||||||
@@ -1207,10 +1206,12 @@ func (manager *SnapshotManager) RetrieveFile(snapshot *Snapshot, file *Entry, ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
hash := snapshot.ChunkHashes[i]
|
hash := snapshot.ChunkHashes[i]
|
||||||
if currentHash != hash {
|
lastChunk, lastChunkHash := manager.chunkDownloader.GetLastDownloadedChunk()
|
||||||
|
if lastChunkHash != hash {
|
||||||
i := manager.chunkDownloader.AddChunk(hash)
|
i := manager.chunkDownloader.AddChunk(hash)
|
||||||
chunk = manager.chunkDownloader.WaitForChunk(i)
|
chunk = manager.chunkDownloader.WaitForChunk(i)
|
||||||
currentHash = hash
|
} else {
|
||||||
|
chunk = lastChunk
|
||||||
}
|
}
|
||||||
|
|
||||||
output(chunk.GetBytes()[start:end])
|
output(chunk.GetBytes()[start:end])
|
||||||
@@ -1482,7 +1483,11 @@ func (manager *SnapshotManager) Diff(top string, snapshotID string, revisions []
|
|||||||
same = right.IsSameAs(left)
|
same = right.IsSameAs(left)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
same = left.Hash == right.Hash
|
if left.Size == 0 && right.Size == 0 {
|
||||||
|
same = true
|
||||||
|
} else {
|
||||||
|
same = left.Hash == right.Hash
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !same {
|
if !same {
|
||||||
|
|||||||
@@ -88,3 +88,7 @@ func (entry *Entry) SetAttributesToFile(fullPath string) {
|
|||||||
func joinPath(components ...string) string {
|
func joinPath(components ...string) string {
|
||||||
return path.Join(components...)
|
return path.Join(components...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitDir(fullPath string) (dir string, file string) {
|
||||||
|
return path.Split(fullPath)
|
||||||
|
}
|
||||||
|
|||||||
@@ -126,3 +126,8 @@ func joinPath(components ...string) string {
|
|||||||
}
|
}
|
||||||
return combinedPath
|
return combinedPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitDir(fullPath string) (dir string, file string) {
|
||||||
|
i := strings.LastIndex(fullPath, "\\")
|
||||||
|
return fullPath[:i+1], fullPath[i+1:]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user