Loading app/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.13.1' implementation "androidx.work:work-runtime:2.7.1" implementation 'androidx.test:core:1.4.0' implementation 'com.jakewharton.timber:timber:5.0.1' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' Loading app/src/main/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ <intent-filter> <action android:name="foundation.e.drive.action.FORCE_SYNC" /> <action android:name="foundation.e.drive.action.DUMP_DATABASE"/> <action android:name="foundation.e.drive.action.FULL_LOG_ON_PROD"/> </intent-filter> </receiver> </application> Loading app/src/main/java/foundation/e/drive/EdriveApplication.java +16 −10 Original line number Diff line number Diff line Loading @@ -15,7 +15,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Environment; import android.util.Log; import foundation.e.drive.FileObservers.FileEventListener; import foundation.e.drive.FileObservers.RecursiveFileObserver; Loading @@ -23,6 +22,9 @@ import foundation.e.drive.database.FailedSyncPrefsManager; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.ReleaseTree; import timber.log.Timber; import static timber.log.Timber.DebugTree; /** * Class representing the eDrive application. Loading @@ -31,15 +33,22 @@ import foundation.e.drive.utils.CommonUtils; * @author Vincent Bourgmayer */ public class EdriveApplication extends Application { private static final String TAG = "EdriveApplication"; private RecursiveFileObserver mFileObserver = null; private FileEventListener fileEventListener; @Override public void onCreate() { super.onCreate(); if (BuildConfig.DEBUG) { Timber.plant(new DebugTree()); } else { //Not handled yet Timber.plant(new ReleaseTree()); } Timber.tag("EdriveApplication"); fileEventListener = new FileEventListener(getApplicationContext()); Log.i(TAG, "Starting"); final String pathForObserver = Environment.getExternalStorageDirectory().getAbsolutePath(); mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); Loading @@ -48,7 +57,6 @@ public class EdriveApplication extends Application { CommonUtils.createNotificationChannel(getApplicationContext()); if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) != null) { Log.d(TAG, "Account already registered"); startRecursiveFileObserver(); FailedSyncPrefsManager.getInstance(getApplicationContext()).clearPreferences(); Loading @@ -56,7 +64,7 @@ public class EdriveApplication extends Application { final Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class); startService(SynchronizationServiceIntent); } else { } else { //todo check utility of below code final Account mAccount = CommonUtils.getAccount(getString(R.string.eelo_account_type), AccountManager.get(this)); if (mAccount == null) { return; } Loading @@ -76,21 +84,19 @@ public class EdriveApplication extends Application { if (!mFileObserver.isWatching()) { fileEventListener.bindToSynchronizationService(); mFileObserver.startWatching(); Log.d(TAG, "Starting RecursiveFileObserver on root folder"); } else { Log.w(TAG, "RecursiveFileObserver (for media's root folder) was already running"); Timber.d("Started RecursiveFileObserver on root folder"); } } public void stopRecursiveFileObserver() { mFileObserver.stopWatching(); fileEventListener.unbindFromSynchronizationService(); Log.d(TAG, "RecursiveFileObserver on root folder stops watching "); Timber.d("Stopped RecursiveFileObserver on root folder"); } @Override public void onLowMemory() { super.onLowMemory(); Log.w(TAG, "System is low on memory. Application might get killed by the system."); Timber.w("System is low on memory. Application might get killed by the system."); } } app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +14 −14 Original line number Diff line number Diff line Loading @@ -14,7 +14,6 @@ import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; import android.content.Context; import android.content.Intent; import android.os.FileObserver; import android.util.Log; import com.owncloud.android.lib.resources.files.FileUtils; Loading @@ -27,18 +26,19 @@ import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.SynchronizationServiceConnection; import timber.log.Timber; /** * @author Narinder Rana * @author vincent Bourgmayer */ public class FileEventListener { private static final String TAG = FileEventListener.class.getSimpleName(); private final Context appContext; private final SynchronizationServiceConnection serviceConnection = new SynchronizationServiceConnection(); public FileEventListener(Context applicationContext) { Timber.tag(FileEventListener.class.getSimpleName()); this.appContext = applicationContext; } Loading Loading @@ -66,7 +66,7 @@ public class FileEventListener { handleFileDelete(file); break; case FileObserver.MOVE_SELF: //todo to be able to catch that, we probably need a buffer to catch a succession (MOVE_FROM, MOVE_TO, then MOVE_SELF). Log.d(TAG, file.getAbsolutePath() + " has been moved. Not handled yet"); Timber.d("%s has been moved. Not handled yet", file.getAbsolutePath()); break; default: break; Loading @@ -90,7 +90,7 @@ public class FileEventListener { handleDirectoryDelete(dir); break; case FileObserver.MOVE_SELF: Log.d(TAG, dir.getAbsolutePath() + " has been moved. Not handled yet"); Timber.d("%s has been moved. Not handled yet", dir.getAbsolutePath()); break; default: break; Loading @@ -102,12 +102,12 @@ public class FileEventListener { * @param request */ private void sendSyncRequestToSynchronizationService(SyncRequest request) { Log.d(TAG, "Sending a SyncRequest for " + request.getSyncedFileState().getName()); Timber.d("Sending a SyncRequest for %s", request.getSyncedFileState().getName()); if (serviceConnection.isBoundToSynchronizationService()) { serviceConnection.getSynchronizationService().queueSyncRequest(request); serviceConnection.getSynchronizationService().startSynchronization(); }else{ Log.w(TAG, "Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); Timber.d("Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); } } Loading @@ -117,7 +117,7 @@ public class FileEventListener { * @param directory */ private void handleDirectoryCreate(File directory) { Log.d(TAG, "handleDirectoryCreate(" + directory.getAbsolutePath() + ")"); Timber.d("handleDirectoryCreate( %s )",directory.getAbsolutePath()); final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null) { Loading @@ -133,7 +133,7 @@ public class FileEventListener { */ private void handleDirectoryCloseWrite(File directory) { final String fileLocalPath = CommonUtils.getLocalPath(directory); Log.d(TAG, "handleDirectoryCloseWrite(" + fileLocalPath + ")"); Timber.d("handleDirectoryCloseWrite( %s )",fileLocalPath ); final SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { handleDirectoryCreate(directory); //todo check if really relevant Loading @@ -149,7 +149,7 @@ public class FileEventListener { */ private void handleDirectoryDelete(File directory) { final String fileLocalPath = CommonUtils.getLocalPath(directory); Log.d(TAG, "handleDirectoryDelete("+fileLocalPath+")"); Timber.d("handleDirectoryDelete( %s )", fileLocalPath); SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { //look for parent Loading @@ -173,7 +173,7 @@ public class FileEventListener { */ private void handleFileCloseWrite(File file) { final String fileLocalPath = CommonUtils.getLocalPath(file); Log.d(TAG, "handleFileCloseWrite("+fileLocalPath+")"); Timber.d("handleFileCloseWrite( %s )", fileLocalPath); SyncRequest request = null; SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); Loading @@ -181,7 +181,7 @@ public class FileEventListener { final String parentPath = CommonUtils.getLocalPath(file.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder == null || !parentFolder.isEnabled()) { Log.w(TAG, "Won't send sync request: no parent are known for new file: "+file.getName()); Timber.w("Won't send sync request: no parent are known for new file: %s", file.getName()); return; } int scannableValue = 0; Loading @@ -197,7 +197,7 @@ public class FileEventListener { fileState.setId(storedId); request = new SyncRequest(fileState, UPLOAD); } else { Log.w(TAG, "New File " + file.getName() + " observed but impossible to insert it in DB"); Timber.d("New File %s observed but impossible to insert it in DB", file.getName()); } } else { //File update if (fileState.getScannable() > 1) { Loading @@ -215,7 +215,7 @@ public class FileEventListener { */ private void handleFileDelete(File file) { final String fileLocalPath = CommonUtils.getLocalPath(file); Log.d(TAG, "handleFileDelete("+fileLocalPath+")"); Timber.d("handleFileDelete( %s )",fileLocalPath); final SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { return; //Todo #1: should we call handleDirectoryDelete before to return ? Loading @@ -232,7 +232,7 @@ public class FileEventListener { if (serviceConnection.isBoundToSynchronizationService()) appContext.unbindService(serviceConnection); else Log.w(TAG, "Not bound to SynchronizationService: can't unbind."); Timber.w("Not bound to SynchronizationService: can't unbind."); } public void bindToSynchronizationService(){ Loading app/src/main/java/foundation/e/drive/activity/AccountsActivity.java +4 −4 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.content.ComponentName; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import androidx.appcompat.app.AppCompatActivity; Loading @@ -38,9 +37,9 @@ import foundation.e.drive.databinding.ActivityAccountsBinding; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.widgets.EDriveWidget; import timber.log.Timber; public class AccountsActivity extends AppCompatActivity { private static final String TAG = AccountsActivity.class.getSimpleName(); public static final String NON_OFFICIAL_AVATAR_PATH = "/index.php/avatar/"; private static final String ACCOUNT_SETTINGS = Loading @@ -51,6 +50,7 @@ public class AccountsActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Timber.tag(AccountsActivity.class.getSimpleName()); binding = ActivityAccountsBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); Loading Loading @@ -98,7 +98,7 @@ public class AccountsActivity extends AppCompatActivity { totalShownQuota = CommonUtils.humanReadableByteCountBin(totalQuotaLong); } } catch (NumberFormatException ignored) { Log.i(TAG, "Bad totalQuotaLong " + totalQuota); Timber.i("Bad totalQuotaLong " + totalQuota); } try { Loading @@ -107,7 +107,7 @@ public class AccountsActivity extends AppCompatActivity { usedShownQuota = CommonUtils.humanReadableByteCountBin(usedQuotaLong); } } catch (NumberFormatException ignore) { Log.i(TAG, "Bad usedQuotaLong " + usedQuota); Timber.i("Bad usedQuotaLong " + usedQuota); } binding.plan.setText(getString(R.string.free_plan, totalShownQuota)); Loading Loading
app/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.13.1' implementation "androidx.work:work-runtime:2.7.1" implementation 'androidx.test:core:1.4.0' implementation 'com.jakewharton.timber:timber:5.0.1' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' Loading
app/src/main/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ <intent-filter> <action android:name="foundation.e.drive.action.FORCE_SYNC" /> <action android:name="foundation.e.drive.action.DUMP_DATABASE"/> <action android:name="foundation.e.drive.action.FULL_LOG_ON_PROD"/> </intent-filter> </receiver> </application> Loading
app/src/main/java/foundation/e/drive/EdriveApplication.java +16 −10 Original line number Diff line number Diff line Loading @@ -15,7 +15,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Environment; import android.util.Log; import foundation.e.drive.FileObservers.FileEventListener; import foundation.e.drive.FileObservers.RecursiveFileObserver; Loading @@ -23,6 +22,9 @@ import foundation.e.drive.database.FailedSyncPrefsManager; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.ReleaseTree; import timber.log.Timber; import static timber.log.Timber.DebugTree; /** * Class representing the eDrive application. Loading @@ -31,15 +33,22 @@ import foundation.e.drive.utils.CommonUtils; * @author Vincent Bourgmayer */ public class EdriveApplication extends Application { private static final String TAG = "EdriveApplication"; private RecursiveFileObserver mFileObserver = null; private FileEventListener fileEventListener; @Override public void onCreate() { super.onCreate(); if (BuildConfig.DEBUG) { Timber.plant(new DebugTree()); } else { //Not handled yet Timber.plant(new ReleaseTree()); } Timber.tag("EdriveApplication"); fileEventListener = new FileEventListener(getApplicationContext()); Log.i(TAG, "Starting"); final String pathForObserver = Environment.getExternalStorageDirectory().getAbsolutePath(); mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); Loading @@ -48,7 +57,6 @@ public class EdriveApplication extends Application { CommonUtils.createNotificationChannel(getApplicationContext()); if (prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) != null) { Log.d(TAG, "Account already registered"); startRecursiveFileObserver(); FailedSyncPrefsManager.getInstance(getApplicationContext()).clearPreferences(); Loading @@ -56,7 +64,7 @@ public class EdriveApplication extends Application { final Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class); startService(SynchronizationServiceIntent); } else { } else { //todo check utility of below code final Account mAccount = CommonUtils.getAccount(getString(R.string.eelo_account_type), AccountManager.get(this)); if (mAccount == null) { return; } Loading @@ -76,21 +84,19 @@ public class EdriveApplication extends Application { if (!mFileObserver.isWatching()) { fileEventListener.bindToSynchronizationService(); mFileObserver.startWatching(); Log.d(TAG, "Starting RecursiveFileObserver on root folder"); } else { Log.w(TAG, "RecursiveFileObserver (for media's root folder) was already running"); Timber.d("Started RecursiveFileObserver on root folder"); } } public void stopRecursiveFileObserver() { mFileObserver.stopWatching(); fileEventListener.unbindFromSynchronizationService(); Log.d(TAG, "RecursiveFileObserver on root folder stops watching "); Timber.d("Stopped RecursiveFileObserver on root folder"); } @Override public void onLowMemory() { super.onLowMemory(); Log.w(TAG, "System is low on memory. Application might get killed by the system."); Timber.w("System is low on memory. Application might get killed by the system."); } }
app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +14 −14 Original line number Diff line number Diff line Loading @@ -14,7 +14,6 @@ import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; import android.content.Context; import android.content.Intent; import android.os.FileObserver; import android.util.Log; import com.owncloud.android.lib.resources.files.FileUtils; Loading @@ -27,18 +26,19 @@ import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.SynchronizationServiceConnection; import timber.log.Timber; /** * @author Narinder Rana * @author vincent Bourgmayer */ public class FileEventListener { private static final String TAG = FileEventListener.class.getSimpleName(); private final Context appContext; private final SynchronizationServiceConnection serviceConnection = new SynchronizationServiceConnection(); public FileEventListener(Context applicationContext) { Timber.tag(FileEventListener.class.getSimpleName()); this.appContext = applicationContext; } Loading Loading @@ -66,7 +66,7 @@ public class FileEventListener { handleFileDelete(file); break; case FileObserver.MOVE_SELF: //todo to be able to catch that, we probably need a buffer to catch a succession (MOVE_FROM, MOVE_TO, then MOVE_SELF). Log.d(TAG, file.getAbsolutePath() + " has been moved. Not handled yet"); Timber.d("%s has been moved. Not handled yet", file.getAbsolutePath()); break; default: break; Loading @@ -90,7 +90,7 @@ public class FileEventListener { handleDirectoryDelete(dir); break; case FileObserver.MOVE_SELF: Log.d(TAG, dir.getAbsolutePath() + " has been moved. Not handled yet"); Timber.d("%s has been moved. Not handled yet", dir.getAbsolutePath()); break; default: break; Loading @@ -102,12 +102,12 @@ public class FileEventListener { * @param request */ private void sendSyncRequestToSynchronizationService(SyncRequest request) { Log.d(TAG, "Sending a SyncRequest for " + request.getSyncedFileState().getName()); Timber.d("Sending a SyncRequest for %s", request.getSyncedFileState().getName()); if (serviceConnection.isBoundToSynchronizationService()) { serviceConnection.getSynchronizationService().queueSyncRequest(request); serviceConnection.getSynchronizationService().startSynchronization(); }else{ Log.w(TAG, "Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); Timber.d("Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); } } Loading @@ -117,7 +117,7 @@ public class FileEventListener { * @param directory */ private void handleDirectoryCreate(File directory) { Log.d(TAG, "handleDirectoryCreate(" + directory.getAbsolutePath() + ")"); Timber.d("handleDirectoryCreate( %s )",directory.getAbsolutePath()); final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null) { Loading @@ -133,7 +133,7 @@ public class FileEventListener { */ private void handleDirectoryCloseWrite(File directory) { final String fileLocalPath = CommonUtils.getLocalPath(directory); Log.d(TAG, "handleDirectoryCloseWrite(" + fileLocalPath + ")"); Timber.d("handleDirectoryCloseWrite( %s )",fileLocalPath ); final SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { handleDirectoryCreate(directory); //todo check if really relevant Loading @@ -149,7 +149,7 @@ public class FileEventListener { */ private void handleDirectoryDelete(File directory) { final String fileLocalPath = CommonUtils.getLocalPath(directory); Log.d(TAG, "handleDirectoryDelete("+fileLocalPath+")"); Timber.d("handleDirectoryDelete( %s )", fileLocalPath); SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { //look for parent Loading @@ -173,7 +173,7 @@ public class FileEventListener { */ private void handleFileCloseWrite(File file) { final String fileLocalPath = CommonUtils.getLocalPath(file); Log.d(TAG, "handleFileCloseWrite("+fileLocalPath+")"); Timber.d("handleFileCloseWrite( %s )", fileLocalPath); SyncRequest request = null; SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); Loading @@ -181,7 +181,7 @@ public class FileEventListener { final String parentPath = CommonUtils.getLocalPath(file.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder == null || !parentFolder.isEnabled()) { Log.w(TAG, "Won't send sync request: no parent are known for new file: "+file.getName()); Timber.w("Won't send sync request: no parent are known for new file: %s", file.getName()); return; } int scannableValue = 0; Loading @@ -197,7 +197,7 @@ public class FileEventListener { fileState.setId(storedId); request = new SyncRequest(fileState, UPLOAD); } else { Log.w(TAG, "New File " + file.getName() + " observed but impossible to insert it in DB"); Timber.d("New File %s observed but impossible to insert it in DB", file.getName()); } } else { //File update if (fileState.getScannable() > 1) { Loading @@ -215,7 +215,7 @@ public class FileEventListener { */ private void handleFileDelete(File file) { final String fileLocalPath = CommonUtils.getLocalPath(file); Log.d(TAG, "handleFileDelete("+fileLocalPath+")"); Timber.d("handleFileDelete( %s )",fileLocalPath); final SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { return; //Todo #1: should we call handleDirectoryDelete before to return ? Loading @@ -232,7 +232,7 @@ public class FileEventListener { if (serviceConnection.isBoundToSynchronizationService()) appContext.unbindService(serviceConnection); else Log.w(TAG, "Not bound to SynchronizationService: can't unbind."); Timber.w("Not bound to SynchronizationService: can't unbind."); } public void bindToSynchronizationService(){ Loading
app/src/main/java/foundation/e/drive/activity/AccountsActivity.java +4 −4 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.content.ComponentName; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import androidx.appcompat.app.AppCompatActivity; Loading @@ -38,9 +37,9 @@ import foundation.e.drive.databinding.ActivityAccountsBinding; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.widgets.EDriveWidget; import timber.log.Timber; public class AccountsActivity extends AppCompatActivity { private static final String TAG = AccountsActivity.class.getSimpleName(); public static final String NON_OFFICIAL_AVATAR_PATH = "/index.php/avatar/"; private static final String ACCOUNT_SETTINGS = Loading @@ -51,6 +50,7 @@ public class AccountsActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Timber.tag(AccountsActivity.class.getSimpleName()); binding = ActivityAccountsBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); Loading Loading @@ -98,7 +98,7 @@ public class AccountsActivity extends AppCompatActivity { totalShownQuota = CommonUtils.humanReadableByteCountBin(totalQuotaLong); } } catch (NumberFormatException ignored) { Log.i(TAG, "Bad totalQuotaLong " + totalQuota); Timber.i("Bad totalQuotaLong " + totalQuota); } try { Loading @@ -107,7 +107,7 @@ public class AccountsActivity extends AppCompatActivity { usedShownQuota = CommonUtils.humanReadableByteCountBin(usedQuotaLong); } } catch (NumberFormatException ignore) { Log.i(TAG, "Bad usedQuotaLong " + usedQuota); Timber.i("Bad usedQuotaLong " + usedQuota); } binding.plan.setText(getString(R.string.free_plan, totalShownQuota)); Loading