Skip to content

Commit b9c449d

Browse files
authored
Merge pull request #87 from Parsely/extract_queuemanager_local_storage
2 parents fcb6d7c + 1b8ffca commit b9c449d

File tree

6 files changed

+360
-110
lines changed

6 files changed

+360
-110
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.parsely.parselyandroid
2+
3+
import android.content.Context
4+
import java.io.EOFException
5+
import java.io.FileNotFoundException
6+
import java.io.ObjectInputStream
7+
import java.io.ObjectOutputStream
8+
9+
internal open class LocalStorageRepository(private val context: Context) {
10+
/**
11+
* Persist an object to storage.
12+
*
13+
* @param o Object to store.
14+
*/
15+
private fun persistObject(o: Any) {
16+
try {
17+
val fos = context.applicationContext.openFileOutput(
18+
STORAGE_KEY,
19+
Context.MODE_PRIVATE
20+
)
21+
val oos = ObjectOutputStream(fos)
22+
oos.writeObject(o)
23+
oos.close()
24+
} catch (ex: Exception) {
25+
ParselyTracker.PLog("Exception thrown during queue serialization: %s", ex.toString())
26+
}
27+
}
28+
29+
/**
30+
* Delete the stored queue from persistent storage.
31+
*/
32+
fun purgeStoredQueue() {
33+
persistObject(ArrayList<Map<String, Any>>())
34+
}
35+
36+
/**
37+
* Get the stored event queue from persistent storage.
38+
*
39+
* @return The stored queue of events.
40+
*/
41+
open fun getStoredQueue(): ArrayList<Map<String, Any?>?> {
42+
var storedQueue: ArrayList<Map<String, Any?>?> = ArrayList()
43+
try {
44+
val fis = context.applicationContext.openFileInput(STORAGE_KEY)
45+
val ois = ObjectInputStream(fis)
46+
@Suppress("UNCHECKED_CAST")
47+
storedQueue = ois.readObject() as ArrayList<Map<String, Any?>?>
48+
ois.close()
49+
} catch (ex: EOFException) {
50+
// Nothing to do here.
51+
} catch (ex: FileNotFoundException) {
52+
// Nothing to do here. Means there was no saved queue.
53+
} catch (ex: Exception) {
54+
ParselyTracker.PLog(
55+
"Exception thrown during queue deserialization: %s",
56+
ex.toString()
57+
)
58+
}
59+
return storedQueue
60+
}
61+
62+
/**
63+
* Delete an event from the stored queue.
64+
*/
65+
open fun expelStoredEvent() {
66+
val storedQueue = getStoredQueue()
67+
storedQueue.removeAt(0)
68+
}
69+
70+
/**
71+
* Save the event queue to persistent storage.
72+
*/
73+
@Synchronized
74+
open fun persistQueue(inMemoryQueue: List<Map<String, Any?>?>) {
75+
ParselyTracker.PLog("Persisting event queue")
76+
persistObject((inMemoryQueue + getStoredQueue()).distinct())
77+
}
78+
79+
companion object {
80+
private const val STORAGE_KEY = "parsely-events.ser"
81+
}
82+
}

parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java

Lines changed: 17 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,13 @@
2929

3030
import com.fasterxml.jackson.databind.ObjectMapper;
3131

32-
import java.io.EOFException;
33-
import java.io.FileInputStream;
34-
import java.io.FileNotFoundException;
35-
import java.io.FileOutputStream;
3632
import java.io.IOException;
37-
import java.io.ObjectInputStream;
38-
import java.io.ObjectOutputStream;
3933
import java.io.StringWriter;
4034
import java.util.ArrayList;
4135
import java.util.Formatter;
4236
import java.util.HashMap;
4337
import java.util.HashSet;
38+
import java.util.List;
4439
import java.util.Map;
4540
import java.util.Timer;
4641
import java.util.TimerTask;
@@ -56,13 +51,10 @@ public class ParselyTracker {
5651
private static ParselyTracker instance = null;
5752
private static final int DEFAULT_FLUSH_INTERVAL_SECS = 60;
5853
private static final int DEFAULT_ENGAGEMENT_INTERVAL_MILLIS = 10500;
59-
private static final int QUEUE_SIZE_LIMIT = 50;
60-
private static final int STORAGE_SIZE_LIMIT = 100;
61-
private static final String STORAGE_KEY = "parsely-events.ser";
6254
@SuppressWarnings("StringOperationCanBeSimplified")
6355
// private static final String ROOT_URL = "http://10.0.2.2:5001/".intern(); // emulator localhost
6456
private static final String ROOT_URL = "https://p1.parsely.com/".intern();
65-
protected ArrayList<Map<String, Object>> eventQueue;
57+
private final ArrayList<Map<String, Object>> eventQueue;
6658
private boolean isDebug;
6759
private final Context context;
6860
private final Timer timer;
@@ -72,14 +64,18 @@ public class ParselyTracker {
7264
private String lastPageviewUuid = null;
7365
@NonNull
7466
private final EventsBuilder eventsBuilder;
75-
@NonNull final HeartbeatIntervalCalculator intervalCalculator = new HeartbeatIntervalCalculator(new Clock());
67+
@NonNull
68+
private final HeartbeatIntervalCalculator intervalCalculator = new HeartbeatIntervalCalculator(new Clock());
69+
@NonNull
70+
private final LocalStorageRepository localStorageRepository;
7671

7772
/**
7873
* Create a new ParselyTracker instance.
7974
*/
8075
protected ParselyTracker(String siteId, int flushInterval, Context c) {
8176
context = c.getApplicationContext();
8277
eventsBuilder = new EventsBuilder(context, siteId);
78+
localStorageRepository = new LocalStorageRepository(context);
8379

8480
// get the adkey straight away on instantiation
8581
timer = new Timer();
@@ -89,7 +85,7 @@ protected ParselyTracker(String siteId, int flushInterval, Context c) {
8985

9086
flushManager = new FlushManager(timer, flushInterval * 1000L);
9187

92-
if (getStoredQueue().size() > 0) {
88+
if (localStorageRepository.getStoredQueue().size() > 0) {
9389
startFlushTimer();
9490
}
9591

@@ -102,6 +98,10 @@ protected ParselyTracker(String siteId, int flushInterval, Context c) {
10298
);
10399
}
104100

101+
List<Map<String, Object>> getInMemoryQueue() {
102+
return eventQueue;
103+
}
104+
105105
/**
106106
* Singleton instance accessor. Note: This must be called after {@link #sharedInstance(String, Context)}
107107
*
@@ -411,14 +411,14 @@ public void resetVideo() {
411411
* Place a data structure representing the event into the in-memory queue for later use.
412412
* <p>
413413
* **Note**: Events placed into this queue will be discarded if the size of the persistent queue
414-
* store exceeds {@link #STORAGE_SIZE_LIMIT}.
414+
* store exceeds {@link QueueManager#STORAGE_SIZE_LIMIT}.
415415
*
416416
* @param event The event Map to enqueue.
417417
*/
418418
void enqueueEvent(Map<String, Object> event) {
419419
// Push it onto the queue
420420
eventQueue.add(event);
421-
new QueueManager().execute();
421+
new QueueManager(this, localStorageRepository).execute();
422422
if (!flushTimerIsActive()) {
423423
startFlushTimer();
424424
PLog("Flush flushTimer set to %ds", (flushManager.getIntervalMillis() / 1000));
@@ -474,85 +474,9 @@ private boolean isReachable() {
474474
return netInfo != null && netInfo.isConnectedOrConnecting();
475475
}
476476

477-
/**
478-
* Save the event queue to persistent storage.
479-
*/
480-
private synchronized void persistQueue() {
481-
PLog("Persisting event queue");
482-
ArrayList<Map<String, Object>> storedQueue = getStoredQueue();
483-
HashSet<Map<String, Object>> hs = new HashSet<>();
484-
hs.addAll(storedQueue);
485-
hs.addAll(eventQueue);
486-
storedQueue.clear();
487-
storedQueue.addAll(hs);
488-
persistObject(storedQueue);
489-
}
490-
491-
/**
492-
* Get the stored event queue from persistent storage.
493-
*
494-
* @return The stored queue of events.
495-
*/
496-
@NonNull
497-
private ArrayList<Map<String, Object>> getStoredQueue() {
498-
ArrayList<Map<String, Object>> storedQueue = null;
499-
try {
500-
FileInputStream fis = context.getApplicationContext().openFileInput(STORAGE_KEY);
501-
ObjectInputStream ois = new ObjectInputStream(fis);
502-
//noinspection unchecked
503-
storedQueue = (ArrayList<Map<String, Object>>) ois.readObject();
504-
ois.close();
505-
} catch (EOFException ex) {
506-
// Nothing to do here.
507-
} catch (FileNotFoundException ex) {
508-
// Nothing to do here. Means there was no saved queue.
509-
} catch (Exception ex) {
510-
PLog("Exception thrown during queue deserialization: %s", ex.toString());
511-
}
512-
513-
if (storedQueue == null) {
514-
storedQueue = new ArrayList<>();
515-
}
516-
return storedQueue;
517-
}
518-
519477
void purgeEventsQueue() {
520478
eventQueue.clear();
521-
purgeStoredQueue();
522-
}
523-
524-
/**
525-
* Delete the stored queue from persistent storage.
526-
*/
527-
private void purgeStoredQueue() {
528-
persistObject(new ArrayList<Map<String, Object>>());
529-
}
530-
531-
/**
532-
* Delete an event from the stored queue.
533-
*/
534-
private void expelStoredEvent() {
535-
ArrayList<Map<String, Object>> storedQueue = getStoredQueue();
536-
storedQueue.remove(0);
537-
}
538-
539-
/**
540-
* Persist an object to storage.
541-
*
542-
* @param o Object to store.
543-
*/
544-
private void persistObject(Object o) {
545-
try {
546-
FileOutputStream fos = context.getApplicationContext().openFileOutput(
547-
STORAGE_KEY,
548-
android.content.Context.MODE_PRIVATE
549-
);
550-
ObjectOutputStream oos = new ObjectOutputStream(fos);
551-
oos.writeObject(o);
552-
oos.close();
553-
} catch (Exception ex) {
554-
PLog("Exception thrown during queue serialization: %s", ex.toString());
555-
}
479+
localStorageRepository.purgeStoredQueue();
556480
}
557481

558482
/**
@@ -621,31 +545,14 @@ public int queueSize() {
621545
* @return The number of events stored in persistent storage.
622546
*/
623547
public int storedEventsCount() {
624-
ArrayList<Map<String, Object>> ar = getStoredQueue();
548+
ArrayList<Map<String, Object>> ar = localStorageRepository.getStoredQueue();
625549
return ar.size();
626550
}
627551

628-
private class QueueManager extends AsyncTask<Void, Void, Void> {
629-
@Override
630-
protected Void doInBackground(Void... params) {
631-
// if event queue is too big, push to persisted storage
632-
if (eventQueue.size() > QUEUE_SIZE_LIMIT) {
633-
PLog("Queue size exceeded, expelling oldest event to persistent memory");
634-
persistQueue();
635-
eventQueue.remove(0);
636-
// if persisted storage is too big, expel one
637-
if (storedEventsCount() > STORAGE_SIZE_LIMIT) {
638-
expelStoredEvent();
639-
}
640-
}
641-
return null;
642-
}
643-
}
644-
645552
private class FlushQueue extends AsyncTask<Void, Void, Void> {
646553
@Override
647554
protected synchronized Void doInBackground(Void... params) {
648-
ArrayList<Map<String, Object>> storedQueue = getStoredQueue();
555+
ArrayList<Map<String, Object>> storedQueue = localStorageRepository.getStoredQueue();
649556
PLog("%d events in queue, %d stored events", eventQueue.size(), storedEventsCount());
650557
// in case both queues have been flushed and app quits, don't crash
651558
if ((eventQueue == null || eventQueue.size() == 0) && storedQueue.size() == 0) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.parsely.parselyandroid
2+
3+
import android.os.AsyncTask
4+
5+
@Suppress("DEPRECATION")
6+
internal class QueueManager(
7+
private val parselyTracker: ParselyTracker,
8+
private val localStorageRepository: LocalStorageRepository
9+
) : AsyncTask<Void?, Void?, Void?>() {
10+
11+
@Deprecated("Deprecated in Java")
12+
override fun doInBackground(vararg params: Void?): Void? {
13+
// if event queue is too big, push to persisted storage
14+
if (parselyTracker.inMemoryQueue.size > QUEUE_SIZE_LIMIT) {
15+
ParselyTracker.PLog("Queue size exceeded, expelling oldest event to persistent memory")
16+
localStorageRepository.persistQueue(parselyTracker.inMemoryQueue)
17+
parselyTracker.inMemoryQueue.removeAt(0)
18+
// if persisted storage is too big, expel one
19+
if (parselyTracker.storedEventsCount() > STORAGE_SIZE_LIMIT) {
20+
localStorageRepository.expelStoredEvent()
21+
}
22+
}
23+
return null
24+
}
25+
26+
companion object {
27+
const val QUEUE_SIZE_LIMIT = 50
28+
const val STORAGE_SIZE_LIMIT = 100
29+
}
30+
}

0 commit comments

Comments
 (0)