diff --git a/app/src/main/java/ai/elimu/analytics/dao/LetterLearningEventDao.java b/app/src/main/java/ai/elimu/analytics/dao/LetterLearningEventDao.java index b60d859..2d7b78a 100644 --- a/app/src/main/java/ai/elimu/analytics/dao/LetterLearningEventDao.java +++ b/app/src/main/java/ai/elimu/analytics/dao/LetterLearningEventDao.java @@ -1,7 +1,12 @@ package ai.elimu.analytics.dao; +import android.database.Cursor; + import androidx.room.Dao; import androidx.room.Insert; +import androidx.room.Query; + +import java.util.List; import ai.elimu.analytics.entity.LetterLearningEvent; @@ -10,4 +15,10 @@ public interface LetterLearningEventDao { @Insert void insert(LetterLearningEvent letterLearningEvent); + + @Query("SELECT * FROM LetterLearningEvent ORDER BY time DESC") + List loadAllOrderedByTimeDesc(); + + @Query("SELECT * FROM LetterLearningEvent ORDER BY time") + Cursor loadAllOrderedByTime(); } diff --git a/app/src/main/java/ai/elimu/analytics/rest/LetterLearningEventService.java b/app/src/main/java/ai/elimu/analytics/rest/LetterLearningEventService.java new file mode 100644 index 0000000..8968dc2 --- /dev/null +++ b/app/src/main/java/ai/elimu/analytics/rest/LetterLearningEventService.java @@ -0,0 +1,15 @@ +package ai.elimu.analytics.rest; + +import okhttp3.MultipartBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.Part; + +public interface LetterLearningEventService { + + @Multipart + @POST("analytics/letter-learning-events/csv") + Call uploadCsvFile(@Part MultipartBody.Part part); +} diff --git a/app/src/main/java/ai/elimu/analytics/task/ExportEventsToCsvWorker.java b/app/src/main/java/ai/elimu/analytics/task/ExportEventsToCsvWorker.java index dfba35f..f634a90 100644 --- a/app/src/main/java/ai/elimu/analytics/task/ExportEventsToCsvWorker.java +++ b/app/src/main/java/ai/elimu/analytics/task/ExportEventsToCsvWorker.java @@ -17,9 +17,11 @@ import java.text.SimpleDateFormat; import java.util.List; +import ai.elimu.analytics.dao.LetterLearningEventDao; import ai.elimu.analytics.dao.StoryBookLearningEventDao; import ai.elimu.analytics.dao.WordLearningEventDao; import ai.elimu.analytics.db.RoomDb; +import ai.elimu.analytics.entity.LetterLearningEvent; import ai.elimu.analytics.entity.StoryBookLearningEvent; import ai.elimu.analytics.entity.WordLearningEvent; @@ -33,21 +35,22 @@ public ExportEventsToCsvWorker(@NonNull Context context, @NonNull WorkerParamete @Override public Result doWork() { Log.i(getClass().getName(), "doWork"); - - exportStoryBookLearningEventsToCsv(); + + exportLetterLearningEventsToCsv(); exportWordLearningEventsToCsv(); + exportStoryBookLearningEventsToCsv(); return Result.success(); } - - private void exportStoryBookLearningEventsToCsv() { - Log.i(getClass().getName(), "exportStoryBookLearningEventsToCsv"); - - // Extract StoryBookLearningEvents from the database that have not yet been exported to CSV. + + private void exportLetterLearningEventsToCsv() { + Log.i(getClass().getName(), "exportLetterLearningEventsToCsv"); + + // Extract LetterLearningEvents from the database that have not yet been exported to CSV. RoomDb roomDb = RoomDb.getDatabase(getApplicationContext()); - StoryBookLearningEventDao storyBookLearningEventDao = roomDb.storyBookLearningEventDao(); - List storyBookLearningEvents = storyBookLearningEventDao.loadAll(); - Log.i(getClass().getName(), "storyBookLearningEvents.size(): " + storyBookLearningEvents.size()); + LetterLearningEventDao letterLearningEventDao = roomDb.letterLearningEventDao(); + List letterLearningEvents = letterLearningEventDao.loadAllOrderedByTimeDesc(); + Log.i(getClass().getName(), "letterLearningEvents.size(): " + letterLearningEvents.size()); CSVFormat csvFormat = CSVFormat.DEFAULT .withHeader( @@ -55,7 +58,8 @@ private void exportStoryBookLearningEventsToCsv() { "time", "android_id", "package_name", - "storybook_id", + "letter_id", + "letter_text", "learning_event_type" ); StringWriter stringWriter = new StringWriter(); @@ -64,26 +68,27 @@ private void exportStoryBookLearningEventsToCsv() { // Generate one CSV file per day of events String dateOfPreviousEvent = null; - for (StoryBookLearningEvent storyBookLearningEvent : storyBookLearningEvents) { - // Export event to CSV file. Example format: "files/storybook-learning-events/7161a85a0e4751cd_storybook-learning-events_2020-03-21.csv" + for (LetterLearningEvent letterLearningEvent : letterLearningEvents) { + // Export event to CSV file. Example format: "files/letter-learning-events/7161a85a0e4751cd_letter-learning-events_2020-03-21.csv" SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); - String date = simpleDateFormat.format(storyBookLearningEvent.getTime().getTime()); + String date = simpleDateFormat.format(letterLearningEvent.getTime().getTime()); if (!date.equals(dateOfPreviousEvent)) { // Reset file content stringWriter = new StringWriter(); csvPrinter = new CSVPrinter(stringWriter, csvFormat); } dateOfPreviousEvent = date; - String csvFilename = storyBookLearningEvent.getAndroidId() + "_storybook-learning-events_" + date + ".csv"; + String csvFilename = letterLearningEvent.getAndroidId() + "_letter-learning-events_" + date + ".csv"; Log.i(getClass().getName(), "csvFilename: " + csvFilename); csvPrinter.printRecord( - storyBookLearningEvent.getId(), - storyBookLearningEvent.getTime().getTimeInMillis(), - storyBookLearningEvent.getAndroidId(), - storyBookLearningEvent.getPackageName(), - storyBookLearningEvent.getStoryBookId(), - storyBookLearningEvent.getLearningEventType() + letterLearningEvent.getId(), + letterLearningEvent.getTime().getTimeInMillis(), + letterLearningEvent.getAndroidId(), + letterLearningEvent.getPackageName(), + letterLearningEvent.getLetterId(), + letterLearningEvent.getLetterText(), + letterLearningEvent.getLearningEventType() ); csvPrinter.flush(); @@ -91,8 +96,8 @@ private void exportStoryBookLearningEventsToCsv() { // Write the content to the CSV file File filesDir = getApplicationContext().getFilesDir(); - File storyBookLearningEventsDir = new File(filesDir, "storybook-learning-events"); - File csvFile = new File(storyBookLearningEventsDir, csvFilename); + File letterLearningEventsDir = new File(filesDir, "letter-learning-events"); + File csvFile = new File(letterLearningEventsDir, csvFilename); FileUtils.writeStringToFile(csvFile, csvFileContent, "UTF-8"); } } catch (IOException e) { @@ -161,4 +166,64 @@ private void exportWordLearningEventsToCsv() { Log.e(getClass().getName(), null, e); } } + + private void exportStoryBookLearningEventsToCsv() { + Log.i(getClass().getName(), "exportStoryBookLearningEventsToCsv"); + + // Extract StoryBookLearningEvents from the database that have not yet been exported to CSV. + RoomDb roomDb = RoomDb.getDatabase(getApplicationContext()); + StoryBookLearningEventDao storyBookLearningEventDao = roomDb.storyBookLearningEventDao(); + List storyBookLearningEvents = storyBookLearningEventDao.loadAll(); + Log.i(getClass().getName(), "storyBookLearningEvents.size(): " + storyBookLearningEvents.size()); + + CSVFormat csvFormat = CSVFormat.DEFAULT + .withHeader( + "id", + "time", + "android_id", + "package_name", + "storybook_id", + "learning_event_type" + ); + StringWriter stringWriter = new StringWriter(); + try { + CSVPrinter csvPrinter = new CSVPrinter(stringWriter, csvFormat); + + // Generate one CSV file per day of events + String dateOfPreviousEvent = null; + for (StoryBookLearningEvent storyBookLearningEvent : storyBookLearningEvents) { + // Export event to CSV file. Example format: "files/storybook-learning-events/7161a85a0e4751cd_storybook-learning-events_2020-03-21.csv" + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + String date = simpleDateFormat.format(storyBookLearningEvent.getTime().getTime()); + if (!date.equals(dateOfPreviousEvent)) { + // Reset file content + stringWriter = new StringWriter(); + csvPrinter = new CSVPrinter(stringWriter, csvFormat); + } + dateOfPreviousEvent = date; + String csvFilename = storyBookLearningEvent.getAndroidId() + "_storybook-learning-events_" + date + ".csv"; + Log.i(getClass().getName(), "csvFilename: " + csvFilename); + + csvPrinter.printRecord( + storyBookLearningEvent.getId(), + storyBookLearningEvent.getTime().getTimeInMillis(), + storyBookLearningEvent.getAndroidId(), + storyBookLearningEvent.getPackageName(), + storyBookLearningEvent.getStoryBookId(), + storyBookLearningEvent.getLearningEventType() + ); + csvPrinter.flush(); + + String csvFileContent = stringWriter.toString(); + + // Write the content to the CSV file + File filesDir = getApplicationContext().getFilesDir(); + File storyBookLearningEventsDir = new File(filesDir, "storybook-learning-events"); + File csvFile = new File(storyBookLearningEventsDir, csvFilename); + FileUtils.writeStringToFile(csvFile, csvFileContent, "UTF-8"); + } + } catch (IOException e) { + Log.e(getClass().getName(), null, e); + } + } } diff --git a/app/src/main/java/ai/elimu/analytics/task/UploadEventsWorker.java b/app/src/main/java/ai/elimu/analytics/task/UploadEventsWorker.java index 89b375a..d4fe985 100644 --- a/app/src/main/java/ai/elimu/analytics/task/UploadEventsWorker.java +++ b/app/src/main/java/ai/elimu/analytics/task/UploadEventsWorker.java @@ -12,6 +12,8 @@ import java.util.Arrays; import ai.elimu.analytics.BaseApplication; +import ai.elimu.analytics.BuildConfig; +import ai.elimu.analytics.rest.LetterLearningEventService; import ai.elimu.analytics.rest.StoryBookLearningEventService; import ai.elimu.analytics.rest.WordLearningEventService; import okhttp3.MediaType; @@ -33,20 +35,23 @@ public UploadEventsWorker(@NonNull Context context, @NonNull WorkerParameters wo public Result doWork() { Log.i(getClass().getName(), "doWork"); - uploadStoryBookLearningEvents(); - uploadWordLearningEvents(); + if (!"debug".equals(BuildConfig.BUILD_TYPE)) { + uploadLetterLearningEvents(); + uploadWordLearningEvents(); + uploadStoryBookLearningEvents(); + } return Result.success(); } - - private void uploadStoryBookLearningEvents() { - Log.i(getClass().getName(), "uploadStoryBookLearningEvents"); + + private void uploadLetterLearningEvents() { + Log.i(getClass().getName(), "uploadLetterLearningEvents"); // Upload CSV files to the server File filesDir = getApplicationContext().getFilesDir(); - File storyBookLearningEventsDir = new File(filesDir, "storybook-learning-events"); - Log.i(getClass().getName(), "Uploading CSV files from " + storyBookLearningEventsDir); - File[] files = storyBookLearningEventsDir.listFiles(); + File letterLearningEventsDir = new File(filesDir, "letter-learning-events"); + Log.i(getClass().getName(), "Uploading CSV files from " + letterLearningEventsDir); + File[] files = letterLearningEventsDir.listFiles(); if (files != null) { Log.i(getClass().getName(), "files.length: " + files.length); Arrays.sort(files); @@ -56,10 +61,10 @@ private void uploadStoryBookLearningEvents() { BaseApplication baseApplication = (BaseApplication) getApplicationContext(); Retrofit retrofit = baseApplication.getRetrofit(); - StoryBookLearningEventService storyBookLearningEventService = retrofit.create(StoryBookLearningEventService.class); + LetterLearningEventService letterLearningEventService = retrofit.create(LetterLearningEventService.class); RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), requestBody); - Call call = storyBookLearningEventService.uploadCsvFile(part); + Call call = letterLearningEventService.uploadCsvFile(part); Log.i(getClass().getName(), "call.request(): " + call.request()); try { Response response = call.execute(); @@ -120,4 +125,45 @@ private void uploadWordLearningEvents() { } } } + + private void uploadStoryBookLearningEvents() { + Log.i(getClass().getName(), "uploadStoryBookLearningEvents"); + + // Upload CSV files to the server + File filesDir = getApplicationContext().getFilesDir(); + File storyBookLearningEventsDir = new File(filesDir, "storybook-learning-events"); + Log.i(getClass().getName(), "Uploading CSV files from " + storyBookLearningEventsDir); + File[] files = storyBookLearningEventsDir.listFiles(); + if (files != null) { + Log.i(getClass().getName(), "files.length: " + files.length); + Arrays.sort(files); + for (int i = 0; i < files.length; i++) { + File file = files[i]; + Log.i(getClass().getName(), "file.getName(): " + file.getName()); + + BaseApplication baseApplication = (BaseApplication) getApplicationContext(); + Retrofit retrofit = baseApplication.getRetrofit(); + StoryBookLearningEventService storyBookLearningEventService = retrofit.create(StoryBookLearningEventService.class); + RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); + MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), requestBody); + Call call = storyBookLearningEventService.uploadCsvFile(part); + Log.i(getClass().getName(), "call.request(): " + call.request()); + try { + Response response = call.execute(); + Log.i(getClass().getName(), "response: " + response); + Log.i(getClass().getName(), "response.isSuccessful(): " + response.isSuccessful()); + if (response.isSuccessful()) { + String bodyString = response.body().string(); + Log.i(getClass().getName(), "bodyString: " + bodyString); + } else { + String errorBodyString = response.errorBody().string(); + Log.e(getClass().getName(), "errorBodyString: " + errorBodyString); + // TODO: Handle error + } + } catch (IOException e) { + Log.e(getClass().getName(), null, e); + } + } + } + } }