RefreshFolderOperation.java 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. /*
  2. * Nextcloud - Android Client
  3. *
  4. * SPDX-FileCopyrightText: 2019-2023 Tobias Kaminsky <tobias@kaminsky.me>
  5. * SPDX-FileCopyrightText: 2015 ownCloud Inc.
  6. * SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
  7. * SPDX-License-Identifier: GPL-2.0-only AND AGPL-3.0-or-later
  8. */
  9. package com.owncloud.android.operations;
  10. import android.content.Context;
  11. import android.content.Intent;
  12. import com.google.common.collect.Maps;
  13. import com.google.gson.Gson;
  14. import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainRemoteOperation;
  15. import com.nextcloud.client.account.User;
  16. import com.nextcloud.common.NextcloudClient;
  17. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  18. import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
  19. import com.owncloud.android.datamodel.FileDataStorageManager;
  20. import com.owncloud.android.datamodel.OCFile;
  21. import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
  22. import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile;
  23. import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
  24. import com.owncloud.android.lib.common.DirectEditing;
  25. import com.owncloud.android.lib.common.OwnCloudClient;
  26. import com.owncloud.android.lib.common.OwnCloudClientFactory;
  27. import com.owncloud.android.lib.common.UserInfo;
  28. import com.owncloud.android.lib.common.accounts.AccountUtils;
  29. import com.owncloud.android.lib.common.operations.RemoteOperation;
  30. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  31. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  32. import com.owncloud.android.lib.common.utils.Log_OC;
  33. import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
  34. import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
  35. import com.owncloud.android.lib.resources.files.model.RemoteFile;
  36. import com.owncloud.android.lib.resources.shares.GetSharesForFileRemoteOperation;
  37. import com.owncloud.android.lib.resources.shares.OCShare;
  38. import com.owncloud.android.lib.resources.shares.ShareType;
  39. import com.owncloud.android.lib.resources.status.E2EVersion;
  40. import com.owncloud.android.lib.resources.users.GetPredefinedStatusesRemoteOperation;
  41. import com.owncloud.android.lib.resources.users.PredefinedStatus;
  42. import com.owncloud.android.syncadapter.FileSyncAdapter;
  43. import com.owncloud.android.utils.DataHolderUtil;
  44. import com.owncloud.android.utils.EncryptionUtils;
  45. import com.owncloud.android.utils.FileStorageUtils;
  46. import com.owncloud.android.utils.MimeType;
  47. import com.owncloud.android.utils.MimeTypeUtil;
  48. import com.owncloud.android.utils.theme.CapabilityUtils;
  49. import java.util.ArrayList;
  50. import java.util.HashMap;
  51. import java.util.List;
  52. import java.util.Map;
  53. import java.util.Vector;
  54. import androidx.annotation.NonNull;
  55. import androidx.annotation.Nullable;
  56. import androidx.localbroadcastmanager.content.LocalBroadcastManager;
  57. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  58. import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
  59. /**
  60. * Remote operation performing the synchronization of the list of files contained in a folder identified with its remote
  61. * path. Fetches the list and properties of the files contained in the given folder, including their properties, and
  62. * updates the local database with them. Does NOT enter in the child folders to synchronize their contents also.
  63. */
  64. @SuppressWarnings("PMD.AvoidDuplicateLiterals")
  65. public class RefreshFolderOperation extends RemoteOperation {
  66. private static final String TAG = RefreshFolderOperation.class.getSimpleName();
  67. public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED =
  68. RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
  69. public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED =
  70. RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
  71. /**
  72. * Time stamp for the synchronization process in progress
  73. */
  74. private long mCurrentSyncTime;
  75. /**
  76. * Remote folder to synchronize
  77. */
  78. private OCFile mLocalFolder;
  79. /**
  80. * Access to the local database
  81. */
  82. private FileDataStorageManager mStorageManager;
  83. /**
  84. * Account where the file to synchronize belongs
  85. */
  86. private User user;
  87. /**
  88. * Android context; necessary to send requests to the download service
  89. */
  90. private Context mContext;
  91. /**
  92. * Files and folders contained in the synchronized folder after a successful operation
  93. */
  94. private List<OCFile> mChildren;
  95. /**
  96. * Counter of conflicts found between local and remote files
  97. */
  98. private int mConflictsFound;
  99. /**
  100. * Counter of failed operations in synchronization of kept-in-sync files
  101. */
  102. private int mFailsInKeptInSyncFound;
  103. /**
  104. * Map of remote and local paths to files that where locally stored in a location out of the ownCloud folder and
  105. * couldn't be copied automatically into it
  106. **/
  107. private Map<String, String> mForgottenLocalFiles;
  108. /**
  109. * 'True' means that this operation is part of a full account synchronization
  110. */
  111. private boolean mSyncFullAccount;
  112. /**
  113. * 'True' means that the remote folder changed and should be fetched
  114. */
  115. private boolean mRemoteFolderChanged;
  116. /**
  117. * 'True' means that Etag will be ignored
  118. */
  119. private boolean mIgnoreETag;
  120. /**
  121. * 'True' means that no share and no capabilities will be updated
  122. */
  123. private boolean mOnlyFileMetadata;
  124. private List<SynchronizeFileOperation> mFilesToSyncContents;
  125. // this will be used for every file when 'folder synchronization' replaces 'folder download'
  126. /**
  127. * Creates a new instance of {@link RefreshFolderOperation}.
  128. *
  129. * @param folder Folder to synchronize.
  130. * @param currentSyncTime Time stamp for the synchronization process in progress.
  131. * @param syncFullAccount 'True' means that this operation is part of a full account synchronization.
  132. * @param ignoreETag 'True' means that the content of the remote folder should be fetched and updated even
  133. * though the 'eTag' did not change.
  134. * @param dataStorageManager Interface with the local database.
  135. * @param user ownCloud account where the folder is located.
  136. * @param context Application context.
  137. */
  138. public RefreshFolderOperation(OCFile folder,
  139. long currentSyncTime,
  140. boolean syncFullAccount,
  141. boolean ignoreETag,
  142. FileDataStorageManager dataStorageManager,
  143. User user,
  144. Context context) {
  145. mLocalFolder = folder;
  146. mCurrentSyncTime = currentSyncTime;
  147. mSyncFullAccount = syncFullAccount;
  148. mStorageManager = dataStorageManager;
  149. this.user = user;
  150. mContext = context;
  151. mForgottenLocalFiles = new HashMap<>();
  152. mRemoteFolderChanged = false;
  153. mIgnoreETag = ignoreETag;
  154. mOnlyFileMetadata = false;
  155. mFilesToSyncContents = new Vector<>();
  156. }
  157. public RefreshFolderOperation(OCFile folder,
  158. long currentSyncTime,
  159. boolean syncFullAccount,
  160. boolean ignoreETag,
  161. boolean onlyFileMetadata,
  162. FileDataStorageManager dataStorageManager,
  163. User user,
  164. Context context) {
  165. mLocalFolder = folder;
  166. mCurrentSyncTime = currentSyncTime;
  167. mSyncFullAccount = syncFullAccount;
  168. mStorageManager = dataStorageManager;
  169. this.user = user;
  170. mContext = context;
  171. mForgottenLocalFiles = new HashMap<>();
  172. mRemoteFolderChanged = false;
  173. mIgnoreETag = ignoreETag;
  174. mOnlyFileMetadata = onlyFileMetadata;
  175. mFilesToSyncContents = new Vector<>();
  176. }
  177. public int getConflictsFound() {
  178. return mConflictsFound;
  179. }
  180. public int getFailsInKeptInSyncFound() {
  181. return mFailsInKeptInSyncFound;
  182. }
  183. public Map<String, String> getForgottenLocalFiles() {
  184. return mForgottenLocalFiles;
  185. }
  186. /**
  187. * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is
  188. * complete.
  189. *
  190. * @return List of files and folders contained in the synchronized folder.
  191. */
  192. public List<OCFile> getChildren() {
  193. return mChildren;
  194. }
  195. /**
  196. * Performs the synchronization.
  197. * <p>
  198. * {@inheritDoc}
  199. */
  200. @Override
  201. protected RemoteOperationResult run(OwnCloudClient client) {
  202. RemoteOperationResult result;
  203. mFailsInKeptInSyncFound = 0;
  204. mConflictsFound = 0;
  205. mForgottenLocalFiles.clear();
  206. if (OCFile.ROOT_PATH.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount && !mOnlyFileMetadata) {
  207. updateOCVersion(client);
  208. updateUserProfile();
  209. }
  210. result = checkForChanges(client);
  211. if (result.isSuccess()) {
  212. if (mRemoteFolderChanged) {
  213. // TODO catch IllegalStateException, show properly to user
  214. result = fetchAndSyncRemoteFolder(client);
  215. } else {
  216. mChildren = mStorageManager.getFolderContent(mLocalFolder, false);
  217. }
  218. if (result.isSuccess()) {
  219. // request for the synchronization of KEPT-IN-SYNC file contents
  220. startContentSynchronizations(mFilesToSyncContents);
  221. } else {
  222. mLocalFolder.setEtag("");
  223. }
  224. mLocalFolder.setLastSyncDateForData(System.currentTimeMillis());
  225. mStorageManager.saveFile(mLocalFolder);
  226. }
  227. if (!mSyncFullAccount && mRemoteFolderChanged) {
  228. sendLocalBroadcast(
  229. EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
  230. );
  231. }
  232. if (result.isSuccess() && !mSyncFullAccount && !mOnlyFileMetadata) {
  233. refreshSharesForFolder(client); // share result is ignored
  234. }
  235. if (!mSyncFullAccount) {
  236. sendLocalBroadcast(
  237. EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
  238. );
  239. }
  240. return result;
  241. }
  242. private void updateOCVersion(OwnCloudClient client) {
  243. UpdateOCVersionOperation update = new UpdateOCVersionOperation(user, mContext);
  244. RemoteOperationResult result = update.execute(client);
  245. if (result.isSuccess()) {
  246. // Update Capabilities for this account
  247. updateCapabilities();
  248. }
  249. }
  250. private void updateUserProfile() {
  251. try {
  252. NextcloudClient nextcloudClient = OwnCloudClientFactory.createNextcloudClient(user, mContext);
  253. RemoteOperationResult<UserInfo> result = new GetUserProfileOperation(mStorageManager).execute(nextcloudClient);
  254. if (!result.isSuccess()) {
  255. Log_OC.w(TAG, "Couldn't update user profile from server");
  256. } else {
  257. Log_OC.i(TAG, "Got display name: " + result.getResultData());
  258. }
  259. } catch (AccountUtils.AccountNotFoundException | NullPointerException e) {
  260. Log_OC.e(this, "Error updating profile", e);
  261. }
  262. }
  263. private void updateCapabilities() {
  264. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(mContext);
  265. String oldDirectEditingEtag = arbitraryDataProvider.getValue(user,
  266. ArbitraryDataProvider.DIRECT_EDITING_ETAG);
  267. RemoteOperationResult result = new GetCapabilitiesOperation(mStorageManager).execute(mContext);
  268. if (result.isSuccess()) {
  269. String newDirectEditingEtag = mStorageManager.getCapability(user.getAccountName()).getDirectEditingEtag();
  270. if (!oldDirectEditingEtag.equalsIgnoreCase(newDirectEditingEtag)) {
  271. updateDirectEditing(arbitraryDataProvider, newDirectEditingEtag);
  272. }
  273. updatePredefinedStatus(arbitraryDataProvider);
  274. } else {
  275. Log_OC.w(TAG, "Update Capabilities unsuccessfully");
  276. }
  277. }
  278. private void updateDirectEditing(ArbitraryDataProvider arbitraryDataProvider, String newDirectEditingEtag) {
  279. RemoteOperationResult<DirectEditing> result = new DirectEditingObtainRemoteOperation().execute(user,
  280. mContext);
  281. if (result.isSuccess()) {
  282. DirectEditing directEditing = result.getResultData();
  283. String json = new Gson().toJson(directEditing);
  284. arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), ArbitraryDataProvider.DIRECT_EDITING, json);
  285. } else {
  286. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), ArbitraryDataProvider.DIRECT_EDITING);
  287. }
  288. arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
  289. ArbitraryDataProvider.DIRECT_EDITING_ETAG,
  290. newDirectEditingEtag);
  291. }
  292. private void updatePredefinedStatus(ArbitraryDataProvider arbitraryDataProvider) {
  293. NextcloudClient client;
  294. try {
  295. client = OwnCloudClientFactory.createNextcloudClient(user, mContext);
  296. } catch (AccountUtils.AccountNotFoundException | NullPointerException e) {
  297. Log_OC.e(this, "Update of predefined status not possible!");
  298. return;
  299. }
  300. RemoteOperationResult<ArrayList<PredefinedStatus>> result =
  301. new GetPredefinedStatusesRemoteOperation().execute(client);
  302. if (result.isSuccess()) {
  303. ArrayList<PredefinedStatus> predefinedStatuses = result.getResultData();
  304. String json = new Gson().toJson(predefinedStatuses);
  305. arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), ArbitraryDataProvider.PREDEFINED_STATUS, json);
  306. } else {
  307. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), ArbitraryDataProvider.PREDEFINED_STATUS);
  308. }
  309. }
  310. private RemoteOperationResult checkForChanges(OwnCloudClient client) {
  311. mRemoteFolderChanged = true;
  312. RemoteOperationResult result;
  313. String remotePath = mLocalFolder.getRemotePath();
  314. Log_OC.d(TAG, "Checking changes in " + user.getAccountName() + remotePath);
  315. // remote request
  316. result = new ReadFileRemoteOperation(remotePath).execute(client);
  317. if (result.isSuccess()) {
  318. OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
  319. if (!mIgnoreETag) {
  320. // check if remote and local folder are different
  321. String remoteFolderETag = remoteFolder.getEtag();
  322. if (remoteFolderETag != null) {
  323. mRemoteFolderChanged = !(remoteFolderETag.equalsIgnoreCase(mLocalFolder.getEtag()));
  324. } else {
  325. Log_OC.e(TAG, "Checked " + user.getAccountName() + remotePath + ": No ETag received from server");
  326. }
  327. }
  328. result = new RemoteOperationResult(ResultCode.OK);
  329. Log_OC.i(TAG, "Checked " + user.getAccountName() + remotePath + " : " +
  330. (mRemoteFolderChanged ? "changed" : "not changed"));
  331. } else {
  332. // check failed
  333. if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
  334. removeLocalFolder();
  335. }
  336. if (result.isException()) {
  337. Log_OC.e(TAG, "Checked " + user.getAccountName() + remotePath + " : " +
  338. result.getLogMessage(), result.getException());
  339. } else {
  340. Log_OC.e(TAG, "Checked " + user.getAccountName() + remotePath + " : " +
  341. result.getLogMessage());
  342. }
  343. }
  344. return result;
  345. }
  346. private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
  347. String remotePath = mLocalFolder.getRemotePath();
  348. RemoteOperationResult result = new ReadFolderRemoteOperation(remotePath).execute(client);
  349. Log_OC.d(TAG, "Refresh folder " + user.getAccountName() + remotePath);
  350. Log_OC.d(TAG, "Refresh folder with remote id" + mLocalFolder.getRemoteId());
  351. if (result.isSuccess()) {
  352. synchronizeData(result.getData());
  353. if (mConflictsFound > 0 || mFailsInKeptInSyncFound > 0) {
  354. result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
  355. // should be a different result code, but will do the job
  356. }
  357. } else {
  358. if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
  359. removeLocalFolder();
  360. }
  361. }
  362. return result;
  363. }
  364. private void removeLocalFolder() {
  365. if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
  366. String currentSavePath = FileStorageUtils.getSavePath(user.getAccountName());
  367. mStorageManager.removeFolder(
  368. mLocalFolder,
  369. true,
  370. mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath)
  371. );
  372. }
  373. }
  374. /**
  375. * Synchronizes the data retrieved from the server about the contents of the target folder with the current data in
  376. * the local database.
  377. * <p>
  378. * Grants that mChildren is updated with fresh data after execution.
  379. *
  380. * @param folderAndFiles Remote folder and children files in Folder
  381. */
  382. private void synchronizeData(List<Object> folderAndFiles) {
  383. // get 'fresh data' from the database
  384. mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath());
  385. // parse data from remote folder
  386. OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) folderAndFiles.get(0));
  387. remoteFolder.setParentId(mLocalFolder.getParentId());
  388. remoteFolder.setFileId(mLocalFolder.getFileId());
  389. Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + " changed - starting update of local data ");
  390. List<OCFile> updatedFiles = new ArrayList<>(folderAndFiles.size() - 1);
  391. mFilesToSyncContents.clear();
  392. // if local folder is encrypted, download fresh metadata
  393. boolean encryptedAncestor = FileStorageUtils.checkEncryptionStatus(mLocalFolder, mStorageManager);
  394. mLocalFolder.setEncrypted(encryptedAncestor);
  395. // update permission
  396. mLocalFolder.setPermissions(remoteFolder.getPermissions());
  397. // update richWorkspace
  398. mLocalFolder.setRichWorkspace(remoteFolder.getRichWorkspace());
  399. // update eTag
  400. mLocalFolder.setEtag(remoteFolder.getEtag());
  401. // update size
  402. mLocalFolder.setFileLength(remoteFolder.getFileLength());
  403. Object object = null;
  404. if (mLocalFolder.isEncrypted()) {
  405. object = getDecryptedFolderMetadata(encryptedAncestor,
  406. mLocalFolder,
  407. getClient(),
  408. user,
  409. mContext);
  410. }
  411. if (CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) {
  412. if (encryptedAncestor && object == null) {
  413. throw new IllegalStateException("metadata is null!");
  414. }
  415. }
  416. // get current data about local contents of the folder to synchronize
  417. Map<String, OCFile> localFilesMap;
  418. E2EVersion e2EVersion;
  419. if (object instanceof DecryptedFolderMetadataFileV1) {
  420. e2EVersion = E2EVersion.V1_2;
  421. localFilesMap = prefillLocalFilesMap((DecryptedFolderMetadataFileV1) object,
  422. mStorageManager.getFolderContent(mLocalFolder, false));
  423. } else {
  424. e2EVersion = E2EVersion.V2_0;
  425. localFilesMap = prefillLocalFilesMap((DecryptedFolderMetadataFile) object,
  426. mStorageManager.getFolderContent(mLocalFolder, false));
  427. // update counter
  428. if (object != null) {
  429. mLocalFolder.setE2eCounter(((DecryptedFolderMetadataFile) object).getMetadata().getCounter());
  430. }
  431. }
  432. // loop to update every child
  433. OCFile remoteFile;
  434. OCFile localFile;
  435. OCFile updatedFile;
  436. RemoteFile remote;
  437. for (int i = 1; i < folderAndFiles.size(); i++) {
  438. /// new OCFile instance with the data from the server
  439. remote = (RemoteFile) folderAndFiles.get(i);
  440. remoteFile = FileStorageUtils.fillOCFile(remote);
  441. // new OCFile instance to merge fresh data from server with local state
  442. updatedFile = FileStorageUtils.fillOCFile(remote);
  443. updatedFile.setParentId(mLocalFolder.getFileId());
  444. // retrieve local data for the read file
  445. localFile = localFilesMap.remove(remoteFile.getRemotePath());
  446. // TODO better implementation is needed
  447. if (localFile == null) {
  448. localFile = mStorageManager.getFileByPath(updatedFile.getRemotePath());
  449. }
  450. // add to updatedFile data about LOCAL STATE (not existing in server)
  451. updatedFile.setLastSyncDateForProperties(mCurrentSyncTime);
  452. // keep thumbnail info
  453. if (!updatedFile.isUpdateThumbnailNeeded() && localFile != null && localFile.getImageDimension() != null) {
  454. updatedFile.setImageDimension(localFile.getImageDimension());
  455. }
  456. // add to updatedFile data from local and remote file
  457. setLocalFileDataOnUpdatedFile(remoteFile, localFile, updatedFile, mRemoteFolderChanged);
  458. // check and fix, if needed, local storage path
  459. FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());
  460. // update file name for encrypted files
  461. if (e2EVersion == E2EVersion.V1_2) {
  462. updateFileNameForEncryptedFileV1(mStorageManager,
  463. (DecryptedFolderMetadataFileV1) object,
  464. updatedFile);
  465. } else {
  466. updateFileNameForEncryptedFile(mStorageManager,
  467. (DecryptedFolderMetadataFile) object,
  468. updatedFile);
  469. if (localFile != null) {
  470. updatedFile.setE2eCounter(localFile.getE2eCounter());
  471. }
  472. }
  473. // we parse content, so either the folder itself or its direct parent (which we check) must be encrypted
  474. boolean encrypted = updatedFile.isEncrypted() || mLocalFolder.isEncrypted();
  475. updatedFile.setEncrypted(encrypted);
  476. updatedFiles.add(updatedFile);
  477. }
  478. // save updated contents in local database
  479. // update file name for encrypted files
  480. if (e2EVersion == E2EVersion.V1_2) {
  481. updateFileNameForEncryptedFileV1(mStorageManager,
  482. (DecryptedFolderMetadataFileV1) object,
  483. mLocalFolder);
  484. } else {
  485. updateFileNameForEncryptedFile(mStorageManager,
  486. (DecryptedFolderMetadataFile) object,
  487. mLocalFolder);
  488. }
  489. mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
  490. mChildren = updatedFiles;
  491. }
  492. @Nullable
  493. public static Object getDecryptedFolderMetadata(boolean encryptedAncestor,
  494. OCFile localFolder,
  495. OwnCloudClient client,
  496. User user,
  497. Context context) {
  498. Object metadata;
  499. if (encryptedAncestor) {
  500. metadata = EncryptionUtils.downloadFolderMetadata(localFolder, client, context, user);
  501. } else {
  502. metadata = null;
  503. }
  504. return metadata;
  505. }
  506. @SuppressFBWarnings("CE")
  507. private static void setMimeTypeAndDecryptedRemotePath(OCFile updatedFile, FileDataStorageManager storageManager, String decryptedFileName, String mimetype) {
  508. OCFile parentFile = storageManager.getFileById(updatedFile.getParentId());
  509. if (parentFile == null) {
  510. throw new NullPointerException("parentFile cannot be null");
  511. }
  512. String decryptedRemotePath;
  513. if (decryptedFileName != null) {
  514. decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
  515. } else {
  516. decryptedRemotePath = parentFile.getRemotePath() + updatedFile.getFileName();
  517. }
  518. if (updatedFile.isFolder()) {
  519. decryptedRemotePath += "/";
  520. }
  521. updatedFile.setDecryptedRemotePath(decryptedRemotePath);
  522. if (mimetype == null || mimetype.isEmpty()) {
  523. if (updatedFile.isFolder()) {
  524. updatedFile.setMimeType(MimeType.DIRECTORY);
  525. } else {
  526. updatedFile.setMimeType("application/octet-stream");
  527. }
  528. } else {
  529. updatedFile.setMimeType(mimetype);
  530. }
  531. }
  532. public static void updateFileNameForEncryptedFileV1(FileDataStorageManager storageManager,
  533. @NonNull DecryptedFolderMetadataFileV1 metadata,
  534. OCFile updatedFile) {
  535. try {
  536. String decryptedFileName;
  537. String mimetype;
  538. if (updatedFile.isFolder()) {
  539. decryptedFileName = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted().getFilename();
  540. mimetype = MimeType.DIRECTORY;
  541. } else {
  542. com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile decryptedFile =
  543. metadata.getFiles().get(updatedFile.getFileName());
  544. if (decryptedFile == null) {
  545. throw new NullPointerException("decryptedFile cannot be null");
  546. }
  547. decryptedFileName = decryptedFile.getEncrypted().getFilename();
  548. mimetype = decryptedFile.getEncrypted().getMimetype();
  549. }
  550. setMimeTypeAndDecryptedRemotePath(updatedFile, storageManager, decryptedFileName, mimetype);
  551. } catch (NullPointerException e) {
  552. Log_OC.e(TAG, "DecryptedMetadata for file " + updatedFile.getFileId() + " not found!");
  553. }
  554. }
  555. public static void updateFileNameForEncryptedFile(FileDataStorageManager storageManager,
  556. @NonNull DecryptedFolderMetadataFile metadata,
  557. OCFile updatedFile) {
  558. try {
  559. String decryptedFileName;
  560. String mimetype;
  561. if (updatedFile.isFolder()) {
  562. decryptedFileName = metadata.getMetadata().getFolders().get(updatedFile.getFileName());
  563. mimetype = MimeType.DIRECTORY;
  564. } else {
  565. DecryptedFile decryptedFile = metadata.getMetadata().getFiles().get(updatedFile.getFileName());
  566. if (decryptedFile == null) {
  567. throw new NullPointerException("decryptedFile cannot be null");
  568. }
  569. decryptedFileName = decryptedFile.getFilename();
  570. mimetype = decryptedFile.getMimetype();
  571. }
  572. setMimeTypeAndDecryptedRemotePath(updatedFile, storageManager, decryptedFileName, mimetype);
  573. } catch (NullPointerException e) {
  574. Log_OC.e(TAG, "DecryptedMetadata for file " + updatedFile.getFileId() + " not found!");
  575. }
  576. }
  577. private void setLocalFileDataOnUpdatedFile(OCFile remoteFile, OCFile localFile, OCFile updatedFile, boolean remoteFolderChanged) {
  578. if (localFile != null) {
  579. updatedFile.setFileId(localFile.getFileId());
  580. updatedFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
  581. updatedFile.setModificationTimestampAtLastSyncForData(
  582. localFile.getModificationTimestampAtLastSyncForData()
  583. );
  584. if (localFile.isEncrypted()) {
  585. if (mLocalFolder.getStoragePath() == null) {
  586. updatedFile.setStoragePath(FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mLocalFolder) +
  587. localFile.getFileName());
  588. } else {
  589. updatedFile.setStoragePath(mLocalFolder.getStoragePath() +
  590. PATH_SEPARATOR +
  591. localFile.getFileName());
  592. }
  593. } else {
  594. updatedFile.setStoragePath(localFile.getStoragePath());
  595. }
  596. // eTag will not be updated unless file CONTENTS are synchronized
  597. if (!updatedFile.isFolder() && localFile.isDown() &&
  598. !updatedFile.getEtag().equals(localFile.getEtag())) {
  599. updatedFile.setEtagInConflict(updatedFile.getEtag());
  600. }
  601. updatedFile.setEtag(localFile.getEtag());
  602. if (updatedFile.isFolder()) {
  603. updatedFile.setFileLength(remoteFile.getFileLength());
  604. updatedFile.setMountType(remoteFile.getMountType());
  605. } else if (remoteFolderChanged && MimeTypeUtil.isImage(remoteFile) &&
  606. remoteFile.getModificationTimestamp() !=
  607. localFile.getModificationTimestamp()) {
  608. updatedFile.setUpdateThumbnailNeeded(true);
  609. Log_OC.d(TAG, "Image " + remoteFile.getFileName() + " updated on the server");
  610. }
  611. updatedFile.setSharedViaLink(localFile.isSharedViaLink());
  612. updatedFile.setSharedWithSharee(localFile.isSharedWithSharee());
  613. } else {
  614. // remote eTag will not be updated unless file CONTENTS are synchronized
  615. updatedFile.setEtag("");
  616. }
  617. // eTag on Server is used for thumbnail validation
  618. updatedFile.setEtagOnServer(remoteFile.getEtag());
  619. }
  620. @NonNull
  621. @SuppressFBWarnings("OCP")
  622. public static Map<String, OCFile> prefillLocalFilesMap(Object metadata, List<OCFile> localFiles) {
  623. Map<String, OCFile> localFilesMap = Maps.newHashMapWithExpectedSize(localFiles.size());
  624. for (OCFile file : localFiles) {
  625. String remotePath = file.getRemotePath();
  626. if (metadata != null) {
  627. remotePath = file.getParentRemotePath() + file.getEncryptedFileName();
  628. if (file.isFolder() && !remotePath.endsWith(PATH_SEPARATOR)) {
  629. remotePath = remotePath + PATH_SEPARATOR;
  630. }
  631. }
  632. localFilesMap.put(remotePath, file);
  633. }
  634. return localFilesMap;
  635. }
  636. /**
  637. * Performs a list of synchronization operations, determining if a download or upload is needed or if exists
  638. * conflict due to changes both in local and remote contents of the each file.
  639. * <p>
  640. * If download or upload is needed, request the operation to the corresponding service and goes on.
  641. *
  642. * @param filesToSyncContents Synchronization operations to execute.
  643. */
  644. private void startContentSynchronizations(List<SynchronizeFileOperation> filesToSyncContents) {
  645. RemoteOperationResult contentsResult;
  646. for (SynchronizeFileOperation op : filesToSyncContents) {
  647. contentsResult = op.execute(mContext); // async
  648. if (!contentsResult.isSuccess()) {
  649. if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
  650. mConflictsFound++;
  651. } else {
  652. mFailsInKeptInSyncFound++;
  653. if (contentsResult.getException() != null) {
  654. Log_OC.e(TAG, "Error while synchronizing favourites : "
  655. + contentsResult.getLogMessage(), contentsResult.getException());
  656. } else {
  657. Log_OC.e(TAG, "Error while synchronizing favourites : "
  658. + contentsResult.getLogMessage());
  659. }
  660. }
  661. } // won't let these fails break the synchronization process
  662. }
  663. }
  664. /**
  665. * Syncs the Share resources for the files contained in the folder refreshed (children, not deeper descendants).
  666. *
  667. * @param client Handler of a session with an OC server.
  668. * @return The result of the remote operation retrieving the Share resources in the folder refreshed by the
  669. * operation.
  670. */
  671. private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
  672. RemoteOperationResult result;
  673. // remote request
  674. GetSharesForFileRemoteOperation operation =
  675. new GetSharesForFileRemoteOperation(mLocalFolder.getRemotePath(), true, true);
  676. result = operation.execute(client);
  677. if (result.isSuccess()) {
  678. // update local database
  679. ArrayList<OCShare> shares = new ArrayList<>();
  680. OCShare share;
  681. for (Object obj : result.getData()) {
  682. share = (OCShare) obj;
  683. if (ShareType.NO_SHARED != share.getShareType()) {
  684. shares.add(share);
  685. }
  686. }
  687. mStorageManager.saveSharesInFolder(shares, mLocalFolder);
  688. }
  689. return result;
  690. }
  691. /**
  692. * Sends a message to any application component interested in the progress of the synchronization.
  693. *
  694. * @param event broadcast event (Intent Action)
  695. * @param dirRemotePath Remote path of a folder that was just synchronized (with or without success)
  696. * @param result remote operation result
  697. */
  698. private void sendLocalBroadcast(String event, String dirRemotePath, RemoteOperationResult result) {
  699. Log_OC.d(TAG, "Send broadcast " + event);
  700. Intent intent = new Intent(event);
  701. intent.putExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME, user.getAccountName());
  702. if (dirRemotePath != null) {
  703. intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath);
  704. }
  705. DataHolderUtil dataHolderUtil = DataHolderUtil.getInstance();
  706. String dataHolderItemId = dataHolderUtil.nextItemId();
  707. dataHolderUtil.save(dataHolderItemId, result);
  708. intent.putExtra(FileSyncAdapter.EXTRA_RESULT, dataHolderItemId);
  709. intent.setPackage(mContext.getPackageName());
  710. LocalBroadcastManager.getInstance(mContext.getApplicationContext()).sendBroadcast(intent);
  711. }
  712. }