Skip to content

Commit a6fe4ba

Browse files
Merge pull request #307 from Countly/sbs-review-fixes
SBS review fixes: replace stored settings, keep the consent requirement, device ID trigger, store file
2 parents f26cb58 + 17cb726 commit a6fe4ba

11 files changed

Lines changed: 436 additions & 201 deletions

‎sdk-java/src/main/java/ly/count/sdk/java/Config.java‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1533,7 +1533,11 @@ public Config disableAutoSendUserProperties() {
15331533
* Provide an initial SDK behavior settings payload to apply at init time,
15341534
* before the first {@code /o/sdk?method=sc} fetch returns. The value is
15351535
* the same JSON shape the Countly server emits: {@code {"v":1,"t":<ts>,
1536-
* "c":{...}}}. The seed is only used when nothing has been persisted yet.
1536+
* "c":{...}}}. The seed is only used when nothing has been persisted yet,
1537+
* and it stands in for the server's settings: the first answer from the
1538+
* server replaces it, so a setting the answer does not carry goes back to
1539+
* this configuration. A {@code "cr":true} here can be lifted by that answer;
1540+
* use {@link #setRequiresConsent(boolean)} for a requirement that must hold.
15371541
*
15381542
* @param sdkBehaviorSettings serialized config JSON, or {@code null} for none
15391543
* @return {@code this} instance for method chaining

‎sdk-java/src/main/java/ly/count/sdk/java/internal/JsonFileStorage.java‎

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
import java.io.BufferedWriter;
44
import java.io.File;
55
import java.io.IOException;
6+
import java.nio.file.AtomicMoveNotSupportedException;
67
import java.nio.file.Files;
8+
import java.nio.file.StandardCopyOption;
79
import javax.annotation.Nonnull;
810
import org.json.JSONArray;
11+
import org.json.JSONException;
912
import org.json.JSONObject;
1013

1114
public class JsonFileStorage {
@@ -22,9 +25,22 @@ public class JsonFileStorage {
2225
public JsonFileStorage(@Nonnull final File file, @Nonnull Log logger) {
2326
this.logger = logger;
2427
this.file = file;
28+
deleteLeftoverTemporaryFile();
2529
this.json = readJsonFile(file);
2630
}
2731

32+
/**
33+
* Deletes the temporary file of a save that was stopped before its move. The json file still
34+
* holds the previous save then, and a leftover file would be taken for other stored SDK data.
35+
*/
36+
private void deleteLeftoverTemporaryFile() {
37+
try {
38+
Files.deleteIfExists(temporaryFileOf(file).toPath());
39+
} catch (IOException e) {
40+
logger.w("[JsonFileStorage] deleteLeftoverTemporaryFile, Failed to delete the temporary file, reason: [" + e + "]");
41+
}
42+
}
43+
2844
/**
2945
* If key exists changes its value, otherwise adds new key-value pair
3046
* Null key or value is not allowed
@@ -39,26 +55,56 @@ public synchronized void add(@Nonnull final String key, @Nonnull Object value) {
3955
}
4056

4157
/**
42-
* Saves changes to the disk/db/memory. When the data cannot be serialized, the file keeps what
43-
* it had.
58+
* Saves changes to the disk/db/memory. The data is written next to the file and then moved over
59+
* it, so a process stopped while saving leaves the previous file whole. When the data cannot be
60+
* serialized, the file keeps what it had.
4461
*/
4562
public synchronized void save() {
4663
logger.i("[JsonFileStorage] save, Saving json file: [" + file.getAbsolutePath() + "]");
4764

48-
// Serialized before the file is opened: opening truncates it, and toString returns null instead of throwing
65+
// toString returns null instead of throwing, so a failed serialization has to be caught before anything is written
4966
String content = json.toString();
5067
if (content == null) {
5168
logger.e("[JsonFileStorage] save, Failed to serialize the data, the json file is left as it is: [" + file.getAbsolutePath() + "]");
5269
return;
5370
}
5471

55-
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath())) {
56-
writer.write(content);
72+
File temporary = temporaryFileOf(file);
73+
try {
74+
try (BufferedWriter writer = Files.newBufferedWriter(temporary.toPath())) {
75+
writer.write(content);
76+
}
77+
replaceFile(temporary, file);
5778
} catch (IOException e) {
5879
logger.e("[JsonFileStorage] save, Failed to save json file, reason: [" + e.getMessage() + "]");
5980
}
6081
}
6182

83+
/**
84+
* The file a save writes before moving it over the json file.
85+
*
86+
* @param file the json file
87+
* @return the temporary file next to it
88+
*/
89+
static File temporaryFileOf(@Nonnull File file) {
90+
return new File(file.getAbsolutePath() + ".tmp");
91+
}
92+
93+
/**
94+
* Moves a file over another, atomically where the file system supports it.
95+
*
96+
* @param source the file to move
97+
* @param target the file it replaces
98+
* @throws IOException when the move fails
99+
*/
100+
private static void replaceFile(@Nonnull File source, @Nonnull File target) throws IOException {
101+
try {
102+
Files.move(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
103+
} catch (AtomicMoveNotSupportedException e) {
104+
Files.move(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
105+
}
106+
}
107+
62108
/**
63109
* Removes key-value pair
64110
* Null key is not allowed
@@ -225,6 +271,9 @@ private JSONObject readJsonFile(@Nonnull final File file) {
225271
} catch (IOException e) {
226272
logger.e("[JsonFileStorage] readJsonFile, Failed to read json file, reason: [" + e.getMessage() + "]");
227273
return new JSONObject();
274+
} catch (JSONException e) {
275+
logger.e("[JsonFileStorage] readJsonFile, The json file is not valid json, starting with empty storage that replaces it on the next save: [" + file.getAbsolutePath() + "], reason: [" + e.getMessage() + "]");
276+
return new JSONObject();
228277
}
229278
}
230279
}

0 commit comments

Comments
 (0)