X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/blobdiff_plain/67eb921007efb1c9204e4b69fb85110fc23bf51f..8f1566a21c1dfdc562d701c5514ee616509fcb65:/src/com/owncloud/android/operations/SynchronizeFolderOperation.java?ds=inline diff --git a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java index 36b9b229..3c1a64f6 100644 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -1,10 +1,9 @@ /* ownCloud Android client application - * Copyright (C) 2012 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -18,22 +17,29 @@ package com.owncloud.android.operations; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Vector; import org.apache.http.HttpStatus; -import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; import android.accounts.Account; -import android.content.Intent; -import android.util.Log; +import android.content.Context; -import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileDownloader; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.utils.FileStorageUtils; import eu.alefzero.webdav.WebdavClient; import eu.alefzero.webdav.WebdavEntry; @@ -47,7 +53,7 @@ import eu.alefzero.webdav.WebdavUtils; */ public class SynchronizeFolderOperation extends RemoteOperation { - private static final String TAG = SynchronizeFolderOperation.class.getCanonicalName(); + private static final String TAG = SynchronizeFolderOperation.class.getSimpleName(); /** Remote folder to synchronize */ private String mRemotePath; @@ -59,135 +65,288 @@ public class SynchronizeFolderOperation extends RemoteOperation { private long mParentId; /** Access to the local database */ - private FileDataStorageManager mStorageManager; + private DataStorageManager mStorageManager; /** Account where the file to synchronize belongs */ private Account mAccount; + /** Android context; necessary to send requests to the download service; maybe something to refactor */ + private Context mContext; - SynchronizeFolderOperation(String remotePath, long currentSyncTime, long parentId, FileDataStorageManager storageManager, Account account) { + /** Files and folders contained in the synchronized folder */ + private List mChildren; + + private int mConflictsFound; + + private int mFailsInFavouritesFound; + + private Map mForgottenLocalFiles; + + + public SynchronizeFolderOperation( String remotePath, + long currentSyncTime, + long parentId, + DataStorageManager dataStorageManager, + Account account, + Context context ) { mRemotePath = remotePath; mCurrentSyncTime = currentSyncTime; mParentId = parentId; - mStorageManager = storageManager; + mStorageManager = dataStorageManager; mAccount = account; + mContext = context; + mForgottenLocalFiles = new HashMap(); + } + + + public int getConflictsFound() { + return mConflictsFound; + } + + public int getFailsInFavouritesFound() { + return mFailsInFavouritesFound; + } + + public Map getForgottenLocalFiles() { + return mForgottenLocalFiles; + } + + /** + * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is complete. + * + * @return List of files and folders contained in the synchronized folder. + */ + public List getChildren() { + return mChildren; } @Override protected RemoteOperationResult run(WebdavClient client) { RemoteOperationResult result = null; + mFailsInFavouritesFound = 0; + mConflictsFound = 0; + mForgottenLocalFiles.clear(); // code before in FileSyncAdapter.fetchData PropFindMethod query = null; - Vector children = null; try { - Log.d(TAG, "Fetching files in " + mRemotePath); + Log_OC.d(TAG, "Synchronizing " + mAccount.name + ", fetching files in " + mRemotePath); // remote request query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath)); int status = client.executeMethod(query); - if (isSuccess(status)) { - + // check and process response - /// TODO take into account all the possible status per child-resource + if (isMultiStatus(status)) { MultiStatus resp = query.getResponseBodyAsMultiStatus(); - // reading files + // synchronize properties of the parent folder, if necessary + if (mParentId == DataStorageManager.ROOT_PARENT_ID) { + WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath()); + OCFile parent = fillOCFile(we); + mStorageManager.saveFile(parent); + mParentId = parent.getFileId(); + } + + // read contents in folder List updatedFiles = new Vector(resp.getResponses().length - 1); + List filesToSyncContents = new Vector(); for (int i = 1; i < resp.getResponses().length; ++i) { + /// new OCFile instance with the data from the server WebdavEntry we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath()); OCFile file = fillOCFile(we); - file.setParentId(mParentId); + + /// set data about local state, keeping unchanged former data if existing + file.setLastSyncDateForProperties(mCurrentSyncTime); OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath()); if (oldFile != null) { - if (oldFile.keepInSync() && file.getModificationTimestamp() > oldFile.getModificationTimestamp()) { - requestContentDownload(); - } file.setKeepInSync(oldFile.keepInSync()); + file.setLastSyncDateForData(oldFile.getLastSyncDateForData()); + file.setModificationTimestampAtLastSyncForData(oldFile.getModificationTimestampAtLastSyncForData()); // must be kept unchanged when the file contents are not updated + checkAndFixForeignStoragePath(oldFile); + file.setStoragePath(oldFile.getStoragePath()); + } + + /// scan default location if local copy of file is not linked in OCFile instance + if (file.getStoragePath() == null && !file.isDirectory()) { + File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); + if (f.exists()) { + file.setStoragePath(f.getAbsolutePath()); + file.setLastSyncDateForData(f.lastModified()); + } + } + + /// prepare content synchronization for kept-in-sync files + if (file.keepInSync()) { + SynchronizeFileOperation operation = new SynchronizeFileOperation( oldFile, + file, + mStorageManager, + mAccount, + true, + false, + mContext + ); + filesToSyncContents.add(operation); } updatedFiles.add(file); } - - - // save updated files in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) + + // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) mStorageManager.saveFiles(updatedFiles); - + // request for the synchronization of files AFTER saving last properties + SynchronizeFileOperation op = null; + RemoteOperationResult contentsResult = null; + for (int i=0; i < filesToSyncContents.size(); i++) { + op = filesToSyncContents.get(i); + contentsResult = op.execute(client); // returns without waiting for upload or download finishes + if (!contentsResult.isSuccess()) { + if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { + mConflictsFound++; + } else { + mFailsInFavouritesFound++; + if (contentsResult.getException() != null) { + Log_OC.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); + } else { + Log_OC.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage()); + } + } + } // won't let these fails break the synchronization process + } + + // removal of obsolete files - children = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId)); + mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId)); OCFile file; - String currentSavePath = FileDownloader.getSavePath(mAccount.name); - for (int i=0; i < children.size(); ) { - file = children.get(i); - if (file.getLastSyncDate() != mCurrentSyncTime) { - Log.d(TAG, "removing file: " + file); + String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); + for (int i=0; i < mChildren.size(); ) { + file = mChildren.get(i); + if (file.getLastSyncDateForProperties() != mCurrentSyncTime) { + Log_OC.d(TAG, "removing file: " + file); mStorageManager.removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath))); - children.remove(i); + mChildren.remove(i); } else { i++; } } - } else if (status == HttpStatus.SC_UNAUTHORIZED) { - syncResult.stats.numAuthExceptions++; - } else { - // TODO something smart with syncResult? OR NOT + client.exhaustResponse(query.getResponseBodyAsStream()); } - result = new RemoteOperationResult(isSuccess(status), status); - Log.i(TAG, "Synchronization of " + mRemotePath + ": " + result.getLogMessage()); - - - } catch (IOException e) { - syncResult.stats.numIoExceptions++; - logException(e, uri); + // prepare result object + if (isMultiStatus(status)) { + if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job + + } else { + result = new RemoteOperationResult(true, status); + } + } else { + result = new RemoteOperationResult(false, status); + } + Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); - } catch (DavException e) { - syncResult.stats.numParseExceptions++; - logException(e, uri); } catch (Exception e) { - // TODO something smart with syncresult - mRightSync = false; - logException(e, uri); + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException()); } finally { if (query != null) query.releaseConnection(); // let the connection available for other methods - - // synchronized folder -> notice to UI - sendStickyBroadcast(true, getStorageManager().getFileById(parentId).getRemotePath()); } - return result; } - - public boolean isSuccess(int status) { - return (status == HttpStatus.SC_MULTI_STATUS); // TODO check other possible OK codes; doc doesn't help - } - - private void requestContentDownload() { - Intent intent = new Intent(this.getContext(), FileDownloader.class); - intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount()); - intent.putExtra(FileDownloader.EXTRA_FILE, file); - file.setKeepInSync(true); - getContext().startService(intent); + public boolean isMultiStatus(int status) { + return (status == HttpStatus.SC_MULTI_STATUS); } + /** + * Creates and populates a new {@link OCFile} object with the data read from the server. + * + * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder). + * @return New OCFile instance representing the remote resource described by we. + */ private OCFile fillOCFile(WebdavEntry we) { OCFile file = new OCFile(we.decodedPath()); file.setCreationTimestamp(we.createTimestamp()); file.setFileLength(we.contentLength()); file.setMimetype(we.contentType()); - file.setModificationTimestamp(we.modifiedTimesamp()); - file.setLastSyncDate(mCurrentSyncTime); + file.setModificationTimestamp(we.modifiedTimestamp()); + file.setParentId(mParentId); return file; } + + /** + * Checks the storage path of the OCFile received as parameter. If it's out of the local ownCloud folder, + * tries to copy the file inside it. + * + * If the copy fails, the link to the local file is nullified. The account of forgotten files is kept in + * {@link #mForgottenLocalFiles} + * + * @param file File to check and fix. + */ + private void checkAndFixForeignStoragePath(OCFile file) { + String storagePath = file.getStoragePath(); + String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file); + if (storagePath != null && !storagePath.equals(expectedPath)) { + /// fix storagePaths out of the local ownCloud folder + File originalFile = new File(storagePath); + if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { + mForgottenLocalFiles.put(file.getRemotePath(), storagePath); + file.setStoragePath(null); + + } else { + InputStream in = null; + OutputStream out = null; + try { + File expectedFile = new File(expectedPath); + File expectedParent = expectedFile.getParentFile(); + expectedParent.mkdirs(); + if (!expectedParent.isDirectory()) { + throw new IOException("Unexpected error: parent directory could not be created"); + } + expectedFile.createNewFile(); + if (!expectedFile.isFile()) { + throw new IOException("Unexpected error: target file could not be created"); + } + in = new FileInputStream(originalFile); + out = new FileOutputStream(expectedFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0){ + out.write(buf, 0, len); + } + file.setStoragePath(expectedPath); + + } catch (Exception e) { + Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e); + mForgottenLocalFiles.put(file.getRemotePath(), storagePath); + file.setStoragePath(null); + + } finally { + try { + if (in != null) in.close(); + } catch (Exception e) { + Log_OC.d(TAG, "Weird exception while closing input stream for " + storagePath + " (ignoring)", e); + } + try { + if (out != null) out.close(); + } catch (Exception e) { + Log_OC.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e); + } + } + } + } + } + + }