Skip to content

Commit 9e9a42d

Browse files
authored
Merge pull request #1268 from couchbase/feature/ttl
Fixed #1207
2 parents 9d0942f + 9271205 commit 9e9a42d

7 files changed

Lines changed: 254 additions & 23 deletions

File tree

src/main/java/com/couchbase/lite/Database.java

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,15 @@
5454
import java.net.URL;
5555
import java.util.ArrayList;
5656
import java.util.Collections;
57+
import java.util.Date;
5758
import java.util.HashMap;
5859
import java.util.HashSet;
5960
import java.util.List;
6061
import java.util.Locale;
6162
import java.util.Map;
6263
import java.util.Set;
64+
import java.util.Timer;
65+
import java.util.TimerTask;
6366
import java.util.concurrent.CopyOnWriteArraySet;
6467
import java.util.concurrent.Future;
6568
import java.util.concurrent.atomic.AtomicBoolean;
@@ -70,9 +73,13 @@
7073
public class Database implements StoreDelegate {
7174

7275
public static final String TAG = Log.TAG_DATABASE;
76+
7377
// When this many changes pile up in _changesToNotify, start removing their bodies to save RAM
7478
private static final int MANY_CHANGES_TO_NOTIFY = 5000;
7579

80+
// How long to wait after a database opens before expiring docs
81+
private static final long kHousekeepingDelayAfterOpening = 3;
82+
7683
private static final String DEFAULT_PBKDF2_KEY_SALT = "Salty McNaCl";
7784
private static final int DEFAULT_PBKDF2_KEY_ROUNDS = 64000;
7885

@@ -120,6 +127,7 @@ public class Database implements StoreDelegate {
120127
private boolean postingChangeNotifications;
121128
private final Object lockPostingChangeNotifications = new Object();
122129
private final long startTime;
130+
private Timer purgeTimer;
123131

124132
/**
125133
* Each database can have an associated PersistentCookieStore,
@@ -715,8 +723,11 @@ public void storageExitedTransaction(boolean committed) {
715723
*/
716724
@InterfaceAudience.Private
717725
public void databaseStorageChanged(DocumentChange change) {
718-
Log.v(Log.TAG, "---> Added: %s as seq %d",
726+
if (change.getRevisionId() != null)
727+
Log.v(Log.TAG, "---> Added: %s as seq %d",
719728
change.getAddedRevision(), change.getAddedRevision().getSequence());
729+
else
730+
Log.v(Log.TAG, "---> Purged: docID=%s", change.getDocumentId());
720731

721732
changesToNotify.add(change);
722733
if (!postChangeNotifications()) {
@@ -1308,6 +1319,8 @@ public synchronized void open(DatabaseOptions options) throws CouchbaseLiteExcep
13081319
upgrader.deleteSQLiteFiles();
13091320
}
13101321
}
1322+
1323+
scheduleDocumentExpiration(kHousekeepingDelayAfterOpening);
13111324
}
13121325

13131326
/**
@@ -1391,6 +1404,9 @@ public boolean close() {
13911404
// Clear all replicators:
13921405
allReplicators.clear();
13931406

1407+
// cancel purge timer
1408+
cancelPurgeTimer();
1409+
13941410
// Close Store:
13951411
if (store != null)
13961412
store.close();
@@ -2250,6 +2266,48 @@ private void installAttachment(AttachmentInternal attachment) throws CouchbaseLi
22502266
}
22512267
}
22522268

2269+
// #pragma mark - EXPIRATION:
2270+
2271+
/* package */void setExpirationDate(Date date, String docID) {
2272+
long unixTime = date != null ? date.getTime() / 1000 : 0;
2273+
store.setExpirationOfDocument(unixTime, docID);
2274+
scheduleDocumentExpiration(0);
2275+
}
2276+
2277+
private void scheduleDocumentExpiration(long minimumDelay) {
2278+
if (store == null) return;
2279+
2280+
long nextExpiration = store.nextDocumentExpiry();
2281+
if (nextExpiration > 0) {
2282+
long delay = Math.max((nextExpiration - System.currentTimeMillis()) / 1000 + 1, minimumDelay);
2283+
Log.v(TAG, "Scheduling next doc expiration in %d sec", delay);
2284+
cancelPurgeTimer();
2285+
purgeTimer = new Timer();
2286+
purgeTimer.schedule(new TimerTask() {
2287+
@Override
2288+
public void run() {
2289+
if (isOpen())
2290+
purgeExpiredDocuments();
2291+
}
2292+
}, delay * 1000);
2293+
} else
2294+
Log.v(TAG, "No pending doc expirations");
2295+
}
2296+
2297+
private void purgeExpiredDocuments() {
2298+
if (store == null) return;
2299+
int nPurged = store.purgeExpiredDocuments();
2300+
Log.v(TAG, "Purged %d expired documents", nPurged);
2301+
scheduleDocumentExpiration(1);
2302+
}
2303+
2304+
private void cancelPurgeTimer() {
2305+
if (purgeTimer != null) {
2306+
purgeTimer.cancel();
2307+
purgeTimer = null;
2308+
}
2309+
}
2310+
22532311
// #pragma mark - LOOKING UP ATTACHMENTS:
22542312

22552313
/**
@@ -2302,7 +2360,7 @@ private boolean postChangeNotifications() {
23022360
// This is a 'while' instead of an 'if' because when we finish posting notifications, there
23032361
// might be new ones that have arrived as a result of notification handlers making document
23042362
// changes of their own (the replicator manager will do this.) So we need to check again.
2305-
while (!store.inTransaction() && open.get() && changesToNotify.size() > 0) {
2363+
while (store != null && !store.inTransaction() && open.get() && changesToNotify.size() > 0) {
23062364
List<DocumentChange> outgoingChanges = new ArrayList<DocumentChange>();
23072365
synchronized (changesToNotify) {
23082366
outgoingChanges.addAll(changesToNotify);

src/main/java/com/couchbase/lite/Document.java

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.URL;
2121
import java.util.ArrayList;
2222
import java.util.Collections;
23+
import java.util.Date;
2324
import java.util.HashMap;
2425
import java.util.List;
2526
import java.util.Map;
@@ -145,6 +146,20 @@ public String getId() {
145146
return documentId;
146147
}
147148

149+
/**
150+
* A date/time after which this document will be automatically purged.
151+
*/
152+
public Date getExpirationDate() {
153+
long timestamp = database.getStore().expirationOfDocument(documentId);
154+
if (timestamp == 0)
155+
return null;
156+
return new Date(timestamp);
157+
}
158+
159+
public void setExpirationDate(Date date) {
160+
database.setExpirationDate(date, documentId);
161+
}
162+
148163
////////////////////////////////////////////////////////////
149164
// Public Methods
150165
////////////////////////////////////////////////////////////
@@ -554,22 +569,29 @@ private boolean revIdGreaterThanCurrent(String revId) {
554569
}
555570

556571
/**
572+
* Notification from the CBLDatabase that a (current, winning) revision has been added
573+
*
557574
* @exclude
558575
*/
559576
@InterfaceAudience.Private
560577
protected void revisionAdded(DocumentChange change, boolean notify) {
561-
String revID = change.getWinningRevisionID();
562-
if (revID == null)
563-
return; // current revision didn't change
564-
565-
if (currentRevision != null && !revID.equals(currentRevision.getId())) {
566-
RevisionInternal rev = change.getWinningRevisionIfKnown();
567-
if (rev == null)
568-
forgetCurrentRevision();
569-
else if (rev.isDeleted())
570-
currentRevision = null;
571-
else
572-
currentRevision = new SavedRevision(this, rev);
578+
if (change.getRevisionId() != null) {
579+
String revID = change.getWinningRevisionID();
580+
if (revID == null)
581+
return; // current revision didn't change
582+
583+
if (currentRevision != null && !revID.equals(currentRevision.getId())) {
584+
RevisionInternal rev = change.getWinningRevisionIfKnown();
585+
if (rev == null)
586+
forgetCurrentRevision();
587+
else if (rev.isDeleted())
588+
currentRevision = null;
589+
else
590+
currentRevision = new SavedRevision(this, rev);
591+
}
592+
} else {
593+
// Document was purged!
594+
currentRevision = null;
573595
}
574596

575597
if (notify) {

src/main/java/com/couchbase/lite/DocumentChange.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/**
22
* Copyright (c) 2016 Couchbase, Inc. All rights reserved.
3-
*
3+
* <p/>
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
55
* except in compliance with the License. You may obtain a copy of the License at
6-
*
6+
* <p/>
77
* http://www.apache.org/licenses/LICENSE-2.0
8-
*
8+
* <p/>
99
* Unless required by applicable law or agreed to in writing, software distributed under the
1010
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
1111
* either express or implied. See the License for the specific language governing permissions
@@ -24,6 +24,7 @@
2424
*/
2525
public class DocumentChange {
2626
private RevisionInternal addedRevision;
27+
private String documentID;
2728
private String winningRevisionID;
2829
private boolean isConflict;
2930
private URL source;
@@ -37,24 +38,31 @@ public DocumentChange(RevisionInternal addedRevision,
3738
boolean isConflict,
3839
URL source) {
3940
this.addedRevision = addedRevision;
41+
this.documentID = addedRevision.getDocID();
4042
this.winningRevisionID = winningRevisionID;
4143
this.isConflict = isConflict;
4244
this.source = source;
4345
}
4446

47+
// - (instancetype) initWithPurgedDocument: (NSString*)docID in CBLDatabaseChange.m
48+
@InterfaceAudience.Private
49+
public DocumentChange(String docID) {
50+
this.documentID = docID;
51+
}
52+
4553
@InterfaceAudience.Public
4654
public String getDocumentId() {
47-
return addedRevision.getDocID();
55+
return documentID;
4856
}
4957

5058
@InterfaceAudience.Public
5159
public String getRevisionId() {
52-
return addedRevision.getRevID();
60+
return addedRevision != null ? addedRevision.getRevID() : null;
5361
}
5462

5563
@InterfaceAudience.Public
5664
public boolean isCurrentRevision() {
57-
return winningRevisionID != null && addedRevision.getRevID().equals(winningRevisionID);
65+
return winningRevisionID != null && addedRevision != null && addedRevision.getRevID().equals(winningRevisionID);
5866
}
5967

6068
@InterfaceAudience.Public
@@ -97,6 +105,7 @@ public String getWinningRevisionID() {
97105
}
98106

99107
protected void reduceMemoryUsage() {
100-
addedRevision = addedRevision.copyWithoutBody();
108+
if (addedRevision != null)
109+
addedRevision = addedRevision.copyWithoutBody();
101110
}
102111
}

src/main/java/com/couchbase/lite/replicator/PusherInternal.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,8 @@ private void submitRevisions(final List<DocumentChange> changes) {
657657
if (source != null && source.toURI().equals(remoteUri))
658658
return;
659659
RevisionInternal rev = change.getAddedRevision();
660+
if (rev == null)
661+
continue;
660662
if (getLocalDatabase().runFilter(filter, filterParams, rev)) {
661663
if (doneBeginReplicating) {
662664
// if not running state anymore, exit from loop.

src/main/java/com/couchbase/lite/router/Router.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,8 @@ public void changed(Database.ChangeEvent event) {
16051605
List<DocumentChange> changes = event.getChanges();
16061606
for (DocumentChange change : changes) {
16071607
RevisionInternal rev = change.getAddedRevision();
1608+
if (rev == null)
1609+
continue;
16081610
String winningRevID = change.getWinningRevisionID();
16091611
if (!this.changesIncludesConflicts) {
16101612
if (winningRevID == null)

0 commit comments

Comments
 (0)