Fixed database update due to renaming folders
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / OCFileListFragment.java
1 /* ownCloud Android client application
2 * Copyright (C) 2011 Bartek Przybylski
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 as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18 package com.owncloud.android.ui.fragment;
19
20 import java.io.File;
21
22 import com.owncloud.android.AccountUtils;
23 import com.owncloud.android.R;
24 import com.owncloud.android.datamodel.DataStorageManager;
25 import com.owncloud.android.datamodel.OCFile;
26 import com.owncloud.android.files.services.FileDownloader;
27 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
28 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
29 import com.owncloud.android.network.OwnCloudClientUtils;
30 import com.owncloud.android.operations.OnRemoteOperationListener;
31 import com.owncloud.android.operations.RemoteOperation;
32 import com.owncloud.android.operations.RemoveFileOperation;
33 import com.owncloud.android.operations.RenameFileOperation;
34 import com.owncloud.android.ui.FragmentListView;
35 import com.owncloud.android.ui.activity.FileDisplayActivity;
36 import com.owncloud.android.ui.activity.TransferServiceGetter;
37 import com.owncloud.android.ui.adapter.FileListListAdapter;
38 import com.owncloud.android.ui.dialog.EditNameDialog;
39 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
40 import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
41
42 import eu.alefzero.webdav.WebdavClient;
43 import eu.alefzero.webdav.WebdavUtils;
44
45 import android.accounts.Account;
46 import android.app.Activity;
47 import android.content.ActivityNotFoundException;
48 import android.content.Intent;
49 import android.net.Uri;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.util.Log;
53 import android.view.ContextMenu;
54 import android.view.MenuInflater;
55 import android.view.MenuItem;
56 import android.view.View;
57 import android.webkit.MimeTypeMap;
58 import android.widget.AdapterView;
59 import android.widget.Toast;
60 import android.widget.AdapterView.AdapterContextMenuInfo;
61
62 /**
63 * A Fragment that lists all files and folders in a given path.
64 *
65 * @author Bartek Przybylski
66 *
67 */
68 public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener {
69 private static final String TAG = "FileListFragment";
70 private static final String SAVED_LIST_POSITION = "LIST_POSITION";
71
72 private OCFileListFragment.ContainerActivity mContainerActivity;
73
74 private OCFile mFile = null;
75 private FileListListAdapter mAdapter;
76
77 private Handler mHandler;
78 private OCFile mTargetFile;
79
80
81 /**
82 * {@inheritDoc}
83 */
84 @Override
85 public void onAttach(Activity activity) {
86 super.onAttach(activity);
87 try {
88 mContainerActivity = (ContainerActivity) activity;
89 } catch (ClassCastException e) {
90 throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName());
91 }
92 }
93
94
95 /**
96 * {@inheritDoc}
97 */
98 @Override
99 public void onActivityCreated(Bundle savedInstanceState) {
100 Log.i(TAG, "onActivityCreated() start");
101
102 super.onActivityCreated(savedInstanceState);
103 mAdapter = new FileListListAdapter(mContainerActivity.getInitialDirectory(), mContainerActivity.getStorageManager(), getActivity(), mContainerActivity);
104 setListAdapter(mAdapter);
105
106 if (savedInstanceState != null) {
107 Log.i(TAG, "savedInstanceState is not null");
108 int position = savedInstanceState.getInt(SAVED_LIST_POSITION);
109 setReferencePosition(position);
110 }
111
112 registerForContextMenu(getListView());
113 getListView().setOnCreateContextMenuListener(this);
114
115 mHandler = new Handler();
116
117 Log.i(TAG, "onActivityCreated() stop");
118 }
119
120
121
122 @Override
123 public void onSaveInstanceState(Bundle savedInstanceState) {
124 Log.i(TAG, "onSaveInstanceState() start");
125
126 savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition());
127
128 Log.i(TAG, "onSaveInstanceState() stop");
129 }
130
131
132 @Override
133 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
134 OCFile file = (OCFile) mAdapter.getItem(position);
135 if (file != null) {
136 /// Click on a directory
137 if (file.getMimetype().equals("DIR")) {
138 // just local updates
139 mFile = file;
140 listDirectory(file);
141 // any other updates are let to the container Activity
142 mContainerActivity.onDirectoryClick(file);
143
144 } else { /// Click on a file
145 mContainerActivity.onFileClick(file);
146 }
147
148 } else {
149 Log.d(TAG, "Null object in ListAdapter!!");
150 }
151
152 }
153
154 /**
155 * {@inheritDoc}
156 */
157 @Override
158 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
159 super.onCreateContextMenu(menu, v, menuInfo);
160 MenuInflater inflater = getActivity().getMenuInflater();
161 inflater.inflate(R.menu.file_context_menu, menu);
162 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
163 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
164 MenuItem item = null;
165 int[] ids = null;
166 if (targetFile.isDirectory()) {
167 int[] theIds = {R.id.open_file_item, R.id.download_file_item, R.id.cancel_download_item, R.id.cancel_upload_item};
168 ids = theIds;
169
170 } else if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
171 int[] theIds = {R.id.open_file_item, R.id.download_file_item, R.id.cancel_upload_item};
172 ids = theIds;
173
174 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
175 int[] theIds = {R.id.open_file_item, R.id.download_file_item, R.id.cancel_download_item};
176 ids = theIds;
177
178 } else if ( targetFile.isDown()) {
179 int[] theIds = {R.id.cancel_download_item, R.id.cancel_upload_item};
180 ids = theIds;
181
182 } else {
183 int[] theIds = {R.id.open_file_item, R.id.cancel_download_item, R.id.cancel_upload_item};
184 ids = theIds;
185 }
186
187 for (int i=0; i < ids.length; i++) {
188 item = menu.findItem(ids[i]);
189 if (item != null) {
190 item.setVisible(false);
191 item.setEnabled(false);
192 }
193 }
194 }
195
196
197 /**
198 * {@inhericDoc}
199 */
200 @Override
201 public boolean onContextItemSelected (MenuItem item) {
202 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
203 mTargetFile = (OCFile) mAdapter.getItem(info.position);
204 switch (item.getItemId()) {
205 case R.id.rename_file_item: {
206 EditNameDialog dialog = EditNameDialog.newInstance(mTargetFile.getFileName());
207 dialog.setOnDismissListener(this);
208 dialog.show(getFragmentManager(), EditNameDialog.TAG);
209 return true;
210 }
211 case R.id.remove_file_item: {
212 int messageStringId = R.string.confirmation_remove_alert;
213 int posBtnStringId = R.string.confirmation_remove_remote;
214 int neuBtnStringId = -1;
215 if (mTargetFile.isDirectory()) {
216 messageStringId = R.string.confirmation_remove_folder_alert;
217 posBtnStringId = R.string.confirmation_remove_remote_and_local;
218 neuBtnStringId = R.string.confirmation_remove_folder_local;
219 } else if (mTargetFile.isDown()) {
220 posBtnStringId = R.string.confirmation_remove_remote_and_local;
221 neuBtnStringId = R.string.confirmation_remove_local;
222 }
223 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
224 messageStringId,
225 new String[]{mTargetFile.getFileName()},
226 posBtnStringId,
227 neuBtnStringId,
228 R.string.common_cancel);
229 confDialog.setOnConfirmationListener(this);
230 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
231 return true;
232 }
233 case R.id.open_file_item: {
234 String storagePath = mTargetFile.getStoragePath();
235 String encodedStoragePath = WebdavUtils.encodePath(storagePath);
236 try {
237 Intent i = new Intent(Intent.ACTION_VIEW);
238 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mTargetFile.getMimetype());
239 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
240 startActivity(i);
241
242 } catch (Throwable t) {
243 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mTargetFile.getMimetype());
244 boolean toastIt = true;
245 String mimeType = "";
246 try {
247 Intent i = new Intent(Intent.ACTION_VIEW);
248 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
249 if (mimeType == null || !mimeType.equals(mTargetFile.getMimetype())) {
250 if (mimeType != null) {
251 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
252 } else {
253 // desperate try
254 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
255 }
256 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
257 startActivity(i);
258 toastIt = false;
259 }
260
261 } catch (IndexOutOfBoundsException e) {
262 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
263
264 } catch (ActivityNotFoundException e) {
265 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
266
267 } catch (Throwable th) {
268 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
269
270 } finally {
271 if (toastIt) {
272 Toast.makeText(getActivity(), "There is no application to handle file " + mTargetFile.getFileName(), Toast.LENGTH_SHORT).show();
273 }
274 }
275
276 }
277 return true;
278 }
279 case R.id.download_file_item: {
280 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
281 Intent i = new Intent(getActivity(), FileDownloader.class);
282 i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
283 i.putExtra(FileDownloader.EXTRA_FILE, mTargetFile);
284 getActivity().startService(i);
285 listDirectory();
286 mContainerActivity.onTransferStateChanged(mTargetFile, true, false);
287 return true;
288 }
289 case R.id.cancel_download_item: {
290 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
291 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
292 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
293 downloaderBinder.cancel(account, mTargetFile);
294 listDirectory();
295 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
296 }
297 return true;
298 }
299 case R.id.cancel_upload_item: {
300 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
301 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
302 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
303 uploaderBinder.cancel(account, mTargetFile);
304 listDirectory();
305 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
306 }
307 return true;
308 }
309 default:
310 return super.onContextItemSelected(item);
311 }
312 }
313
314
315 /**
316 * Call this, when the user presses the up button
317 */
318 public void onNavigateUp() {
319 OCFile parentDir = null;
320
321 if(mFile != null){
322 DataStorageManager storageManager = mContainerActivity.getStorageManager();
323 parentDir = storageManager.getFileById(mFile.getParentId());
324 mFile = parentDir;
325 }
326 listDirectory(parentDir);
327 }
328
329 /**
330 * Use this to query the {@link OCFile} that is currently
331 * being displayed by this fragment
332 * @return The currently viewed OCFile
333 */
334 public OCFile getCurrentFile(){
335 return mFile;
336 }
337
338 /**
339 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
340 */
341 public void listDirectory(){
342 listDirectory(null);
343 }
344
345 /**
346 * Lists the given directory on the view. When the input parameter is null,
347 * it will either refresh the last known directory, or list the root
348 * if there never was a directory.
349 *
350 * @param directory File to be listed
351 */
352 public void listDirectory(OCFile directory) {
353 DataStorageManager storageManager = mContainerActivity.getStorageManager();
354 if (storageManager != null) {
355
356 // Check input parameters for null
357 if(directory == null){
358 if(mFile != null){
359 directory = mFile;
360 } else {
361 directory = storageManager.getFileByPath("/");
362 if (directory == null) return; // no files, wait for sync
363 }
364 }
365
366
367 // If that's not a directory -> List its parent
368 if(!directory.isDirectory()){
369 Log.w(TAG, "You see, that is not a directory -> " + directory.toString());
370 directory = storageManager.getFileById(directory.getParentId());
371 }
372
373 mAdapter.swapDirectory(directory, storageManager);
374 if (mFile == null || !mFile.equals(directory)) {
375 mList.setSelectionFromTop(0, 0);
376 }
377 mFile = directory;
378 }
379 }
380
381
382
383 /**
384 * Interface to implement by any Activity that includes some instance of FileListFragment
385 *
386 * @author David A. Velasco
387 */
388 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
389
390 /**
391 * Callback method invoked when a directory is clicked by the user on the files list
392 *
393 * @param file
394 */
395 public void onDirectoryClick(OCFile file);
396
397 /**
398 * Callback method invoked when a file (non directory) is clicked by the user on the files list
399 *
400 * @param file
401 */
402 public void onFileClick(OCFile file);
403
404 /**
405 * Getter for the current DataStorageManager in the container activity
406 */
407 public DataStorageManager getStorageManager();
408
409
410 /**
411 * Callback method invoked when the parent activity is fully created to get the directory to list firstly.
412 *
413 * @return Directory to list firstly. Can be NULL.
414 */
415 public OCFile getInitialDirectory();
416
417
418 /**
419 * Callback method invoked when a the 'transfer state' of a file changes.
420 *
421 * This happens when a download or upload is started or ended for a file.
422 *
423 * This method is necessary by now to update the user interface of the double-pane layout in tablets
424 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
425 * won't provide the needed response before the method where this is called finishes.
426 *
427 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
428 *
429 * @param file OCFile which state changed.
430 * @param downloading Flag signaling if the file is now downloading.
431 * @param uploading Flag signaling if the file is now uploading.
432 */
433 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
434
435 }
436
437
438 @Override
439 public void onDismiss(EditNameDialog dialog) {
440 if (dialog.getResult()) {
441 String newFilename = dialog.getNewFilename();
442 Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
443 RemoteOperation operation = new RenameFileOperation(mTargetFile,
444 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
445 newFilename,
446 mContainerActivity.getStorageManager());
447 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
448 operation.execute(wc, mContainerActivity, mHandler);
449 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
450 }
451 }
452
453
454 @Override
455 public void onConfirmation(String callerTag) {
456 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
457 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
458 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
459 true,
460 mContainerActivity.getStorageManager());
461 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
462 operation.execute(wc, mContainerActivity, mHandler);
463
464 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
465 }
466 }
467 }
468
469 @Override
470 public void onNeutral(String callerTag) {
471 File f = null;
472 if (mTargetFile.isDirectory()) {
473 // TODO run in a secondary thread?
474 mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
475
476 } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
477 f.delete();
478 mTargetFile.setStoragePath(null);
479 mContainerActivity.getStorageManager().saveFile(mTargetFile);
480 }
481 listDirectory();
482 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
483 }
484
485 @Override
486 public void onCancel(String callerTag) {
487 Log.d(TAG, "REMOVAL CANCELED");
488 }
489
490
491 }