diff --git a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java index 3328223368718f85deeb26ca544210219da850ab..f53ee9c7cab76154dcf1dc54f6fc79d0fc6e10c1 100644 --- a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java @@ -26,6 +26,7 @@ import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DataConsumptionObserver; import foundation.e.drive.utils.SynchronizationServiceConnection; /** @@ -43,6 +44,8 @@ public class FileEventListener { } public void onEvent(int event, File file) { + if (file.getName().equals(DataConsumptionObserver.FILE_NAME)) return; //tmp. Should be handled better! + if (event == FileObserver.DELETE) { if (file.isDirectory()) { handleDirectoryDelete(file); diff --git a/app/src/main/java/foundation/e/drive/models/DataConsumptionEvent.java b/app/src/main/java/foundation/e/drive/models/DataConsumptionEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..1e71992e65398c9e9bbf98cf92e2287508819c70 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/models/DataConsumptionEvent.java @@ -0,0 +1,69 @@ +/* + * Copyright © Vincent Bourgmayer (/e/ foundation). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.models; + +/** + * This class is a temporary data class for listening data consumption + * @author vincent Bourgmayer + */ +public class DataConsumptionEvent { + private static final String SEPARATOR = ","; + private final String requestType; + private final long timestamp; + private final boolean success; //success, failure + private final int httpResultCode; + private final String ncResultCode; + private final String networkType; //wifi vs mobile Data + private String filePathConcerned; //Optionnal + private long fileSize; //Optionnal + + /** + * Constructor for DataConsumptionEvent for when no file is concerned + * @param requestType + * @param success + * @param network + */ + public DataConsumptionEvent(String requestType, boolean success, int httpResultCode, String ncResultCode, String network ){ + this.requestType = requestType; + this.timestamp = System.currentTimeMillis(); + this.success = success; + this.httpResultCode = httpResultCode; + this.ncResultCode = ncResultCode; + this.networkType = network; + } + + /** + * Constructor for DataConsumptionEvent for when a file is concerned + * @param requestType + * @param success + * @param network + * @param filePath + */ + public DataConsumptionEvent(String requestType, boolean success, int httpResultCode, String ncResultCode, String network, String filePath, long fileSize ){ + this(requestType, success, httpResultCode, ncResultCode, network); + this.filePathConcerned = filePath; + this.fileSize = fileSize; + } + + + @Override + public String toString() { + return requestType + SEPARATOR + + timestamp + SEPARATOR + + success + SEPARATOR + + httpResultCode + SEPARATOR + + ncResultCode + SEPARATOR + + networkType + ((filePathConcerned != null ) ? SEPARATOR + + filePathConcerned + SEPARATOR +fileSize: ",NO,0"); + } + + public static String getCSVHeader(){ + return "requestType,timestamp,success,httpResultCode,ncResultCode,networkType,filePath,filesSize\n"; + } +} diff --git a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java index 4445239c721e7a0ead86a1264ed77327d9b88cde..610eacb288bfd00ad29cbd5306bdab2ce287f62a 100644 --- a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java @@ -17,8 +17,10 @@ import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.files.model.RemoteFile; import java.io.File; import foundation.e.drive.database.DbHelper; +import foundation.e.drive.models.DataConsumptionEvent; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DataConsumptionObserver; /** * @author Vincent Bourgmayer @@ -51,6 +53,7 @@ public class DownloadFileOperation extends RemoteOperation { protected RemoteOperationResult run(OwnCloudClient ownCloudClient) { Log.i(TAG, "run(ownCloudClient)"); + DataConsumptionObserver dataConsObserver = DataConsumptionObserver.getInstance(); //get or build synced file equivalent of this.mFile if (syncedFileState == null || targetPath == null || targetPath.isEmpty()) { Log.e(TAG, "syncedFileState or targetPath is empty or null. Can't Download in those conditions"); @@ -70,6 +73,17 @@ public class DownloadFileOperation extends RemoteOperation { tmpTargetPath); final RemoteOperationResult downloadResult = downloadOperation.execute(ownCloudClient); + + final DataConsumptionEvent dataEvent = new DataConsumptionEvent(downloadOperation.getClass().getSimpleName(), + downloadResult.isSuccess(), + downloadResult.getHttpCode(), + downloadResult.getCode().name(), + CommonUtils.getCurrentNetworkType(context), + remoteFile.getRemotePath(), + remoteFile.getSize()); + + dataConsObserver.addEvent(dataEvent); + RemoteOperationResult.ResultCode resultCode; boolean mustRestart = true; diff --git a/app/src/main/java/foundation/e/drive/operations/ListFileRemoteOperation.java b/app/src/main/java/foundation/e/drive/operations/ListFileRemoteOperation.java index 9efccf5821706c8b3ed27fc90a996a340c5ff214..6f4312ecc829c876650f67608df216a3e66cc48a 100644 --- a/app/src/main/java/foundation/e/drive/operations/ListFileRemoteOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/ListFileRemoteOperation.java @@ -20,8 +20,10 @@ import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import foundation.e.drive.database.DbHelper; +import foundation.e.drive.models.DataConsumptionEvent; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DataConsumptionObserver; import static org.apache.jackrabbit.webdav.DavConstants.DEPTH_1; @@ -98,7 +100,13 @@ public class ListFileRemoteOperation extends RemoteOperation { //Create ReadRemoteOperation LightReadFolderRemoteOperation operation = new LightReadFolderRemoteOperation(syncedFolder.getRemoteFolder(), DEPTH_1, false); RemoteOperationResult result = operation.execute(ownCloudClient); - + DataConsumptionObserver.getInstance().addEvent(new DataConsumptionEvent(operation.getClass().getSimpleName(), + result.isSuccess(), + result.getHttpCode(), + result.getCode().name(), + CommonUtils.getCurrentNetworkType(mContext), + syncedFolder.getRemoteFolder(), + 0)); if(result.isSuccess() ){ //is success then data can't be null int dataSize = result.getData().size(); diff --git a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java index 5b4c24ae50d72f4e268accd8271cb4b476004b8e..422ee42ee8b851c893d9e9141194657e59e33318 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -25,8 +25,10 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo import java.io.File; import java.util.ArrayList; import foundation.e.drive.database.DbHelper; +import foundation.e.drive.models.DataConsumptionEvent; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DataConsumptionObserver; /** * @author Vincent Bourgmayer @@ -40,6 +42,7 @@ public class UploadFileOperation extends RemoteOperation { private Context context; private SyncedFileState syncedState; private long availableQuota = -1; + private DataConsumptionObserver dataConsObserver; /** * Construct an upload operation with an already known syncedFileState @@ -85,6 +88,7 @@ public class UploadFileOperation extends RemoteOperation { return new RemoteOperationResult(ResultCode.SYNC_CONFLICT); } + dataConsObserver = DataConsumptionObserver.getInstance(); Float relativeQuotaBeforeFileUpload = 0.0f; if (this.availableQuota == -1) { RemoteOperationResult checkQuotaResult = checkAvailableSpace(client, file.length()); @@ -106,6 +110,14 @@ public class UploadFileOperation extends RemoteOperation { ResultCode resultCode; boolean mustRestart = true; + DataConsumptionEvent uploadEvent = new DataConsumptionEvent(uploadOperation.getClass().getSimpleName(), + uploadResult.isSuccess(), + uploadResult.getHttpCode(), + uploadResult.getCode().name(), + CommonUtils.getCurrentNetworkType(context), + file.getAbsolutePath(), + file.length()); + dataConsObserver.addEvent(uploadEvent); //if upload is a success if (uploadResult.isSuccess()) { Object data = uploadResult.getSingleData(); @@ -120,6 +132,7 @@ public class UploadFileOperation extends RemoteOperation { resultCode = ResultCode.CONFLICT; Log.d(TAG, "Catched a conflict result for : "+ file.getName()); mustRestart = false; + } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { resultCode = ResultCode.QUOTA_EXCEEDED; mustRestart = false; @@ -175,8 +188,17 @@ public class UploadFileOperation extends RemoteOperation { */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public RemoteOperationResult checkAvailableSpace(OwnCloudClient client, long fileSize) { + GetRemoteUserInfoOperation getRemoteUserInfoOperation = new GetRemoteUserInfoOperation(); RemoteOperationResult ocsResult = getRemoteUserInfoOperation.execute(client); + + DataConsumptionEvent dataEvent = new DataConsumptionEvent(getRemoteUserInfoOperation.getClass().getSimpleName(), + ocsResult.isSuccess(), + ocsResult.getHttpCode(), + ocsResult.getCode().name(), + CommonUtils.getCurrentNetworkType(context)); + dataConsObserver.addEvent(dataEvent); + if (ocsResult.isSuccess() && ocsResult.getData() != null) { UserInfo userInfo = (UserInfo) ocsResult.getData().get(0); this.availableQuota = userInfo.getQuota().getFree(); @@ -207,6 +229,13 @@ public class UploadFileOperation extends RemoteOperation { final CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(remoteFolderPath, true); try{ final RemoteOperationResult createFolderResult = createFolderOperation.execute(client); + dataConsObserver.addEvent(new DataConsumptionEvent(createFolderOperation.getClass().getSimpleName(), + createFolderResult.isSuccess(), + createFolderResult.getHttpCode(), + createFolderResult.getCode().name(), + CommonUtils.getCurrentNetworkType(context), + targetPath, 0)); + if (createFolderResult.isSuccess() || createFolderResult.getCode() == ResultCode.FOLDER_ALREADY_EXISTS) { return true; } diff --git a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java index 93db0f01ebf3ed414b1795643cbda0943b1bf48d..ac356f465c9ea251c82a7c24cd5d7ce0ff6c3f00 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -41,6 +41,7 @@ import foundation.e.drive.operations.RemoveFileOperation; import foundation.e.drive.operations.UploadFileOperation; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DataConsumptionObserver; import foundation.e.drive.utils.DavClientProvider; /** @@ -263,6 +264,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation break; } } + DataConsumptionObserver.getInstance().saveToFile(getApplicationContext()); } private void updateFailureCounter(SyncRequest request, boolean success) { diff --git a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java index 34665df12618fd626a388d30cef4480324892544..5e7e702dd095c8f58749689ad20b894fed08f9a6 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -22,8 +22,10 @@ import android.content.ContentResolver; import android.content.Context; import android.media.MediaScannerConnection; import android.net.ConnectivityManager; +import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.util.Log; @@ -249,6 +251,13 @@ public abstract class CommonUtils { return false; } + public static String getCurrentNetworkType(Context context){ + final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); + final NetworkInfo info = cm.getActiveNetworkInfo(); + if (info != null && info.isConnected()) return info.getTypeName(); + else return "NO"; + } + /** * Get mimetype of file from the file itself diff --git a/app/src/main/java/foundation/e/drive/utils/DataConsumptionObserver.java b/app/src/main/java/foundation/e/drive/utils/DataConsumptionObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..849446cfa9ef646b40e5a36a2632967dfb6f898b --- /dev/null +++ b/app/src/main/java/foundation/e/drive/utils/DataConsumptionObserver.java @@ -0,0 +1,73 @@ +package foundation.e.drive.utils; + +import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; + +import android.content.Context; +import android.util.Log; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.List; + +import foundation.e.drive.models.DataConsumptionEvent; + +/** + * This class is a little utility to temporary track data consumption due to eDrive + * @author vincent Bourgmayer + */ +public class DataConsumptionObserver { + private static final String LINE_BREAK ="\n"; + public static final String FILE_NAME = "dataConsumptionEvents.csv"; + private static DataConsumptionObserver INSTANCE; + + private ArrayList events; + private DataConsumptionObserver(){ + events = new ArrayList<>(); + }; + + public synchronized static DataConsumptionObserver getInstance(){ + if (INSTANCE == null){ + INSTANCE = new DataConsumptionObserver(); + } + return INSTANCE; + } + + public void addEvent(DataConsumptionEvent event){ + synchronized (this) { + events.add(event); + } + } + + public void saveToFile(Context context){ + if (events == null || events.isEmpty()) {return;} + + final String filedir = context.getFilesDir().getAbsolutePath()+PATH_SEPARATOR; + final File file = new File(filedir+FILE_NAME); + final StringBuilder fileContents = new StringBuilder(); + + synchronized (this) { + for (DataConsumptionEvent event : events) { + fileContents.append(event.toString() + LINE_BREAK); + } + try { + boolean isNewFile = false; + if (!file.exists()) { + file.createNewFile(); + isNewFile = true; + } + + FileWriter fileWritter = new FileWriter(file,true); + BufferedWriter bw = new BufferedWriter(fileWritter); + if(isNewFile) bw.write(DataConsumptionEvent.getCSVHeader()); + bw.write(fileContents.toString()); + bw.close(); + + events.clear(); + } catch (Exception e) { + Log.e("DataConsumptionObserver", "Can't save DataConsumptionEvent.csv "+e.toString()); + } + } + } +} diff --git a/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java b/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java index 700338d0f9e22b5a69e04faea94f03d2633b04a9..237a6594103314bb9ee286d3986ec1f731318356 100644 --- a/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java +++ b/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java @@ -39,9 +39,11 @@ import java.util.ArrayList; import foundation.e.drive.R; import foundation.e.drive.activity.AccountsActivity; +import foundation.e.drive.models.DataConsumptionEvent; import foundation.e.drive.operations.GetAliasOperation; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DataConsumptionObserver; import foundation.e.drive.widgets.EDriveWidget; /** @@ -55,6 +57,7 @@ public class AccountUserInfoWorker extends Worker { final GetAliasOperation getAliasOperation = new GetAliasOperation(); private final Context mContext; private Account account; + DataConsumptionObserver dataConsObserver; public AccountUserInfoWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); @@ -65,6 +68,7 @@ public class AccountUserInfoWorker extends Worker { @NonNull @Override public Result doWork() { + dataConsObserver = DataConsumptionObserver.getInstance(); account = CommonUtils.getAccount(mContext.getString(R.string.eelo_account_type), accountManager); OwnCloudClient client = CommonUtils.getOwnCloudClient(account, mContext); @@ -87,6 +91,11 @@ public class AccountUserInfoWorker extends Worker { private boolean fetchUserInfo(final OwnCloudClient client) { final RemoteOperationResult ocsResult = getRemoteUserInfoOperation.execute(client); + dataConsObserver.addEvent(new DataConsumptionEvent(getRemoteUserInfoOperation.getClass().getSimpleName(), + ocsResult.isSuccess(), + ocsResult.getHttpCode(), + ocsResult.getCode().name(), + CommonUtils.getCurrentNetworkType(mContext))); if (ocsResult.isSuccess() && ocsResult.getData() != null && !ocsResult.getData().isEmpty()) { final UserInfo userInfo = (UserInfo) ocsResult.getData().get(0); @@ -181,6 +190,11 @@ public class AccountUserInfoWorker extends Worker { private boolean fetchAliases(final OwnCloudClient client) { final RemoteOperationResult ocsResult = getAliasOperation.execute(client); String aliases = ""; + dataConsObserver.addEvent(new DataConsumptionEvent(getAliasOperation.getClass().getSimpleName(), + ocsResult.isSuccess(), + ocsResult.getHttpCode(), + ocsResult.getCode().name(), + CommonUtils.getCurrentNetworkType(mContext))); if (ocsResult.isSuccess() && ocsResult.getData() != null && !ocsResult.getData().isEmpty()) { ArrayList alias = new ArrayList<>(ocsResult.getData().size()); for (Object object : ocsResult.getData()) { diff --git a/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java b/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java index 38ec32f06445505f260f340b9a78913bb5525db6..36ae85526e62e32ea106461470cc76a414b3733d 100644 --- a/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java +++ b/app/src/main/java/foundation/e/drive/work/CreateRemoteFolderWorker.java @@ -26,9 +26,11 @@ import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation; import java.io.File; import foundation.e.drive.database.DbHelper; +import foundation.e.drive.models.DataConsumptionEvent; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.DataConsumptionObserver; /** * Create folder on ecloud for a given local folder @@ -86,6 +88,14 @@ public class CreateRemoteFolderWorker extends Worker { try { final RemoteOperationResult result = mkcolRequest.execute(client, true); + DataConsumptionObserver.getInstance().addEvent(new DataConsumptionEvent(mkcolRequest.getClass().getSimpleName(), + result.isSuccess(), + result.getHttpCode(), + result.getCode().name(), + CommonUtils.getCurrentNetworkType(context), + syncedFolder.getRemoteFolder(), + 0)); + if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS) { if(DbHelper.insertSyncedFolder(syncedFolder, context) >= 0 ) { Log.d(TAG, "Insertion in DB succeed");