06bb91c277330b07fbc8bc67d27b7dc85d613027
[pub/Android/ownCloud.git] / src / com / owncloud / android / operations / RenameFileOperation.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2013 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 package com.owncloud.android.operations;
19
20 import java.io.File;
21 import java.io.IOException;
22
23 import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
24 //import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
25
26 import android.accounts.Account;
27
28 import com.owncloud.android.Log_OC;
29 import com.owncloud.android.datamodel.FileDataStorageManager;
30 import com.owncloud.android.datamodel.OCFile;
31 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
32 import com.owncloud.android.utils.FileStorageUtils;
33
34 import eu.alefzero.webdav.WebdavClient;
35 import eu.alefzero.webdav.WebdavUtils;
36
37 /**
38 * Remote operation performing the rename of a remote file (or folder?) in the ownCloud server.
39 *
40 * @author David A. Velasco
41 */
42 public class RenameFileOperation extends RemoteOperation {
43
44 private static final String TAG = RenameFileOperation.class.getSimpleName();
45
46 private static final int RENAME_READ_TIMEOUT = 10000;
47 private static final int RENAME_CONNECTION_TIMEOUT = 5000;
48
49
50 private OCFile mFile;
51 private Account mAccount;
52 private String mNewName;
53 private String mNewRemotePath;
54 private FileDataStorageManager mStorageManager;
55
56
57 /**
58 * Constructor
59 *
60 * @param file OCFile instance describing the remote file or folder to rename
61 * @param account OwnCloud account containing the remote file
62 * @param newName New name to set as the name of file.
63 * @param storageManager Reference to the local database corresponding to the account where the file is contained.
64 */
65 public RenameFileOperation(OCFile file, Account account, String newName, FileDataStorageManager storageManager) {
66 mFile = file;
67 mAccount = account;
68 mNewName = newName;
69 mNewRemotePath = null;
70 mStorageManager = storageManager;
71 }
72
73 public OCFile getFile() {
74 return mFile;
75 }
76
77
78 /**
79 * Performs the rename operation.
80 *
81 * @param client Client object to communicate with the remote ownCloud server.
82 */
83 @Override
84 protected RemoteOperationResult run(WebdavClient client) {
85 RemoteOperationResult result = null;
86
87 LocalMoveMethod move = null;
88 mNewRemotePath = null;
89 try {
90 if (mNewName.equals(mFile.getFileName())) {
91 return new RemoteOperationResult(ResultCode.OK);
92 }
93
94 String parent = (new File(mFile.getRemotePath())).getParent();
95 parent = (parent.endsWith(OCFile.PATH_SEPARATOR)) ? parent : parent + OCFile.PATH_SEPARATOR;
96 mNewRemotePath = parent + mNewName;
97 if (mFile.isDirectory()) {
98 mNewRemotePath += OCFile.PATH_SEPARATOR;
99 }
100
101 // check if the new name is valid in the local file system
102 if (!isValidNewName()) {
103 return new RemoteOperationResult(ResultCode.INVALID_LOCAL_FILE_NAME);
104 }
105
106 // check if a file with the new name already exists
107 if (client.existsFile(mNewRemotePath) || // remote check could fail by network failure. by indeterminate behavior of HEAD for folders ...
108 mStorageManager.getFileByPath(mNewRemotePath) != null) { // ... so local check is convenient
109 return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE);
110 }
111 move = new LocalMoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()),
112 client.getBaseUri() + WebdavUtils.encodePath(mNewRemotePath));
113 int status = client.executeMethod(move, RENAME_READ_TIMEOUT, RENAME_CONNECTION_TIMEOUT);
114 if (move.succeeded()) {
115
116 if (mFile.isDirectory()) {
117 saveLocalDirectory();
118
119 } else {
120 saveLocalFile();
121
122 }
123
124 /*
125 *} else if (mFile.isDirectory() && (status == 207 || status >= 500)) {
126 * // TODO
127 * // if server fails in the rename of a folder, some children files could have been moved to a folder with the new name while some others
128 * // stayed in the old folder;
129 * //
130 * // easiest and heaviest solution is synchronizing the parent folder (or the full account);
131 * //
132 * // a better solution is synchronizing the folders with the old and new names;
133 *}
134 */
135
136 }
137
138 move.getResponseBodyAsString(); // exhaust response, although not interesting
139 result = new RemoteOperationResult(move.succeeded(), status, move.getResponseHeaders());
140 Log_OC.i(TAG, "Rename " + mFile.getRemotePath() + " to " + mNewRemotePath + ": " + result.getLogMessage());
141
142 } catch (Exception e) {
143 result = new RemoteOperationResult(e);
144 Log_OC.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((mNewRemotePath==null) ? mNewName : mNewRemotePath) + ": " + result.getLogMessage(), e);
145
146 } finally {
147 if (move != null)
148 move.releaseConnection();
149 }
150 return result;
151 }
152
153
154 private void saveLocalDirectory() {
155 mStorageManager.moveDirectory(mFile, mNewRemotePath);
156 String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
157 File localDir = new File(localPath);
158 if (localDir.exists()) {
159 localDir.renameTo(new File(FileStorageUtils.getSavePath(mAccount.name) + mNewRemotePath));
160 // TODO - if renameTo fails, children files that are already down will result unlinked
161 }
162 }
163
164 private void saveLocalFile() {
165 mFile.setFileName(mNewName);
166
167 // try to rename the local copy of the file
168 if (mFile.isDown()) {
169 File f = new File(mFile.getStoragePath());
170 String parentStoragePath = f.getParent();
171 if (!parentStoragePath.endsWith(File.separator))
172 parentStoragePath += File.separator;
173 if (f.renameTo(new File(parentStoragePath + mNewName))) {
174 mFile.setStoragePath(parentStoragePath + mNewName);
175 }
176 // else - NOTHING: the link to the local file is kept although the local name can't be updated
177 // TODO - study conditions when this could be a problem
178 }
179
180 mStorageManager.saveFile(mFile);
181 }
182
183 /**
184 * Checks if the new name to set is valid in the file system
185 *
186 * The only way to be sure is trying to create a file with that name. It's made in the temporal directory
187 * for downloads, out of any account, and then removed.
188 *
189 * IMPORTANT: The test must be made in the same file system where files are download. The internal storage
190 * could be formatted with a different file system.
191 *
192 * TODO move this method, and maybe FileDownload.get***Path(), to a class with utilities specific for the interactions with the file system
193 *
194 * @return 'True' if a temporal file named with the name to set could be created in the file system where
195 * local files are stored.
196 * @throws IOException When the temporal folder can not be created.
197 */
198 private boolean isValidNewName() throws IOException {
199 // check tricky names
200 if (mNewName == null || mNewName.length() <= 0 || mNewName.contains(File.separator) || mNewName.contains("%")) {
201 return false;
202 }
203 // create a test file
204 String tmpFolderName = FileStorageUtils.getTemporalPath("");
205 File testFile = new File(tmpFolderName + mNewName);
206 File tmpFolder = testFile.getParentFile();
207 tmpFolder.mkdirs();
208 if (!tmpFolder.isDirectory()) {
209 throw new IOException("Unexpected error: temporal directory could not be created");
210 }
211 try {
212 testFile.createNewFile(); // return value is ignored; it could be 'false' because the file already existed, that doesn't invalidate the name
213 } catch (IOException e) {
214 Log_OC.i(TAG, "Test for validity of name " + mNewName + " in the file system failed");
215 return false;
216 }
217 boolean result = (testFile.exists() && testFile.isFile());
218
219 // cleaning ; result is ignored, since there is not much we could do in case of failure, but repeat and repeat...
220 testFile.delete();
221
222 return result;
223 }
224
225
226 // move operation
227 private class LocalMoveMethod extends DavMethodBase {
228
229 public LocalMoveMethod(String uri, String dest) {
230 super(uri);
231 addRequestHeader(new org.apache.commons.httpclient.Header("Destination", dest));
232 }
233
234 @Override
235 public String getName() {
236 return "MOVE";
237 }
238
239 @Override
240 protected boolean isSuccess(int status) {
241 return status == 201 || status == 204;
242 }
243
244 }
245
246
247 }