DropBoxManagerService: Don't store redundant information
Filenames can be calculated from other fields, so there's no need to
store them.
This will reduce the average EntryFile size from 160b to 28b, so in theory
it could save 132 KB for 1000 entries.
- Also switched from HashMap to ArrayMap, which should help a bit too.
- Also fixed unit tests and added more.
Bug: 20890386
Test: bit FrameworksServicesTests:com.android.server.DropBoxTest
Test: Boot, check the dropbox directory, run dumpsys dropbox, reboot and repeat.
Change-Id: If567750f478318acd621864d1d4ef2ed41f214bd
diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java
index d109a5a..59e5a64 100644
--- a/core/java/com/android/internal/util/ObjectUtils.java
+++ b/core/java/com/android/internal/util/ObjectUtils.java
@@ -28,4 +28,12 @@
public static <T> T firstNotNull(@Nullable T a, @NonNull T b) {
return a != null ? a : Preconditions.checkNotNull(b);
}
+
+ public static <T extends Comparable> int compare(@Nullable T a, @Nullable T b) {
+ if (a != null) {
+ return (b != null) ? a.compareTo(b) : 1;
+ } else {
+ return (b != null) ? -1 : 0;
+ }
+ }
}
diff --git a/core/tests/utiltests/src/com/android/internal/util/ObjectUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ObjectUtilsTest.java
new file mode 100644
index 0000000..443183e
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/ObjectUtilsTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.internal.util;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ObjectUtilsTest extends AndroidTestCase {
+ public void testCompare() {
+ assertEquals(0, ObjectUtils.compare(null, null));
+ assertEquals(1, ObjectUtils.compare("a", null));
+ assertEquals(-1, ObjectUtils.compare(null, "a"));
+
+ assertEquals(0, ObjectUtils.compare("a", "a"));
+
+ assertEquals(-1, ObjectUtils.compare("a", "b"));
+ assertEquals(1, ObjectUtils.compare("b", "a"));
+ }
+}
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index e1756d1..1bf12c4 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -29,18 +29,23 @@
import android.os.DropBoxManager;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.StatFs;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.text.format.Time;
+import android.util.ArrayMap;
import android.util.Slog;
import libcore.io.IoUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.ObjectUtils;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -52,7 +57,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.zip.GZIPOutputStream;
@@ -87,7 +92,7 @@
// Accounting of all currently written log files (set in init()).
private FileList mAllFiles = null;
- private HashMap<String, FileList> mFilesByTag = null;
+ private ArrayMap<String, FileList> mFilesByTag = null;
// Various bits of disk information
@@ -153,7 +158,7 @@
* @param context to use for receiving free space & gservices intents
*/
public DropBoxManagerService(final Context context) {
- this(context, new File("/data/system/dropbox"));
+ this(context, new File("/data/system/dropbox"), FgThread.get().getLooper());
}
/**
@@ -163,11 +168,12 @@
* @param context to use for receiving free space & gservices intents
* @param path to store drop box entries in
*/
- public DropBoxManagerService(final Context context, File path) {
+ @VisibleForTesting
+ public DropBoxManagerService(final Context context, File path, Looper looper) {
super(context);
mDropBoxDir = path;
mContentResolver = getContext().getContentResolver();
- mHandler = new Handler() {
+ mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_SEND_BROADCAST) {
@@ -338,11 +344,12 @@
if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) {
return new DropBoxManager.Entry(entry.tag, entry.timestampMillis);
}
+ final File file = entry.getFile(mDropBoxDir);
try {
return new DropBoxManager.Entry(
- entry.tag, entry.timestampMillis, entry.file, entry.flags);
+ entry.tag, entry.timestampMillis, file, entry.flags);
} catch (IOException e) {
- Slog.e(TAG, "Can't read: " + entry.file, e);
+ Slog.wtf(TAG, "Can't read: " + file, e);
// Continue to next file
}
}
@@ -410,7 +417,9 @@
numFound++;
if (doPrint) out.append("========================================\n");
out.append(date).append(" ").append(entry.tag == null ? "(no tag)" : entry.tag);
- if (entry.file == null) {
+
+ final File file = entry.getFile(mDropBoxDir);
+ if (file == null) {
out.append(" (no file)\n");
continue;
} else if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) {
@@ -420,12 +429,12 @@
out.append(" (");
if ((entry.flags & DropBoxManager.IS_GZIPPED) != 0) out.append("compressed ");
out.append((entry.flags & DropBoxManager.IS_TEXT) != 0 ? "text" : "data");
- out.append(", ").append(entry.file.length()).append(" bytes)\n");
+ out.append(", ").append(file.length()).append(" bytes)\n");
}
if (doFile || (doPrint && (entry.flags & DropBoxManager.IS_TEXT) == 0)) {
if (!doPrint) out.append(" ");
- out.append(entry.file.getPath()).append("\n");
+ out.append(file.getPath()).append("\n");
}
if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) {
@@ -433,7 +442,7 @@
InputStreamReader isr = null;
try {
dbe = new DropBoxManager.Entry(
- entry.tag, entry.timestampMillis, entry.file, entry.flags);
+ entry.tag, entry.timestampMillis, file, entry.flags);
if (doPrint) {
isr = new InputStreamReader(dbe.getInputStream());
@@ -466,7 +475,7 @@
}
} catch (IOException e) {
out.append("*** ").append(e.toString()).append("\n");
- Slog.e(TAG, "Can't read: " + entry.file, e);
+ Slog.e(TAG, "Can't read: " + file, e);
} finally {
if (dbe != null) dbe.close();
if (isr != null) {
@@ -509,29 +518,37 @@
}
}
- /** Metadata describing an on-disk log file. */
- private static final class EntryFile implements Comparable<EntryFile> {
+ /**
+ * Metadata describing an on-disk log file.
+ *
+ * Note its instances do no have knowledge on what directory they're stored, just to save
+ * 4/8 bytes per instance. Instead, {@link #getFile} takes a directory so it can build a
+ * fullpath.
+ */
+ @VisibleForTesting
+ static final class EntryFile implements Comparable<EntryFile> {
public final String tag;
public final long timestampMillis;
public final int flags;
- public final File file;
public final int blocks;
/** Sorts earlier EntryFile instances before later ones. */
public final int compareTo(EntryFile o) {
- if (timestampMillis < o.timestampMillis) return -1;
- if (timestampMillis > o.timestampMillis) return 1;
- if (file != null && o.file != null) return file.compareTo(o.file);
- if (o.file != null) return -1;
- if (file != null) return 1;
- if (this == o) return 0;
- if (hashCode() < o.hashCode()) return -1;
- if (hashCode() > o.hashCode()) return 1;
- return 0;
+ int comp = Long.compare(timestampMillis, o.timestampMillis);
+ if (comp != 0) return comp;
+
+ comp = ObjectUtils.compare(tag, o.tag);
+ if (comp != 0) return comp;
+
+ comp = Integer.compare(flags, o.flags);
+ if (comp != 0) return comp;
+
+ return Integer.compare(hashCode(), o.hashCode());
}
/**
* Moves an existing temporary file to a new log filename.
+ *
* @param temp file to rename
* @param dir to store file in
* @param tag to use for new log file name
@@ -544,76 +561,94 @@
int flags, int blockSize) throws IOException {
if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();
- this.tag = tag;
+ this.tag = TextUtils.safeIntern(tag);
this.timestampMillis = timestampMillis;
this.flags = flags;
- this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis +
- ((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") +
- ((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : ""));
- if (!temp.renameTo(this.file)) {
- throw new IOException("Can't rename " + temp + " to " + this.file);
+ final File file = this.getFile(dir);
+ if (!temp.renameTo(file)) {
+ throw new IOException("Can't rename " + temp + " to " + file);
}
- this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
+ this.blocks = (int) ((file.length() + blockSize - 1) / blockSize);
}
/**
* Creates a zero-length tombstone for a file whose contents were lost.
+ *
* @param dir to store file in
* @param tag to use for new log file name
* @param timestampMillis of log entry
* @throws IOException if the file can't be created.
*/
public EntryFile(File dir, String tag, long timestampMillis) throws IOException {
- this.tag = tag;
+ this.tag = TextUtils.safeIntern(tag);
this.timestampMillis = timestampMillis;
this.flags = DropBoxManager.IS_EMPTY;
- this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ".lost");
this.blocks = 0;
- new FileOutputStream(this.file).close();
+ new FileOutputStream(getFile(dir)).close();
}
/**
* Extracts metadata from an existing on-disk log filename.
+ *
+ * Note when a filename is not recognizable, it will create an instance that
+ * {@link #hasFile()} would return false on, and also remove the file.
+ *
* @param file name of existing log file
* @param blockSize to use for space accounting
*/
public EntryFile(File file, int blockSize) {
- this.file = file;
- this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
+
+ boolean parseFailure = false;
String name = file.getName();
- int at = name.lastIndexOf('@');
- if (at < 0) {
- this.tag = null;
- this.timestampMillis = 0;
- this.flags = DropBoxManager.IS_EMPTY;
- return;
- }
-
int flags = 0;
- this.tag = Uri.decode(name.substring(0, at));
- if (name.endsWith(".gz")) {
- flags |= DropBoxManager.IS_GZIPPED;
- name = name.substring(0, name.length() - 3);
- }
- if (name.endsWith(".lost")) {
- flags |= DropBoxManager.IS_EMPTY;
- name = name.substring(at + 1, name.length() - 5);
- } else if (name.endsWith(".txt")) {
- flags |= DropBoxManager.IS_TEXT;
- name = name.substring(at + 1, name.length() - 4);
- } else if (name.endsWith(".dat")) {
- name = name.substring(at + 1, name.length() - 4);
+ String tag = null;
+ long millis = 0;
+
+ final int at = name.lastIndexOf('@');
+ if (at < 0) {
+ parseFailure = true;
} else {
+ tag = Uri.decode(name.substring(0, at));
+ if (name.endsWith(".gz")) {
+ flags |= DropBoxManager.IS_GZIPPED;
+ name = name.substring(0, name.length() - 3);
+ }
+ if (name.endsWith(".lost")) {
+ flags |= DropBoxManager.IS_EMPTY;
+ name = name.substring(at + 1, name.length() - 5);
+ } else if (name.endsWith(".txt")) {
+ flags |= DropBoxManager.IS_TEXT;
+ name = name.substring(at + 1, name.length() - 4);
+ } else if (name.endsWith(".dat")) {
+ name = name.substring(at + 1, name.length() - 4);
+ } else {
+ parseFailure = true;
+ }
+ if (!parseFailure) {
+ try {
+ millis = Long.parseLong(name);
+ } catch (NumberFormatException e) {
+ parseFailure = true;
+ }
+ }
+ }
+ if (parseFailure) {
+ Slog.wtf(TAG, "Invalid filename: " + file);
+
+ // Remove the file and return an empty instance.
+ file.delete();
+ this.tag = null;
this.flags = DropBoxManager.IS_EMPTY;
this.timestampMillis = 0;
+ this.blocks = 0;
return;
}
- this.flags = flags;
- long millis;
- try { millis = Long.parseLong(name); } catch (NumberFormatException e) { millis = 0; }
+ this.blocks = (int) ((file.length() + blockSize - 1) / blockSize);
+ this.tag = TextUtils.safeIntern(tag);
+ this.flags = flags;
this.timestampMillis = millis;
}
@@ -625,9 +660,50 @@
this.tag = null;
this.timestampMillis = millis;
this.flags = DropBoxManager.IS_EMPTY;
- this.file = null;
this.blocks = 0;
}
+
+ /**
+ * @return whether an entry actually has a backing file, or it's an empty "tombstone"
+ * entry.
+ */
+ public boolean hasFile() {
+ return tag != null;
+ }
+
+ /** @return File extension for the flags. */
+ private String getExtension() {
+ if ((flags & DropBoxManager.IS_EMPTY) != 0) {
+ return ".lost";
+ }
+ return ((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") +
+ ((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : "");
+ }
+
+ /**
+ * @return filename for this entry without the pathname.
+ */
+ public String getFilename() {
+ return hasFile() ? Uri.encode(tag) + "@" + timestampMillis + getExtension() : null;
+ }
+
+ /**
+ * Get a full-path {@link File} representing this entry.
+ * @param dir Parent directly. The caller needs to pass it because {@link EntryFile}s don't
+ * know in which directory they're stored.
+ */
+ public File getFile(File dir) {
+ return hasFile() ? new File(dir, getFilename()) : null;
+ }
+
+ /**
+ * If an entry has a backing file, remove it.
+ */
+ public void deleteFile(File dir) {
+ if (hasFile()) {
+ getFile(dir).delete();
+ }
+ }
}
///////////////////////////////////////////////////////////////////////////
@@ -651,7 +727,7 @@
if (files == null) throw new IOException("Can't list files: " + mDropBoxDir);
mAllFiles = new FileList();
- mFilesByTag = new HashMap<String, FileList>();
+ mFilesByTag = new ArrayMap<>();
// Scan pre-existing files.
for (File file : files) {
@@ -662,16 +738,12 @@
}
EntryFile entry = new EntryFile(file, mBlockSize);
- if (entry.tag == null) {
- Slog.w(TAG, "Unrecognized file: " + file);
- continue;
- } else if (entry.timestampMillis == 0) {
- Slog.w(TAG, "Invalid filename: " + file);
- file.delete();
- continue;
- }
- enrollEntry(entry);
+ if (entry.hasFile()) {
+ // Enroll only when the filename is valid. Otherwise the above constructor
+ // has removed the file already.
+ enrollEntry(entry);
+ }
}
}
}
@@ -684,11 +756,11 @@
// mFilesByTag is used for trimming, so don't list empty files.
// (Zero-length/lost files are trimmed by date from mAllFiles.)
- if (entry.tag != null && entry.file != null && entry.blocks > 0) {
+ if (entry.hasFile() && entry.blocks > 0) {
FileList tagFiles = mFilesByTag.get(entry.tag);
if (tagFiles == null) {
tagFiles = new FileList();
- mFilesByTag.put(entry.tag, tagFiles);
+ mFilesByTag.put(TextUtils.safeIntern(entry.tag), tagFiles);
}
tagFiles.contents.add(entry);
tagFiles.blocks += entry.blocks;
@@ -722,8 +794,8 @@
tagFiles.blocks -= late.blocks;
}
if ((late.flags & DropBoxManager.IS_EMPTY) == 0) {
- enrollEntry(new EntryFile(
- late.file, mDropBoxDir, late.tag, t++, late.flags, mBlockSize));
+ enrollEntry(new EntryFile(late.getFile(mDropBoxDir), mDropBoxDir,
+ late.tag, t++, late.flags, mBlockSize));
} else {
enrollEntry(new EntryFile(mDropBoxDir, late.tag, t++));
}
@@ -757,7 +829,7 @@
FileList tag = mFilesByTag.get(entry.tag);
if (tag != null && tag.contents.remove(entry)) tag.blocks -= entry.blocks;
if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
- if (entry.file != null) entry.file.delete();
+ entry.deleteFile(mDropBoxDir);
}
// Compute overall quota (a fraction of available free space) in blocks.
@@ -823,7 +895,7 @@
if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
try {
- if (entry.file != null) entry.file.delete();
+ entry.deleteFile(mDropBoxDir);
enrollEntry(new EntryFile(mDropBoxDir, entry.tag, entry.timestampMillis));
} catch (IOException e) {
Slog.e(TAG, "Can't write tombstone file", e);
diff --git a/services/tests/servicestests/src/com/android/server/DropBoxTest.java b/services/tests/servicestests/src/com/android/server/DropBoxTest.java
index 7f28d44..56773e8 100644
--- a/services/tests/servicestests/src/com/android/server/DropBoxTest.java
+++ b/services/tests/servicestests/src/com/android/server/DropBoxTest.java
@@ -18,18 +18,20 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
import android.os.DropBoxManager;
+import android.os.Looper;
import android.os.Parcel;
-import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.os.Process;
-import android.os.ServiceManager;
import android.os.StatFs;
+import android.os.UserHandle;
import android.provider.Settings;
import android.test.AndroidTestCase;
-import com.android.server.DropBoxManagerService;
+import com.android.server.DropBoxManagerService.EntryFile;
import java.io.BufferedReader;
import java.io.File;
@@ -41,8 +43,28 @@
import java.util.Random;
import java.util.zip.GZIPOutputStream;
-/** Test {@link DropBoxManager} functionality. */
+/**
+ * Test {@link DropBoxManager} functionality.
+ *
+ * Run with:
+ * bit FrameworksServicesTests:com.android.server.DropBoxTest
+ */
public class DropBoxTest extends AndroidTestCase {
+ private Context mContext;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mContext = new ContextWrapper(super.getContext()) {
+ @Override
+ public void sendBroadcastAsUser(Intent intent,
+ UserHandle user, String receiverPermission) {
+ // Don't actually send broadcasts.
+ }
+ };
+ }
+
public void tearDown() throws Exception {
ContentResolver cr = getContext().getContentResolver();
Settings.Global.putString(cr, Settings.Global.DROPBOX_AGE_SECONDS, "");
@@ -51,9 +73,15 @@
Settings.Global.putString(cr, Settings.Global.DROPBOX_TAG_PREFIX + "DropBoxTest", "");
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
public void testAddText() throws Exception {
File dir = getEmptyDir("testAddText");
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
long before = System.currentTimeMillis();
@@ -89,15 +117,19 @@
public void testAddData() throws Exception {
File dir = getEmptyDir("testAddData");
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
long before = System.currentTimeMillis();
+ Thread.sleep(1);
dropbox.addData("DropBoxTest", "TEST".getBytes(), 0);
+ Thread.sleep(1);
long after = System.currentTimeMillis();
DropBoxManager.Entry e = dropbox.getNextEntry("DropBoxTest", before);
- assertTrue(null == dropbox.getNextEntry("DropBoxTest", e.getTimeMillis()));
+ assertNotNull(e);
+ assertNull(dropbox.getNextEntry("DropBoxTest", e.getTimeMillis()));
assertEquals("DropBoxTest", e.getTag());
assertTrue(e.getTimeMillis() >= before);
@@ -114,10 +146,12 @@
File dir = getEmptyDir("testAddFile");
long before = System.currentTimeMillis();
- File f0 = new File(dir, "f0.txt");
- File f1 = new File(dir, "f1.txt.gz");
- File f2 = new File(dir, "f2.dat");
- File f3 = new File(dir, "f2.dat.gz");
+ File clientDir = getEmptyDir("testAddFile_client");
+
+ File f0 = new File(clientDir, "f0.txt");
+ File f1 = new File(clientDir, "f1.txt.gz");
+ File f2 = new File(clientDir, "f2.dat");
+ File f3 = new File(clientDir, "f2.dat.gz");
FileWriter w0 = new FileWriter(f0);
GZIPOutputStream gz1 = new GZIPOutputStream(new FileOutputStream(f1));
@@ -134,7 +168,8 @@
os2.close();
gz3.close();
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
dropbox.addFile("DropBoxTest", f0, DropBoxManager.IS_TEXT);
@@ -200,7 +235,8 @@
// Tombstone in the far future
new FileOutputStream(new File(dir, "DropBoxTest@" + (before + 100002) + ".lost")).close();
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
// Until a write, the timestamps are taken at face value
@@ -251,7 +287,8 @@
public void testIsTagEnabled() throws Exception {
File dir = getEmptyDir("testIsTagEnabled");
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
long before = System.currentTimeMillis();
@@ -284,7 +321,8 @@
public void testGetNextEntry() throws Exception {
File dir = getEmptyDir("testGetNextEntry");
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
long before = System.currentTimeMillis();
@@ -346,7 +384,8 @@
final int overhead = 64;
long before = System.currentTimeMillis();
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
addRandomEntry(dropbox, "DropBoxTest0", blockSize - overhead);
@@ -440,7 +479,8 @@
// Write one normal entry and another so big that it is instantly tombstoned
long before = System.currentTimeMillis();
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
dropbox.addText("DropBoxTest", "TEST");
@@ -471,7 +511,8 @@
public void testFileCountLimits() throws Exception {
File dir = getEmptyDir("testFileCountLimits");
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
dropbox.addText("DropBoxTest", "TEST0");
dropbox.addText("DropBoxTest", "TEST1");
@@ -524,7 +565,8 @@
File dir = new File(getEmptyDir("testCreateDropBoxManagerWith"), "InvalidDirectory");
new FileOutputStream(dir).close(); // Create an empty file
- DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+ DropBoxManagerService service = new DropBoxManagerService(getContext(), dir,
+ Looper.getMainLooper());
DropBoxManager dropbox = new DropBoxManager(getContext(), service.getServiceStub());
dropbox.addText("DropBoxTest", "should be ignored");
@@ -735,6 +777,223 @@
assertTrue(after < before + 20);
}
+ public void testEntryFile() throws Exception {
+ File fromDir = getEmptyDir("testEntryFile_from");
+ File toDir = getEmptyDir("testEntryFile_to");
+
+ {
+ File f = new File(fromDir, "f0.txt");
+ try (FileWriter w = new FileWriter(f)) {
+ w.write("abc");
+ }
+
+ EntryFile e = new EntryFile(f, toDir, "tag:!", 12345, DropBoxManager.IS_TEXT, 1024);
+
+ assertEquals("tag:!", e.tag);
+ assertEquals(12345, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_TEXT, e.flags);
+ assertEquals(1, e.blocks);
+
+ assertFalse(f.exists()); // Because it should be renamed.
+
+ assertTrue(e.hasFile());
+ assertEquals(new File(toDir, "tag%3A!@12345.txt"), e.getFile(toDir));
+ assertTrue(e.getFile(toDir).exists());
+ }
+ // Same test with gzip.
+ {
+ File f = new File(fromDir, "f0.txt.gz"); // It's a lie; it's not actually gz.
+ try (FileWriter w = new FileWriter(f)) {
+ w.write("abc");
+ }
+
+ EntryFile e = new EntryFile(f, toDir, "tag:!", 12345,
+ DropBoxManager.IS_TEXT | DropBoxManager.IS_GZIPPED, 1024);
+
+ assertEquals("tag:!", e.tag);
+
+ assertFalse(f.exists()); // Because it should be renamed.
+
+ assertTrue(e.hasFile());
+ assertEquals(new File(toDir, "tag%3A!@12345.txt.gz"), e.getFile(toDir));
+ assertTrue(e.getFile(toDir).exists());
+
+ }
+ // binary, gzip.
+ {
+ File f = new File(fromDir, "f0.dat.gz"); // It's a lie; it's not actually gz.
+ try (FileWriter w = new FileWriter(f)) {
+ w.write("abc");
+ }
+
+ EntryFile e = new EntryFile(f, toDir, "tag:!", 12345,
+ DropBoxManager.IS_GZIPPED, 1024);
+
+ assertEquals("tag:!", e.tag);
+
+ assertFalse(f.exists()); // Because it should be renamed.
+
+ assertTrue(e.hasFile());
+ assertEquals(new File(toDir, "tag%3A!@12345.dat.gz"), e.getFile(toDir));
+ assertTrue(e.getFile(toDir).exists());
+
+ }
+
+ // Tombstone.
+ {
+ EntryFile e = new EntryFile(toDir, "tag:!", 12345);
+
+ assertEquals("tag:!", e.tag);
+ assertEquals(12345, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_EMPTY, e.flags);
+ assertEquals(0, e.blocks);
+
+ assertTrue(e.hasFile());
+ assertEquals(new File(toDir, "tag%3A!@12345.lost"), e.getFile(toDir));
+ assertTrue(e.getFile(toDir).exists());
+ }
+
+ // From existing files.
+ {
+ File f = new File(fromDir, "tag%3A!@12345.dat");
+ f.createNewFile();
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals("tag:!", e.tag);
+ assertEquals(12345, e.timestampMillis);
+ assertEquals(0, e.flags);
+ assertEquals(0, e.blocks);
+
+ assertTrue(f.exists());
+ }
+ {
+ File f = new File(fromDir, "tag%3A!@12345.dat.gz");
+ f.createNewFile();
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals("tag:!", e.tag);
+ assertEquals(12345, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_GZIPPED, e.flags);
+ assertEquals(0, e.blocks);
+
+ assertTrue(f.exists());
+ }
+ {
+ File f = new File(fromDir, "tag%3A!@12345.txt");
+ try (FileWriter w = new FileWriter(f)) {
+ w.write(new char[1024]);
+ }
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals("tag:!", e.tag);
+ assertEquals(12345, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_TEXT, e.flags);
+ assertEquals(1, e.blocks);
+
+ assertTrue(f.exists());
+ }
+ {
+ File f = new File(fromDir, "tag%3A!@12345.txt.gz");
+ try (FileWriter w = new FileWriter(f)) {
+ w.write(new char[1025]);
+ }
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals("tag:!", e.tag);
+ assertEquals(12345, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_TEXT | DropBoxManager.IS_GZIPPED, e.flags);
+ assertEquals(2, e.blocks);
+
+ assertTrue(f.exists());
+ }
+ {
+ File f = new File(fromDir, "tag%3A!@12345.lost");
+ f.createNewFile();
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals("tag:!", e.tag);
+ assertEquals(12345, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_EMPTY, e.flags);
+ assertEquals(0, e.blocks);
+
+ assertTrue(f.exists());
+ }
+ {
+ File f = new File(fromDir, "@12345.dat"); // Empty tag -- this actually works.
+ f.createNewFile();
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals("", e.tag);
+ assertEquals(12345, e.timestampMillis);
+ assertEquals(0, e.flags);
+ assertEquals(0, e.blocks);
+
+ assertTrue(f.exists());
+ }
+ // From invalid filenames.
+ {
+ File f = new File(fromDir, "tag.dat"); // No @.
+ f.createNewFile();
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals(null, e.tag);
+ assertEquals(0, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_EMPTY, e.flags);
+ assertEquals(0, e.blocks);
+
+ assertFalse(f.exists());
+ }
+ {
+ File f = new File(fromDir, "tag@.dat"); // Invalid timestamp.
+ f.createNewFile();
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals(null, e.tag);
+ assertEquals(0, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_EMPTY, e.flags);
+ assertEquals(0, e.blocks);
+
+ assertFalse(f.exists());
+ }
+ {
+ File f = new File(fromDir, "tag@12345.daxt"); // Invalid extension.
+ f.createNewFile();
+
+ EntryFile e = new EntryFile(f, 1024);
+
+ assertEquals(null, e.tag);
+ assertEquals(0, e.timestampMillis);
+ assertEquals(DropBoxManager.IS_EMPTY, e.flags);
+ assertEquals(0, e.blocks);
+
+ assertFalse(f.exists());
+ }
+ }
+
+ public void testCompareEntries() {
+ File dir = getEmptyDir("testCompareEntries");
+ assertEquals(-1,
+ new EntryFile(new File(dir, "aaa@100.dat"), 1).compareTo(
+ new EntryFile(new File(dir, "bbb@200.dat"), 1)));
+ assertEquals(1,
+ new EntryFile(new File(dir, "aaa@200.dat"), 1).compareTo(
+ new EntryFile(new File(dir, "bbb@100.dat"), 1)));
+ assertEquals(-1,
+ new EntryFile(new File(dir, "aaa@100.dat"), 1).compareTo(
+ new EntryFile(new File(dir, "bbb@100.dat"), 1)));
+ assertEquals(1,
+ new EntryFile(new File(dir, "bbb@100.dat"), 1).compareTo(
+ new EntryFile(new File(dir, "aaa@100.dat"), 1)));
+ }
+
private void addRandomEntry(DropBoxManager dropbox, String tag, int size) throws Exception {
byte[] bytes = new byte[size];
new Random(System.currentTimeMillis()).nextBytes(bytes);