Skip to content

Commit

Permalink
Display locally ignored files marked for removal.
Browse files Browse the repository at this point in the history
  • Loading branch information
p0l0us committed Nov 22, 2023
1 parent 8e9ee3f commit 282fb1d
Show file tree
Hide file tree
Showing 22 changed files with 373 additions and 35 deletions.
7 changes: 7 additions & 0 deletions gui/default/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,12 @@ <h4 class="panel-title">
<a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a>
</td>
</tr>
<tr ng-if="hasLocalIgnored(folder)">
<th><span class="fas fa-fw fa-exclamation-circle"></span>&nbsp;<span translate>Ignored items</span></th>
<td class="text-right">
<a href="" ng-click="showLocalIgnored(folder.id, folder.type)">{{model[folder.id].localIgnoredTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].localIgnoredBytes | binary}}B</a>
</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-folder"></span>&nbsp;<span translate>Folder Type</span></th>
<td class="text-right">
Expand Down Expand Up @@ -1040,6 +1046,7 @@ <h4 class="panel-title">
<ng-include src="'syncthing/transfer/failedFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/remoteNeededFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/localChangedFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/localIgnoredFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/core/upgradeModalView.html'"></ng-include>
<ng-include src="'syncthing/core/majorUpgradeModalView.html'"></ng-include>
<ng-include src="'syncthing/core/aboutModalView.html'"></ng-include>
Expand Down
36 changes: 35 additions & 1 deletion gui/default/syncthing/core/syncthingController.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ angular.module('syncthing.core')
$scope.neededFolder = '';
$scope.failed = {};
$scope.localChanged = {};
$scope.localIgnored = {};
$scope.scanProgress = {};
$scope.themes = [];
$scope.globalChangeEvents = {};
Expand Down Expand Up @@ -906,6 +907,19 @@ angular.module('syncthing.core')
}).error($scope.emitHTTPError);
};

$scope.refreshLocalIgnored = function (page, perpage) {
console.log("refreshLocalIgnored")
if (!$scope.localIgnoredFolder) {
return;
}
var url = urlbase + '/db/localignored?folder=';
url += encodeURIComponent($scope.localIgnoredFolder);
url += "&page=" + page + "&perpage=" + perpage;
$http.get(url).success(function (data) {
$scope.localIgnored = data;
console.log("refreshLocalIgnored", data)
}).error($scope.emitHTTPError);
};
var refreshDeviceStats = debounce(function () {
$http.get(urlbase + "/stats/device").success(function (data) {
$scope.deviceStats = data;
Expand Down Expand Up @@ -2970,13 +2984,33 @@ angular.module('syncthing.core')
};

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


$scope.showLocalIgnored = function (folder, folderType) {
$scope.localIgnoredFolder = folder;
$scope.localIgnoredType = folderType;
$scope.localIgnored = $scope.refreshLocalIgnored(1, 10);
$('#localIgnored').modal().one('hidden.bs.modal', function () {
$scope.localIgnored = {};
$scope.localIgnoredFolder = undefined;
$scope.localIgnoredType = undefined;
});
};

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

$scope.revertOverride = function () {
$http.post(
urlbase + "/db/" + $scope.revertOverrideParams.operation +"?folder="
Expand Down
31 changes: 31 additions & 0 deletions gui/default/syncthing/transfer/localIgnoredFilesModalView.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<modal id="localIgnored" status="info" icon="fas fa-exclamation-circle" heading="{{localIgnoredHeading(localIgnoredType)}}" large="yes" closeable="yes">
<div class="modal-body" ng-switch="localIgnoredType">
<p translate>
The following items were ignored locally.
</p>
<table class="table table-striped">
<thead>
<tr>
<th translate>Path</th>
<th translate>Size</th>
</tr>
</thead>
<tr dir-paginate="file in localIgnored.files | itemsPerPage: localIgnored.perpage" current-page="localIgnored.page" total-items="model[localIgnoredFolder].localIgnoredTotalItems" pagination-id="localIgnored">
<td class="word-break-all">{{file.name}}</td>
<td><span ng-hide="file.type == 'DIRECTORY'">{{file.size | binary}}B</span></td>
</tr>
</table>
<dir-pagination-controls on-page-change="refreshLocalIgnored(newPageNumber, localIgnored.perpage)" pagination-id="localIgnored"></dir-pagination-controls>
<ul class="pagination pull-right">
<li ng-repeat="option in [10, 25, 50]" ng-class="{ active: localIgnored.perpage == option }">
<a href="#" ng-click="refreshLocalIgnored(localIgnored.page, option)">{{option}}</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
</button>
</div>
</modal>
21 changes: 21 additions & 0 deletions lib/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ func (s *service) Serve(ctx context.Context) error {
restMux.HandlerFunc(http.MethodGet, "/rest/db/need", s.getDBNeed) // folder [perpage] [page]
restMux.HandlerFunc(http.MethodGet, "/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
restMux.HandlerFunc(http.MethodGet, "/rest/db/localchanged", s.getDBLocalChanged) // folder [perpage] [page]
restMux.HandlerFunc(http.MethodGet, "/rest/db/localignored", s.getDBLocalIgnored) // folder [perpage] [page]
restMux.HandlerFunc(http.MethodGet, "/rest/db/status", s.getDBStatus) // folder
restMux.HandlerFunc(http.MethodGet, "/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
restMux.HandlerFunc(http.MethodGet, "/rest/folder/versions", s.getFolderVersions) // folder
Expand Down Expand Up @@ -925,6 +926,26 @@ func (s *service) getDBLocalChanged(w http.ResponseWriter, r *http.Request) {
})
}

func (s *service) getDBLocalIgnored(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()

folder := qs.Get("folder")

page, perpage := getPagingParams(qs)

files, err := s.model.LocalIgnoredFolderFiles(folder, page, perpage)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}

sendJSON(w, map[string]interface{}{
"files": toJsonFileInfoSlice(files),
"page": page,
"perpage": perpage,
})
}

func (s *service) getSystemConnections(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, s.model.ConnectionStats())
}
Expand Down
4 changes: 4 additions & 0 deletions lib/db/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ func (s *Snapshot) ReceiveOnlyChangedSize() Counts {
return s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly)
}

func (s *Snapshot) ReceiveDeleteIgnoredSize() Counts {
return s.meta.Counts(protocol.LocalDeviceID, protocol.FlagDeleteIgnored)
}

func (s *Snapshot) GlobalSize() Counts {
return s.meta.Counts(protocol.GlobalDeviceID, 0)
}
Expand Down
29 changes: 27 additions & 2 deletions lib/db/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1278,14 +1278,20 @@ func TestReceiveOnlyAccounting(t *testing.T) {
if n := receiveOnlyChangedSize(t, s).Bytes; n != 0 {
t.Fatal("expected 0 receive only changed bytes initially, not", n)
}
if n := receiveDeleteIgnoredSize(t, s).Files; n != 0 {
t.Fatal("expected 0 receive only remove ignored files initially, not", n)
}
if n := receiveDeleteIgnoredSize(t, s).Bytes; n != 0 {
t.Fatal("expected 0 receive only remove ignored bytes initially, not", n)
}

// Detected a local change in a receive only folder

changed := files[0]
changed.Version = changed.Version.Update(local.Short())
changed.Size = 100
changed.ModifiedBy = local.Short()
changed.LocalFlags = protocol.FlagLocalReceiveOnly
changed.LocalFlags = protocol.FlagLocalReceiveOnly | protocol.FlagDeleteIgnored
s.Update(protocol.LocalDeviceID, []protocol.FileInfo{changed})

// Check that we see the files
Expand All @@ -1308,13 +1314,20 @@ func TestReceiveOnlyAccounting(t *testing.T) {
if n := receiveOnlyChangedSize(t, s).Bytes; n != 100 {
t.Fatal("expected 100 receive only changed bytes after local change, not", n)
}
if n := receiveDeleteIgnoredSize(t, s).Files; n != 1 {
t.Fatal("expected 1 receive only remove ignored file after local change, not", n)
}
if n := receiveDeleteIgnoredSize(t, s).Bytes; n != 100 {
t.Fatal("expected 100 receive only remove ignored bytes after local change, not", n)
}

// Fake a revert. That's a two step process, first converting our
// changed file into a less preferred variant, then pulling down the old
// version.

changed.Version = protocol.Vector{}
changed.LocalFlags &^= protocol.FlagLocalReceiveOnly
changed.LocalFlags &^= protocol.FlagDeleteIgnored
s.Update(protocol.LocalDeviceID, []protocol.FileInfo{changed})

s.Update(protocol.LocalDeviceID, []protocol.FileInfo{files[0]})
Expand All @@ -1339,6 +1352,12 @@ func TestReceiveOnlyAccounting(t *testing.T) {
if n := receiveOnlyChangedSize(t, s).Bytes; n != 0 {
t.Fatal("expected 0 receive only changed bytes after revert, not", n)
}
if n := receiveDeleteIgnoredSize(t, s).Files; n != 0 {
t.Fatal("expected 0 receive only remove ignored files after revert, not", n)
}
if n := receiveDeleteIgnoredSize(t, s).Bytes; n != 0 {
t.Fatal("expected 0 receive only remove ignored bytes after revert, not", n)
}
}

func TestNeedAfterUnignore(t *testing.T) {
Expand All @@ -1353,7 +1372,7 @@ func TestNeedAfterUnignore(t *testing.T) {

// Initial state: Devices in sync, locally ignored
local := protocol.FileInfo{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: remID, Value: 1}, {ID: myID, Value: 1}}}, ModifiedS: 10}
local.SetIgnored()
local.SetIgnored(false)
remote := protocol.FileInfo{Name: file, Version: protocol.Vector{Counters: []protocol.Counter{{ID: remID, Value: 1}, {ID: myID, Value: 1}}}, ModifiedS: 10}
s.Update(protocol.LocalDeviceID, fileList{local})
s.Update(remoteDevice0, fileList{remote})
Expand Down Expand Up @@ -1846,6 +1865,12 @@ func receiveOnlyChangedSize(t testing.TB, fs *db.FileSet) db.Counts {
return snap.ReceiveOnlyChangedSize()
}

func receiveDeleteIgnoredSize(t testing.TB, fs *db.FileSet) db.Counts {
snap := snapshot(t, fs)
defer snap.Release()
return snap.ReceiveDeleteIgnoredSize()
}

func filesToCounts(files []protocol.FileInfo) db.Counts {
cp := db.Counts{}
for _, f := range files {
Expand Down
15 changes: 13 additions & 2 deletions lib/db/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func (f FileInfoTruncated) IsIgnored() bool {
return f.LocalFlags&protocol.FlagLocalIgnored != 0
}

func (f FileInfoTruncated) IsDeleteIgnored() bool {
return f.LocalFlags&protocol.FlagDeleteIgnored != 0
}

func (f FileInfoTruncated) MustRescan() bool {
return f.LocalFlags&protocol.FlagLocalMustRescan != 0
}
Expand Down Expand Up @@ -137,15 +141,22 @@ func (f FileInfoTruncated) FileBlocksHash() []byte {
return f.BlocksHash
}

func (f FileInfoTruncated) ConvertToIgnoredFileInfo() protocol.FileInfo {
func (f FileInfoTruncated) ConvertToIgnoredFileInfo(deleteIgnored bool) protocol.FileInfo {
file := f.copyToFileInfo()
file.SetIgnored(deleteIgnored)
return file
}

func (f FileInfoTruncated) ConvertToDeleteIgnoredFileInfo(deleteIgnored bool) protocol.FileInfo {
file := f.copyToFileInfo()
file.SetIgnored()
file.SetDeleteIgnored(deleteIgnored)
return file
}

func (f FileInfoTruncated) ConvertToDeletedFileInfo(by protocol.ShortID) protocol.FileInfo {
file := f.copyToFileInfo()
file.SetDeleted(by)
file.LocalFlags &^= protocol.FlagDeleteIgnored
return file
}

Expand Down
22 changes: 17 additions & 5 deletions lib/ignore/ignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ import (
)

const (
resultNotMatched Result = 0
resultInclude Result = 1 << iota
resultDeletable = 1 << iota
resultFoldCase = 1 << iota
resultNotMatched Result = 0
resultInclude Result = 1 << iota
resultDeletable = 1 << iota
resultFoldCase = 1 << iota
resultDeleteIgnored = 1 << iota
)

var defaultResult Result = resultInclude
Expand Down Expand Up @@ -84,6 +85,9 @@ func (p Pattern) String() string {
if p.result&resultDeletable == resultDeletable {
ret = "(?d)" + ret
}
if p.result&resultDeleteIgnored == resultDeleteIgnored {
ret = "(?r)" + ret
}
return ret
}

Expand Down Expand Up @@ -111,6 +115,10 @@ func (r Result) IsDeletable() bool {
return r.IsIgnored() && r&resultDeletable == resultDeletable
}

func (r Result) IsDeleteIgnored() bool {
return r.IsIgnored() && r&resultDeleteIgnored == resultDeleteIgnored
}

func (r Result) IsCaseFolded() bool {
return r&resultFoldCase == resultFoldCase
}
Expand Down Expand Up @@ -435,7 +443,7 @@ func parseLine(line string) ([]Pattern, error) {
}

// Allow prefixes to be specified in any order, but only once.
var seenPrefix [3]bool
var seenPrefix [4]bool

for {
if strings.HasPrefix(line, "!") && !seenPrefix[0] {
Expand All @@ -450,6 +458,10 @@ func parseLine(line string) ([]Pattern, error) {
seenPrefix[2] = true
pattern.result |= resultDeletable
line = line[4:]
} else if strings.HasPrefix(line, "(?r)") && !seenPrefix[3] {
seenPrefix[3] = true
pattern.result |= resultDeleteIgnored
line = line[4:]
} else {
break
}
Expand Down
Loading

0 comments on commit 282fb1d

Please sign in to comment.