Skip to content

Commit 282fb1d

Browse files
committed
Display locally ignored files marked for removal.
1 parent 8e9ee3f commit 282fb1d

22 files changed

+373
-35
lines changed

gui/default/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,12 @@ <h4 class="panel-title">
510510
<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>
511511
</td>
512512
</tr>
513+
<tr ng-if="hasLocalIgnored(folder)">
514+
<th><span class="fas fa-fw fa-exclamation-circle"></span>&nbsp;<span translate>Ignored items</span></th>
515+
<td class="text-right">
516+
<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>
517+
</td>
518+
</tr>
513519
<tr>
514520
<th><span class="fas fa-fw fa-folder"></span>&nbsp;<span translate>Folder Type</span></th>
515521
<td class="text-right">
@@ -1040,6 +1046,7 @@ <h4 class="panel-title">
10401046
<ng-include src="'syncthing/transfer/failedFilesModalView.html'"></ng-include>
10411047
<ng-include src="'syncthing/transfer/remoteNeededFilesModalView.html'"></ng-include>
10421048
<ng-include src="'syncthing/transfer/localChangedFilesModalView.html'"></ng-include>
1049+
<ng-include src="'syncthing/transfer/localIgnoredFilesModalView.html'"></ng-include>
10431050
<ng-include src="'syncthing/core/upgradeModalView.html'"></ng-include>
10441051
<ng-include src="'syncthing/core/majorUpgradeModalView.html'"></ng-include>
10451052
<ng-include src="'syncthing/core/aboutModalView.html'"></ng-include>

gui/default/syncthing/core/syncthingController.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ angular.module('syncthing.core')
6060
$scope.neededFolder = '';
6161
$scope.failed = {};
6262
$scope.localChanged = {};
63+
$scope.localIgnored = {};
6364
$scope.scanProgress = {};
6465
$scope.themes = [];
6566
$scope.globalChangeEvents = {};
@@ -906,6 +907,19 @@ angular.module('syncthing.core')
906907
}).error($scope.emitHTTPError);
907908
};
908909

910+
$scope.refreshLocalIgnored = function (page, perpage) {
911+
console.log("refreshLocalIgnored")
912+
if (!$scope.localIgnoredFolder) {
913+
return;
914+
}
915+
var url = urlbase + '/db/localignored?folder=';
916+
url += encodeURIComponent($scope.localIgnoredFolder);
917+
url += "&page=" + page + "&perpage=" + perpage;
918+
$http.get(url).success(function (data) {
919+
$scope.localIgnored = data;
920+
console.log("refreshLocalIgnored", data)
921+
}).error($scope.emitHTTPError);
922+
};
909923
var refreshDeviceStats = debounce(function () {
910924
$http.get(urlbase + "/stats/device").success(function (data) {
911925
$scope.deviceStats = data;
@@ -2970,13 +2984,33 @@ angular.module('syncthing.core')
29702984
};
29712985

29722986
$scope.hasReceiveOnlyChanged = function (folderCfg) {
2973-
if (!folderCfg || ["receiveonly", "receiveencrypted"].indexOf(folderCfg.type) === -1) {
2987+
if (!folderCfg || ["receiveonly", "receiveencrypted"].indexOf(folderCfg.type) === -1) {
29742988
return false;
29752989
}
29762990
var counts = $scope.model[folderCfg.id];
29772991
return counts && counts.receiveOnlyTotalItems > 0;
29782992
};
29792993

2994+
2995+
$scope.showLocalIgnored = function (folder, folderType) {
2996+
$scope.localIgnoredFolder = folder;
2997+
$scope.localIgnoredType = folderType;
2998+
$scope.localIgnored = $scope.refreshLocalIgnored(1, 10);
2999+
$('#localIgnored').modal().one('hidden.bs.modal', function () {
3000+
$scope.localIgnored = {};
3001+
$scope.localIgnoredFolder = undefined;
3002+
$scope.localIgnoredType = undefined;
3003+
});
3004+
};
3005+
3006+
$scope.hasLocalIgnored = function (folderCfg) {
3007+
if (!folderCfg || ["sendreceive", "sendonly", "receiveonly"].indexOf(folderCfg.type) === -1) {
3008+
return false;
3009+
}
3010+
var counts = $scope.model[folderCfg.id];
3011+
return counts && counts.localIgnoredFiles > 0;
3012+
};
3013+
29803014
$scope.revertOverride = function () {
29813015
$http.post(
29823016
urlbase + "/db/" + $scope.revertOverrideParams.operation +"?folder="
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<modal id="localIgnored" status="info" icon="fas fa-exclamation-circle" heading="{{localIgnoredHeading(localIgnoredType)}}" large="yes" closeable="yes">
2+
<div class="modal-body" ng-switch="localIgnoredType">
3+
<p translate>
4+
The following items were ignored locally.
5+
</p>
6+
<table class="table table-striped">
7+
<thead>
8+
<tr>
9+
<th translate>Path</th>
10+
<th translate>Size</th>
11+
</tr>
12+
</thead>
13+
<tr dir-paginate="file in localIgnored.files | itemsPerPage: localIgnored.perpage" current-page="localIgnored.page" total-items="model[localIgnoredFolder].localIgnoredTotalItems" pagination-id="localIgnored">
14+
<td class="word-break-all">{{file.name}}</td>
15+
<td><span ng-hide="file.type == 'DIRECTORY'">{{file.size | binary}}B</span></td>
16+
</tr>
17+
</table>
18+
<dir-pagination-controls on-page-change="refreshLocalIgnored(newPageNumber, localIgnored.perpage)" pagination-id="localIgnored"></dir-pagination-controls>
19+
<ul class="pagination pull-right">
20+
<li ng-repeat="option in [10, 25, 50]" ng-class="{ active: localIgnored.perpage == option }">
21+
<a href="#" ng-click="refreshLocalIgnored(localIgnored.page, option)">{{option}}</a>
22+
</li>
23+
</ul>
24+
<div class="clearfix"></div>
25+
</div>
26+
<div class="modal-footer">
27+
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
28+
<span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
29+
</button>
30+
</div>
31+
</modal>

lib/api/api.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ func (s *service) Serve(ctx context.Context) error {
253253
restMux.HandlerFunc(http.MethodGet, "/rest/db/need", s.getDBNeed) // folder [perpage] [page]
254254
restMux.HandlerFunc(http.MethodGet, "/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
255255
restMux.HandlerFunc(http.MethodGet, "/rest/db/localchanged", s.getDBLocalChanged) // folder [perpage] [page]
256+
restMux.HandlerFunc(http.MethodGet, "/rest/db/localignored", s.getDBLocalIgnored) // folder [perpage] [page]
256257
restMux.HandlerFunc(http.MethodGet, "/rest/db/status", s.getDBStatus) // folder
257258
restMux.HandlerFunc(http.MethodGet, "/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
258259
restMux.HandlerFunc(http.MethodGet, "/rest/folder/versions", s.getFolderVersions) // folder
@@ -925,6 +926,26 @@ func (s *service) getDBLocalChanged(w http.ResponseWriter, r *http.Request) {
925926
})
926927
}
927928

929+
func (s *service) getDBLocalIgnored(w http.ResponseWriter, r *http.Request) {
930+
qs := r.URL.Query()
931+
932+
folder := qs.Get("folder")
933+
934+
page, perpage := getPagingParams(qs)
935+
936+
files, err := s.model.LocalIgnoredFolderFiles(folder, page, perpage)
937+
if err != nil {
938+
http.Error(w, err.Error(), http.StatusNotFound)
939+
return
940+
}
941+
942+
sendJSON(w, map[string]interface{}{
943+
"files": toJsonFileInfoSlice(files),
944+
"page": page,
945+
"perpage": perpage,
946+
})
947+
}
948+
928949
func (s *service) getSystemConnections(w http.ResponseWriter, _ *http.Request) {
929950
sendJSON(w, s.model.ConnectionStats())
930951
}

lib/db/set.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,10 @@ func (s *Snapshot) ReceiveOnlyChangedSize() Counts {
360360
return s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly)
361361
}
362362

363+
func (s *Snapshot) ReceiveDeleteIgnoredSize() Counts {
364+
return s.meta.Counts(protocol.LocalDeviceID, protocol.FlagDeleteIgnored)
365+
}
366+
363367
func (s *Snapshot) GlobalSize() Counts {
364368
return s.meta.Counts(protocol.GlobalDeviceID, 0)
365369
}

lib/db/set_test.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,14 +1278,20 @@ func TestReceiveOnlyAccounting(t *testing.T) {
12781278
if n := receiveOnlyChangedSize(t, s).Bytes; n != 0 {
12791279
t.Fatal("expected 0 receive only changed bytes initially, not", n)
12801280
}
1281+
if n := receiveDeleteIgnoredSize(t, s).Files; n != 0 {
1282+
t.Fatal("expected 0 receive only remove ignored files initially, not", n)
1283+
}
1284+
if n := receiveDeleteIgnoredSize(t, s).Bytes; n != 0 {
1285+
t.Fatal("expected 0 receive only remove ignored bytes initially, not", n)
1286+
}
12811287

12821288
// Detected a local change in a receive only folder
12831289

12841290
changed := files[0]
12851291
changed.Version = changed.Version.Update(local.Short())
12861292
changed.Size = 100
12871293
changed.ModifiedBy = local.Short()
1288-
changed.LocalFlags = protocol.FlagLocalReceiveOnly
1294+
changed.LocalFlags = protocol.FlagLocalReceiveOnly | protocol.FlagDeleteIgnored
12891295
s.Update(protocol.LocalDeviceID, []protocol.FileInfo{changed})
12901296

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

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

13161328
changed.Version = protocol.Vector{}
13171329
changed.LocalFlags &^= protocol.FlagLocalReceiveOnly
1330+
changed.LocalFlags &^= protocol.FlagDeleteIgnored
13181331
s.Update(protocol.LocalDeviceID, []protocol.FileInfo{changed})
13191332

13201333
s.Update(protocol.LocalDeviceID, []protocol.FileInfo{files[0]})
@@ -1339,6 +1352,12 @@ func TestReceiveOnlyAccounting(t *testing.T) {
13391352
if n := receiveOnlyChangedSize(t, s).Bytes; n != 0 {
13401353
t.Fatal("expected 0 receive only changed bytes after revert, not", n)
13411354
}
1355+
if n := receiveDeleteIgnoredSize(t, s).Files; n != 0 {
1356+
t.Fatal("expected 0 receive only remove ignored files after revert, not", n)
1357+
}
1358+
if n := receiveDeleteIgnoredSize(t, s).Bytes; n != 0 {
1359+
t.Fatal("expected 0 receive only remove ignored bytes after revert, not", n)
1360+
}
13421361
}
13431362

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

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

1868+
func receiveDeleteIgnoredSize(t testing.TB, fs *db.FileSet) db.Counts {
1869+
snap := snapshot(t, fs)
1870+
defer snap.Release()
1871+
return snap.ReceiveDeleteIgnoredSize()
1872+
}
1873+
18491874
func filesToCounts(files []protocol.FileInfo) db.Counts {
18501875
cp := db.Counts{}
18511876
for _, f := range files {

lib/db/structs.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ func (f FileInfoTruncated) IsIgnored() bool {
4747
return f.LocalFlags&protocol.FlagLocalIgnored != 0
4848
}
4949

50+
func (f FileInfoTruncated) IsDeleteIgnored() bool {
51+
return f.LocalFlags&protocol.FlagDeleteIgnored != 0
52+
}
53+
5054
func (f FileInfoTruncated) MustRescan() bool {
5155
return f.LocalFlags&protocol.FlagLocalMustRescan != 0
5256
}
@@ -137,15 +141,22 @@ func (f FileInfoTruncated) FileBlocksHash() []byte {
137141
return f.BlocksHash
138142
}
139143

140-
func (f FileInfoTruncated) ConvertToIgnoredFileInfo() protocol.FileInfo {
144+
func (f FileInfoTruncated) ConvertToIgnoredFileInfo(deleteIgnored bool) protocol.FileInfo {
145+
file := f.copyToFileInfo()
146+
file.SetIgnored(deleteIgnored)
147+
return file
148+
}
149+
150+
func (f FileInfoTruncated) ConvertToDeleteIgnoredFileInfo(deleteIgnored bool) protocol.FileInfo {
141151
file := f.copyToFileInfo()
142-
file.SetIgnored()
152+
file.SetDeleteIgnored(deleteIgnored)
143153
return file
144154
}
145155

146156
func (f FileInfoTruncated) ConvertToDeletedFileInfo(by protocol.ShortID) protocol.FileInfo {
147157
file := f.copyToFileInfo()
148158
file.SetDeleted(by)
159+
file.LocalFlags &^= protocol.FlagDeleteIgnored
149160
return file
150161
}
151162

lib/ignore/ignore.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ import (
2626
)
2727

2828
const (
29-
resultNotMatched Result = 0
30-
resultInclude Result = 1 << iota
31-
resultDeletable = 1 << iota
32-
resultFoldCase = 1 << iota
29+
resultNotMatched Result = 0
30+
resultInclude Result = 1 << iota
31+
resultDeletable = 1 << iota
32+
resultFoldCase = 1 << iota
33+
resultDeleteIgnored = 1 << iota
3334
)
3435

3536
var defaultResult Result = resultInclude
@@ -84,6 +85,9 @@ func (p Pattern) String() string {
8485
if p.result&resultDeletable == resultDeletable {
8586
ret = "(?d)" + ret
8687
}
88+
if p.result&resultDeleteIgnored == resultDeleteIgnored {
89+
ret = "(?r)" + ret
90+
}
8791
return ret
8892
}
8993

@@ -111,6 +115,10 @@ func (r Result) IsDeletable() bool {
111115
return r.IsIgnored() && r&resultDeletable == resultDeletable
112116
}
113117

118+
func (r Result) IsDeleteIgnored() bool {
119+
return r.IsIgnored() && r&resultDeleteIgnored == resultDeleteIgnored
120+
}
121+
114122
func (r Result) IsCaseFolded() bool {
115123
return r&resultFoldCase == resultFoldCase
116124
}
@@ -435,7 +443,7 @@ func parseLine(line string) ([]Pattern, error) {
435443
}
436444

437445
// Allow prefixes to be specified in any order, but only once.
438-
var seenPrefix [3]bool
446+
var seenPrefix [4]bool
439447

440448
for {
441449
if strings.HasPrefix(line, "!") && !seenPrefix[0] {
@@ -450,6 +458,10 @@ func parseLine(line string) ([]Pattern, error) {
450458
seenPrefix[2] = true
451459
pattern.result |= resultDeletable
452460
line = line[4:]
461+
} else if strings.HasPrefix(line, "(?r)") && !seenPrefix[3] {
462+
seenPrefix[3] = true
463+
pattern.result |= resultDeleteIgnored
464+
line = line[4:]
453465
} else {
454466
break
455467
}

0 commit comments

Comments
 (0)