Skip to content

Commit

Permalink
Merge branch 'delete-ignored-existing-items' of https://github.com/p0…
Browse files Browse the repository at this point in the history
…l0us/syncthing into read-only-folder-selective-download-preview
  • Loading branch information
p0l0us committed Sep 28, 2023
2 parents 3b5fb0a + 60fdc1f commit cd4e70b
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 0 deletions.
3 changes: 3 additions & 0 deletions gui/default/index.html
Expand Up @@ -609,6 +609,9 @@ <h4 class="panel-title">
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revertOverrideConfirmationModal('deleteEnc', folder.id)" ng-if="hasReceiveEncryptedItems(folder)">
<span class="fa fa-minus-circle"></span>&nbsp;<span translate>Delete Unexpected Items</span>
</button>
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revertOverrideConfirmationModal('deleteIgn', folder.id)" ng-if="hasReceiveOnlyIgnored(folder)">
<span class="fa fa-arrow-circle-down"></span>&nbsp;<span translate>Delete ignored files</span>
</button>
<span class="pull-right">
<button ng-if="!folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, true)">
<span class="fas fa-pause"></span>&nbsp;<span translate>Pause</span>
Expand Down
13 changes: 13 additions & 0 deletions gui/default/syncthing/core/syncthingController.js
Expand Up @@ -3143,6 +3143,14 @@ angular.module('syncthing.core')
return counts && counts.receiveOnlyTotalItems > 0;
};

$scope.hasReceiveOnlyIgnored = function (folderCfg) {
if (!folderCfg || ["receiveonly"].indexOf(folderCfg.type) === -1) {
return false;
}
var counts = $scope.model[folderCfg.id];
return counts && counts.localIgnoredTotalItems > 0;
};

$scope.showLocalIgnored = function (folder, folderType) {
$scope.localIgnoredFolder = folder;
$scope.localIgnoredType = folderType;
Expand Down Expand Up @@ -3189,6 +3197,11 @@ angular.module('syncthing.core')
params.icon = "fas fa-minus-circle"
params.operation = "revert";
break;
case "deleteIgn":
params.heading = $translate.instant("Delete Ignored Items");
params.icon = "fas fa-minus-circle"
params.operation = "deleteIgnored";
break;
}
$scope.revertOverrideParams = params;
showModal('#revert-override-confirmation');
Expand Down
7 changes: 7 additions & 0 deletions lib/api/api.go
Expand Up @@ -286,6 +286,7 @@ func (s *service) Serve(ctx context.Context) error {
restMux.HandlerFunc(http.MethodPost, "/rest/db/ignores", s.postDBIgnores) // folder
restMux.HandlerFunc(http.MethodPost, "/rest/db/override", s.postDBOverride) // folder
restMux.HandlerFunc(http.MethodPost, "/rest/db/revert", s.postDBRevert) // folder
restMux.HandlerFunc(http.MethodPost, "/rest/db/deleteignored", s.postDBDeleteIgnored) // folder
restMux.HandlerFunc(http.MethodPost, "/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
restMux.HandlerFunc(http.MethodPost, "/rest/folder/versions", s.postFolderVersionsRestore) // folder <body>
restMux.HandlerFunc(http.MethodPost, "/rest/system/error", s.postSystemError) // <body>
Expand Down Expand Up @@ -837,6 +838,12 @@ func (s *service) postDBRevert(_ http.ResponseWriter, r *http.Request) {
go s.model.Revert(folder)
}

func (s *service) postDBDeleteIgnored(_ http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
go s.model.DeleteIgnored(folder)
}

func getPagingParams(qs url.Values) (int, int) {
page, err := strconv.Atoi(qs.Get("page"))
if err != nil || page < 1 {
Expand Down
2 changes: 2 additions & 0 deletions lib/model/folder.go
Expand Up @@ -245,6 +245,8 @@ func (*folder) Override() {}

func (*folder) Revert() {}

func (*folder) DeleteIgnored() {}

func (f *folder) DelayScan(next time.Duration) {
select {
case f.scanDelay <- next:
Expand Down
108 changes: 108 additions & 0 deletions lib/model/folder_recvonly.go
Expand Up @@ -176,6 +176,93 @@ func (f *receiveOnlyFolder) revert() error {
return nil
}

func (f *receiveOnlyFolder) DeleteIgnored() {
f.doInSync(f.deleteIgnored)
}

func (f *receiveOnlyFolder) deleteIgnored() error {
l.Infof("Deleting ignored files from folder %v", f.Description())

f.setState(FolderScanning)
defer f.setState(FolderIdle)

scanChan := make(chan string)
go f.pullScannerRoutine(scanChan)
defer close(scanChan)

delQueue := &deleteQueue{
handler: f, // for the deleteItemOnDisk and deleteDirOnDisk methods
ignores: f.ignores,
scanChan: scanChan,
}

batch := db.NewFileInfoBatch(func(files []protocol.FileInfo) error {
f.updateLocalsFromScanning(files)
return nil
})
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()

snap.WithHave(protocol.LocalDeviceID, func(intf protocol.FileIntf) bool {
fi := intf.(protocol.FileInfo)
if !fi.IsIgnoredExisting() {
// We're only interested in files that are ignored and exists locally.
return true
}

if fi.Deleted {
fi.Version = protocol.Vector{} // if this file ever resurfaces anywhere we want our delete to be strictly older
return true;
}

handled, err := delQueue.handleIgnored(fi, snap)
fi.LocalFlags &^= protocol.FlagLocalIgnoredExisting

if err != nil {
l.Infof("deleteIgnored: deleting %s: %v\n", fi.Name, err)
return true // continue
}
if !handled {
return true // continue
}
fi.SetDeleted(f.shortID)
fi.Version = protocol.Vector{} // if this file ever resurfaces anywhere we want our delete to be strictly older

batch.Append(fi)
_ = batch.FlushIfFull()

return true
})

// Handle any queued directories
deleted, err := delQueue.flush(snap)
if err != nil {
l.Infoln("deleteIgnored:", err)
}
now := time.Now()
for _, dir := range deleted {
batch.Append(protocol.FileInfo{
Name: dir,
Type: protocol.FileInfoTypeDirectory,
ModifiedS: now.Unix(),
ModifiedBy: f.shortID,
Deleted: true,
Version: protocol.Vector{},
})
}
_ = batch.Flush()

// We will likely have changed our local index, but that won't trigger a
// pull by itself. Make sure we schedule one so that we start
// downloading files.
f.SchedulePull()

return nil
}

// deleteQueue handles deletes by delegating to a handler and queuing
// directories for last.
type deleteQueue struct {
Expand Down Expand Up @@ -206,6 +293,27 @@ func (q *deleteQueue) handle(fi protocol.FileInfo, snap *db.Snapshot) (bool, err
return true, err
}


func (q *deleteQueue) handleIgnored(fi protocol.FileInfo, snap *db.Snapshot) (bool, error) {
// TODO: fix this to work with ignored or merge with handle somehow.

// Things that are not ignored are not processed.
ign := q.ignores.Match(fi.Name)
if !ign.IsIgnored() {
return false, nil
}

// Directories are queued for later processing.
if fi.IsDirectory() {
q.dirs = append(q.dirs, fi.Name)
return false, nil
}

// Kill it.
err := q.handler.deleteItemOnDisk(fi, snap, q.scanChan)
return true, err
}

func (q *deleteQueue) flush(snap *db.Snapshot) ([]string, error) {
// Process directories from the leaves inward.
sort.Sort(sort.Reverse(sort.StringSlice(q.dirs)))
Expand Down
35 changes: 35 additions & 0 deletions lib/model/mocks/model.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions lib/model/model.go
Expand Up @@ -52,6 +52,7 @@ type service interface {
BringToFront(string)
Override()
Revert()
DeleteIgnored()
DelayScan(d time.Duration)
ScheduleScan()
SchedulePull() // something relevant changed, we should try a pull
Expand Down Expand Up @@ -85,6 +86,7 @@ type Model interface {
WatchError(folder string) error
Override(folder string)
Revert(folder string)
DeleteIgnored(folder string)
BringToFront(folder, file string)
LoadIgnores(folder string) ([]string, []string, error)
CurrentIgnores(folder string) ([]string, []string, error)
Expand Down Expand Up @@ -2791,6 +2793,20 @@ func (m *model) Revert(folder string) {
runner.Revert()
}

func (m *model) DeleteIgnored(folder string) {
// Grab the runner and the file set.

m.fmut.RLock()
runner, ok := m.folderRunners.Get(folder)
m.fmut.RUnlock()
if !ok {
return
}

// Run the deletation, taking updates as they came from scanning.
runner.DeleteIgnored()
}

type TreeEntry struct {
Name string `json:"name"`
ModTime time.Time `json:"modTime"`
Expand Down

0 comments on commit cd4e70b

Please sign in to comment.