Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Java] Transition User profile away from bytables #181

Merged
merged 8 commits into from
Jan 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private JSONObject doInBackground(String requestData, String customEndpoint, Tra
request.endpoint(customEndpoint);
//getting connection ready
try {
connection = cp.connection(request, null);
connection = cp.connection(request);
} catch (IOException e) {
L.e("[ImmediateRequestMaker] IOException while preparing remote config update request :[" + e + "]");
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ protected MigrationHelper(final Log logger) {

// add migrations below
migrations.add(this::migration_DeleteConfigFile_01);
migrations.add(this::migration_UserImplFile_02);
latestMigrationVersion = migrations.size();
}

Expand Down Expand Up @@ -127,6 +128,48 @@ protected boolean migration_DeleteConfigFile_01(final Map<String, Object> migrat
return true;
}

/**
* Deletes the timed event file, the user impl file and the session files and extracts events
* stored inside the timed event file and the session files
*
* @param migrationParams parameters to pass to migrations
* @return true if the migration was successful, false otherwise
*/
protected boolean migration_UserImplFile_02(final Map<String, Object> migrationParams) {
if (currentDataModelVersion >= 2) {
logger.d("[MigrationHelper] migration_UserImplFile_02, Migration already applied");
return true;
}
logger.i("[MigrationHelper] migration_UserImplFile_02, Deleting user impl file migrating from 01 to 02");
currentDataModelVersion += 1;

File sdkPath = (File) migrationParams.get("sdk_path");
if (sdkPath == null) { // this is not expected, but just in case and for null safety, why 2? because we expect 1 file to be the json config file
logger.d("[MigrationHelper] migration_UserImplFile_02, No files to delete, returning");
return false;
}

//SDK_FOLDER/[CLY]_user_0
File userFile = new File(sdkPath, SDKStorage.FILE_NAME_PREFIX + SDKStorage.FILE_NAME_SEPARATOR + "user" + SDKStorage.FILE_NAME_SEPARATOR + 0);
//delete user file
deleteFileIfExist(userFile, "migration_UserImplFile_02, Cannot delete user file ");
return true;
}

/**
* Deletes the file if it exists, logs the error if it cannot be deleted
*
* @param file to delete
* @param log to log if the file cannot be deleted
*/
private void deleteFileIfExist(File file, String log) {
try { // if we cannot delete the config file, we cannot continue
Files.deleteIfExists(file.toPath());
} catch (IOException e) {
logger.e("[MigrationHelper] " + log + e);
}
}

/**
* Reads unnecessary parts of the config file for the 1st migration
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

public class ModuleUserProfile extends ModuleBase {
static final String CUSTOM_KEY = "custom";
static final String PICTURE_IN_USER_PROFILE = "[CLY]_USER_PROFILE_PICTURE";
boolean isSynced = true;
static final String PICTURE_BYTES = "[CLY]_picture_bytes";
UserProfile userProfileInterface;
private final Map<String, Object> sets;
private final List<OpParams> ops;
Expand Down Expand Up @@ -93,9 +93,10 @@ private Object optString(String key, Object value) {
* Transforming changes in "sets" into a json contained in "changes"
*
* @param changes JSONObject to store changes
* @param params Params to store changes
* @throws JSONException if something goes wrong
*/
void perform(JSONObject changes) throws JSONException {
void perform(JSONObject changes, Params params) throws JSONException {
for (String key : sets.keySet()) {
Object value = sets.get(key);
switch (key) {
Expand All @@ -114,7 +115,7 @@ void perform(JSONObject changes) throws JSONException {
} else if (value instanceof byte[]) {
internalConfig.sdk.user().picture = (byte[]) value;
//set a special value to indicate that the picture information is already stored in memory
changes.put(PredefinedUserPropertyKeys.PICTURE_PATH, PICTURE_IN_USER_PROFILE);
params.add(PICTURE_BYTES, Utils.Base64.encode((byte[]) value));
}
break;
case PredefinedUserPropertyKeys.PICTURE_PATH:
Expand All @@ -128,7 +129,7 @@ void perform(JSONObject changes) throws JSONException {
changes.put(PredefinedUserPropertyKeys.PICTURE, value);
} else {
//if we get here then that means it is a local file path which we would send over as bytes to the server
changes.put(PredefinedUserPropertyKeys.PICTURE_PATH, value);
params.add(PredefinedUserPropertyKeys.PICTURE_PATH, value);
}
internalConfig.sdk.user().picturePath = value.toString();
} else {
Expand Down Expand Up @@ -211,21 +212,12 @@ private Params prepareRequestParamsForUserProfile() {
isSynced = true;
Params params = new Params();
final JSONObject json = new JSONObject();
perform(json);
if (json.has(PredefinedUserPropertyKeys.PICTURE_PATH)) {
try {
params.add(PredefinedUserPropertyKeys.PICTURE_PATH, json.getString(PredefinedUserPropertyKeys.PICTURE_PATH));
json.remove(PredefinedUserPropertyKeys.PICTURE_PATH);
} catch (JSONException e) {
L.w("Won't send picturePath" + e);
}
}
if (!json.isEmpty() || internalConfig.sdk.user().picturePath != null || internalConfig.sdk.user().picture != null) {
perform(json, params);
if (!json.isEmpty()) {
params.add("user_details", json.toString());
return params;
} else {
return new Params();
}

return params;
}

/**
Expand Down
11 changes: 1 addition & 10 deletions sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -557,16 +557,7 @@ public Integer remaningRequests() {
}
}

try {
user = Storage.read(config, new UserImpl(config));
if (user == null) {
user = new UserImpl(config);
}
} catch (Throwable e) {
L.e("[SDKCore] Cannot happen" + e);
user = new UserImpl(config);
}

user = new UserImpl(config);
initFinished(config);
}

Expand Down
74 changes: 33 additions & 41 deletions sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import ly.count.sdk.java.PredefinedUserPropertyKeys;
import ly.count.sdk.java.User;
import org.json.JSONObject;

/**
Expand Down Expand Up @@ -118,11 +117,10 @@ public HttpURLConnection openConnection(String url, String params, boolean using
* set SSL context, calculate and add checksum, load and send user picture if needed.
*
* @param request request to send
* @param user user to check for picture
* @return connection, not {@link HttpURLConnection} yet
* @throws IOException from {@link HttpURLConnection} in case of error
*/
HttpURLConnection connection(final Request request, final User user) throws IOException {
HttpURLConnection connection(final Request request) throws IOException {
String endpoint = request.params.remove(Request.ENDPOINT);

if (!request.params.has("device_id") && config.getDeviceId() != null) {
Expand All @@ -135,10 +133,10 @@ HttpURLConnection connection(final Request request, final User user) throws IOEx
}

String path = config.getServerURL().toString() + endpoint;
String picturePathValue = request.params.remove(PredefinedUserPropertyKeys.PICTURE_PATH);
boolean usingGET = !config.isHTTPPostForced() && request.isGettable(config.getServerURL()) && Utils.isEmptyOrNull(picturePathValue);
byte[] maybePictureData = getPictureDataFromRequest(request);
boolean usingGET = !config.isHTTPPostForced() && request.isGettable(config.getServerURL()) && maybePictureData == null;

if (!usingGET && !Utils.isEmptyOrNull(picturePathValue)) {
if (!usingGET && maybePictureData != null) {
path = setProfilePicturePathRequestParams(path, request.params);
}

Expand All @@ -159,18 +157,15 @@ HttpURLConnection connection(final Request request, final User user) throws IOEx
OutputStream output = null;
PrintWriter writer = null;
try {
L.d("[network] Picture path value " + picturePathValue);
byte[] pictureByteData = picturePathValue == null ? null : getPictureDataFromGivenValue(user, picturePathValue);

if (pictureByteData != null) {
if (maybePictureData != null) {
String boundary = Long.toHexString(System.currentTimeMillis());

connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

output = connection.getOutputStream();
writer = new PrintWriter(new OutputStreamWriter(output, Utils.UTF8), true);

addMultipart(output, writer, boundary, "image/jpeg", "binaryFile", "image", pictureByteData);
addMultipart(output, writer, boundary, "image/jpeg", "binaryFile", "image", maybePictureData);

StringBuilder salting = new StringBuilder();
Map<String, String> map = request.params.map();
Expand Down Expand Up @@ -240,30 +235,25 @@ void addMultipart(OutputStream output, PrintWriter writer, String boundary, Stri
* If we have the bytes, give them
* Otherwise load them from disk
*
* @param user
* @param picture
* @return
* @param request picture path or base64 encoded byte array
* @return picture data
*/
byte[] getPictureDataFromGivenValue(User user, String picture) {
if (user == null) {
return null;
}
byte[] getPictureDataFromRequest(Request request) {
String maybeLocalPath = request.params.remove(PredefinedUserPropertyKeys.PICTURE_PATH);
String maybePictureData = request.params.remove(ModuleUserProfile.PICTURE_BYTES);

byte[] data = null;
if (ModuleUserProfile.PICTURE_IN_USER_PROFILE.equals(picture)) {
//if the value is this special value then we know that we will send over bytes that are already provided by the integrator
//those stored bytes are already in a internal data structure, use them
data = user.picture();
} else {
//otherwise we assume it is a local path, and we try to read it from disk

//first check for pure bytes
if (!Utils.isEmptyOrNull(maybePictureData)) {
data = Utils.Base64.decode(maybePictureData, L);
} else if (!Utils.isEmptyOrNull(maybeLocalPath)) {
//if no bytes, check for local path
File file = new File(maybeLocalPath);
try {
File file = new File(picture);
if (!file.exists()) {
return null;
}
data = Files.readAllBytes(file.toPath());
} catch (Throwable t) {
L.w("[Transport] getPictureDataFromGivenValue, Error while reading picture from disk " + t);
} catch (IOException e) {
L.e("[Transport] getPictureDataFromRequest, Error while reading picture, wont send from path:[ " + maybeLocalPath + "], " + e);
}
}

Expand Down Expand Up @@ -319,7 +309,7 @@ public Boolean send() {
Class requestOwner = request.owner();
request.params.remove(Request.MODULE);

connection = connection(request, SDKCore.instance.user());
connection = connection(request);
connection.connect();

int code = connection.getResponseCode();
Expand Down Expand Up @@ -516,22 +506,24 @@ public X509Certificate[] getAcceptedIssuers() {
private String setProfilePicturePathRequestParams(String path, Params params) {
Params tempParams = new Params();

tempParams.add("device_id", params.get("device_id"));
tempParams.add("app_key", params.get("app_key"));
tempParams.add("timestamp", params.get("timestamp"));
tempParams.add("sdk_name", params.get("sdk_name"));
tempParams.add("sdk_version", params.get("sdk_version"));
tempParams.add("tz", params.get("tz"));
tempParams.add("hour", params.get("hour"));
tempParams.add("dow", params.get("dow"));
tempParams.add("rr", params.get("rr"));
tempParams.add("device_id", params.remove("device_id"));
tempParams.add("app_key", params.remove("app_key"));
tempParams.add("timestamp", params.remove("timestamp"));
tempParams.add("sdk_name", params.remove("sdk_name"));
tempParams.add("sdk_version", params.remove("sdk_version"));
tempParams.add("tz", params.remove("tz"));
tempParams.add("hour", params.remove("hour"));
tempParams.add("dow", params.remove("dow"));
tempParams.add("rr", params.remove("rr"));

if (params.has("av")) {
tempParams.add("av", params.get("av"));
tempParams.add("av", params.remove("av"));
}
//if no user details, add empty user details to indicate that we are sending a picture
if (!params.has("user_details")) {
tempParams.add("user_details", "{}");
} else {
tempParams.add("user_details", params.remove("user_details"));
}

return path + tempParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ public User commit() {

try {
Countly.instance().userProfile().save();
Storage.push(SDKCore.instance.config, user); // todo this is not need it is for another task
} catch (JSONException e) {
L.e("[UserEditorImpl] Exception while committing changes to User profile" + e);
}
Expand Down
Loading
Loading