FileStorageUtils.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author David A. Velasco
  5. * Copyright (C) 2016 ownCloud Inc.
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License version 2,
  9. * as published by the Free Software Foundation.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. package com.owncloud.android.utils;
  21. import android.accounts.Account;
  22. import android.content.Context;
  23. import android.content.SharedPreferences;
  24. import android.net.Uri;
  25. import android.preference.PreferenceManager;
  26. import android.webkit.MimeTypeMap;
  27. import com.owncloud.android.MainApp;
  28. import com.owncloud.android.R;
  29. import com.owncloud.android.datamodel.OCFile;
  30. import com.owncloud.android.lib.common.utils.Log_OC;
  31. import com.owncloud.android.lib.resources.files.RemoteFile;
  32. import java.io.File;
  33. import java.io.FileInputStream;
  34. import java.io.FileOutputStream;
  35. import java.io.IOException;
  36. import java.io.InputStream;
  37. import java.io.OutputStream;
  38. import java.text.SimpleDateFormat;
  39. import java.util.ArrayList;
  40. import java.util.Arrays;
  41. import java.util.Collections;
  42. import java.util.Comparator;
  43. import java.util.Date;
  44. import java.util.List;
  45. import java.util.Locale;
  46. import java.util.Vector;
  47. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  48. import third_parties.daveKoeller.AlphanumComparator;
  49. /**
  50. * Static methods to help in access to local file system.
  51. */
  52. public class FileStorageUtils {
  53. private static final String TAG = FileStorageUtils.class.getSimpleName();
  54. public static final Integer SORT_NAME = 0;
  55. public static final Integer SORT_DATE = 1;
  56. public static final Integer SORT_SIZE = 2;
  57. public static Integer mSortOrder = SORT_NAME;
  58. public static Boolean mSortAscending = true;
  59. /**
  60. * Get local owncloud storage path for accountName.
  61. */
  62. public static String getSavePath(String accountName) {
  63. return MainApp.getStoragePath()
  64. + File.separator
  65. + MainApp.getDataFolder()
  66. + File.separator
  67. + Uri.encode(accountName, "@");
  68. // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names,
  69. // that can be in the accountName since 0.1.190B
  70. }
  71. /**
  72. * Get local path where OCFile file is to be stored after upload. That is,
  73. * corresponding local path (in local owncloud storage) to remote uploaded
  74. * file.
  75. */
  76. public static String getDefaultSavePathFor(String accountName, OCFile file) {
  77. return getSavePath(accountName) + file.getRemotePath();
  78. }
  79. /**
  80. * Get absolute path to tmp folder inside datafolder in sd-card for given accountName.
  81. */
  82. public static String getTemporalPath(String accountName) {
  83. return MainApp.getStoragePath()
  84. + File.separator
  85. + MainApp.getDataFolder()
  86. + File.separator
  87. + "tmp"
  88. + File.separator
  89. + Uri.encode(accountName, "@");
  90. // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names,
  91. // that can be in the accountName since 0.1.190B
  92. }
  93. /**
  94. * Optimistic number of bytes available on sd-card. accountName is ignored.
  95. *
  96. * @param accountName not used. can thus be null.
  97. * @return Optimistic number of available bytes (can be less)
  98. */
  99. public static long getUsableSpace(String accountName) {
  100. File savePath = new File(MainApp.getStoragePath());
  101. return savePath.getUsableSpace();
  102. }
  103. public static String getLogPath() {
  104. return MainApp.getStoragePath() + File.separator + MainApp.getDataFolder() + File.separator + "log";
  105. }
  106. /**
  107. * Returns the a string like 2016/08/ for the passed date. If date is 0 an empty
  108. * string is returned
  109. *
  110. * @param date: date in microseconds since 1st January 1970
  111. * @return
  112. */
  113. private static String getSubpathFromDate(long date) {
  114. if (date == 0) {
  115. return "";
  116. }
  117. try {
  118. SimpleDateFormat formatter = new SimpleDateFormat(
  119. "yyyy" + OCFile.PATH_SEPARATOR + "MM" + OCFile.PATH_SEPARATOR, Locale.ENGLISH);
  120. return formatter.format(new Date(date));
  121. }
  122. catch(RuntimeException ex) {
  123. Log_OC.w(TAG, "could not extract date from timestamp");
  124. return "";
  125. }
  126. }
  127. /**
  128. * Returns the InstantUploadFilePath on the owncloud instance
  129. *
  130. * @param fileName
  131. * @param dateTaken: Time in milliseconds since 1970 when the picture was taken.
  132. * @return
  133. */
  134. public static String getInstantUploadFilePath(String remotePath, String fileName, long dateTaken,
  135. Boolean subfolderByDate) {
  136. String subPath = "";
  137. if (subfolderByDate) {
  138. subPath = getSubpathFromDate(dateTaken);
  139. }
  140. return remotePath + OCFile.PATH_SEPARATOR + subPath + (fileName == null ? "" : fileName);
  141. }
  142. /**
  143. * Gets the composed path when video is or must be stored
  144. * @param context
  145. * @param fileName: video file name
  146. * @return String: video file path composed
  147. */
  148. public static String getInstantVideoUploadFilePath(Context context, String fileName, long dateTaken) {
  149. SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
  150. String uploadVideoPathdef = context.getString(R.string.instant_upload_path);
  151. String uploadVideoPath = pref.getString("instant_video_upload_path", uploadVideoPathdef);
  152. String subPath = "";
  153. if (com.owncloud.android.db.PreferenceManager.instantVideoUploadPathUseSubfolders(context)) {
  154. subPath = getSubpathFromDate(dateTaken);
  155. }
  156. return uploadVideoPath + OCFile.PATH_SEPARATOR + subPath
  157. + (fileName == null ? "" : fileName);
  158. }
  159. public static String getParentPath(String remotePath) {
  160. String parentPath = new File(remotePath).getParent();
  161. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
  162. return parentPath;
  163. }
  164. /**
  165. * Creates and populates a new {@link OCFile} object with the data read from the server.
  166. *
  167. * @param remote remote file read from the server (remote file or folder).
  168. * @return New OCFile instance representing the remote resource described by remote.
  169. */
  170. public static OCFile fillOCFile(RemoteFile remote) {
  171. OCFile file = new OCFile(remote.getRemotePath());
  172. file.setCreationTimestamp(remote.getCreationTimestamp());
  173. if (remote.getMimeType().equalsIgnoreCase(MimeType.DIRECTORY)){
  174. file.setFileLength(remote.getSize());
  175. } else {
  176. file.setFileLength(remote.getLength());
  177. }
  178. file.setMimetype(remote.getMimeType());
  179. file.setModificationTimestamp(remote.getModifiedTimestamp());
  180. file.setEtag(remote.getEtag());
  181. file.setPermissions(remote.getPermissions());
  182. file.setRemoteId(remote.getRemoteId());
  183. return file;
  184. }
  185. /**
  186. * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}.
  187. *
  188. * @param ocFile OCFile
  189. * @return New RemoteFile instance representing the resource described by ocFile.
  190. */
  191. public static RemoteFile fillRemoteFile(OCFile ocFile){
  192. RemoteFile file = new RemoteFile(ocFile.getRemotePath());
  193. file.setCreationTimestamp(ocFile.getCreationTimestamp());
  194. file.setLength(ocFile.getFileLength());
  195. file.setMimeType(ocFile.getMimetype());
  196. file.setModifiedTimestamp(ocFile.getModificationTimestamp());
  197. file.setEtag(ocFile.getEtag());
  198. file.setPermissions(ocFile.getPermissions());
  199. file.setRemoteId(ocFile.getRemoteId());
  200. return file;
  201. }
  202. /**
  203. * Sorts all filenames, regarding last user decision
  204. */
  205. public static Vector<OCFile> sortOcFolder(Vector<OCFile> files){
  206. switch (mSortOrder){
  207. case 0:
  208. files = FileStorageUtils.sortOCFilesByName(files);
  209. break;
  210. case 1:
  211. files = FileStorageUtils.sortOCFilesByDate(files);
  212. break;
  213. case 2:
  214. files = FileStorageUtils.sortOCFilesBySize(files);
  215. break;
  216. }
  217. files = FileStorageUtils.sortOCFilesByFavourite(files);
  218. return files;
  219. }
  220. /**
  221. * Sorts all filenames, regarding last user decision
  222. */
  223. public static File[] sortLocalFolder(File[] files){
  224. switch (mSortOrder){
  225. case 0:
  226. files = FileStorageUtils.sortLocalFilesByName(files);
  227. break;
  228. case 1:
  229. files = FileStorageUtils.sortLocalFilesByDate(files);
  230. break;
  231. case 2:
  232. files = FileStorageUtils.sortLocalFilesBySize(files);
  233. break;
  234. }
  235. return files;
  236. }
  237. /**
  238. * Sorts list by Date
  239. * @param files
  240. */
  241. public static Vector<OCFile> sortOCFilesByDate(Vector<OCFile> files){
  242. final int multiplier = mSortAscending ? 1 : -1;
  243. Collections.sort(files, new Comparator<OCFile>() {
  244. @SuppressFBWarnings(value = "Bx", justification = "Would require stepping up API level")
  245. public int compare(OCFile o1, OCFile o2) {
  246. Long obj1 = o1.getModificationTimestamp();
  247. return multiplier * obj1.compareTo(o2.getModificationTimestamp());
  248. }
  249. });
  250. return files;
  251. }
  252. /**
  253. * Sorts list by Date
  254. * @param filesArray
  255. */
  256. public static File[] sortLocalFilesByDate(File[] filesArray){
  257. final int multiplier = mSortAscending ? 1 : -1;
  258. List<File> files = new ArrayList<File>(Arrays.asList(filesArray));
  259. Collections.sort(files, new Comparator<File>() {
  260. @SuppressFBWarnings(value = "Bx")
  261. public int compare(File o1, File o2) {
  262. Long obj1 = o1.lastModified();
  263. return multiplier * obj1.compareTo(o2.lastModified());
  264. }
  265. });
  266. File[] returnArray = new File[files.size()];
  267. return files.toArray(returnArray);
  268. }
  269. /**
  270. * Sorts list by Size
  271. */
  272. public static Vector<OCFile> sortOCFilesBySize(Vector<OCFile> files){
  273. final int multiplier = mSortAscending ? 1 : -1;
  274. Collections.sort(files, new Comparator<OCFile>() {
  275. @SuppressFBWarnings(value = "Bx")
  276. public int compare(OCFile o1, OCFile o2) {
  277. if (o1.isFolder() && o2.isFolder()) {
  278. Long obj1 = o1.getFileLength();
  279. return multiplier * obj1.compareTo(o2.getFileLength());
  280. } else if (o1.isFolder()) {
  281. return -1;
  282. } else if (o2.isFolder()) {
  283. return 1;
  284. } else {
  285. Long obj1 = o1.getFileLength();
  286. return multiplier * obj1.compareTo(o2.getFileLength());
  287. }
  288. }
  289. });
  290. return files;
  291. }
  292. /**
  293. * Sorts list by Size
  294. */
  295. public static File[] sortLocalFilesBySize(File[] filesArray) {
  296. final int multiplier = mSortAscending ? 1 : -1;
  297. List<File> files = new ArrayList<>(Arrays.asList(filesArray));
  298. Collections.sort(files, new Comparator<File>() {
  299. @SuppressFBWarnings(value = "Bx")
  300. public int compare(File o1, File o2) {
  301. if (o1.isDirectory() && o2.isDirectory()) {
  302. // Long obj1 = getFolderSize(o1);
  303. // return multiplier * obj1.compareTo(getFolderSize(o2));
  304. return o1.getPath().toLowerCase().compareTo(o2.getPath().toLowerCase());
  305. } else if (o1.isDirectory()) {
  306. return -1;
  307. } else if (o2.isDirectory()) {
  308. return 1;
  309. } else {
  310. Long obj1 = o1.length();
  311. return multiplier * obj1.compareTo(o2.length());
  312. }
  313. }
  314. });
  315. File[] returnArray = new File[files.size()];
  316. return files.toArray(returnArray);
  317. }
  318. /**
  319. * Sorts list by Name
  320. * @param files files to sort
  321. */
  322. @SuppressFBWarnings(value = "Bx")
  323. public static Vector<OCFile> sortOCFilesByName(Vector<OCFile> files){
  324. final int multiplier = mSortAscending ? 1 : -1;
  325. Collections.sort(files, new Comparator<OCFile>() {
  326. public int compare(OCFile o1, OCFile o2) {
  327. if (o1.isFolder() && o2.isFolder()) {
  328. return multiplier * new AlphanumComparator().compare(o1, o2);
  329. } else if (o1.isFolder()) {
  330. return -1;
  331. } else if (o2.isFolder()) {
  332. return 1;
  333. }
  334. return multiplier * new AlphanumComparator().compare(o1, o2);
  335. }
  336. });
  337. return files;
  338. }
  339. /**
  340. * Sorts list by Name
  341. * @param filesArray files to sort
  342. */
  343. public static File[] sortLocalFilesByName(File[] filesArray) {
  344. final int multiplier = mSortAscending ? 1 : -1;
  345. List<File> files = new ArrayList<File>(Arrays.asList(filesArray));
  346. Collections.sort(files, new Comparator<File>() {
  347. public int compare(File o1, File o2) {
  348. if (o1.isDirectory() && o2.isDirectory()) {
  349. return multiplier * o1.getPath().toLowerCase().compareTo(o2.getPath().toLowerCase());
  350. } else if (o1.isDirectory()) {
  351. return -1;
  352. } else if (o2.isDirectory()) {
  353. return 1;
  354. }
  355. return multiplier * new AlphanumComparator().compare(o1.getPath().toLowerCase(),
  356. o2.getPath().toLowerCase());
  357. }
  358. });
  359. File[] returnArray = new File[files.size()];
  360. return files.toArray(returnArray);
  361. }
  362. /**
  363. * Sorts list by Favourites
  364. * @param files files to sort
  365. */
  366. public static Vector<OCFile> sortOCFilesByFavourite(Vector<OCFile> files){
  367. Collections.sort(files, new Comparator<OCFile>() {
  368. public int compare(OCFile o1, OCFile o2) {
  369. if (o1.isFavorite() && o2.isFavorite()) {
  370. return 0;
  371. } else if (o1.isFavorite()) {
  372. return -1;
  373. } else if (o2.isFavorite()) {
  374. return 1;
  375. }
  376. return 0;
  377. }
  378. });
  379. return files;
  380. }
  381. /**
  382. * Local Folder size.
  383. *
  384. * @param dir File
  385. * @return Size in bytes
  386. */
  387. public static long getFolderSize(File dir) {
  388. if (dir.exists()) {
  389. long result = 0;
  390. for (File f : dir.listFiles()) {
  391. if (f.isDirectory()) {
  392. result += getFolderSize(f);
  393. } else {
  394. result += f.length();
  395. }
  396. }
  397. return result;
  398. }
  399. return 0;
  400. }
  401. /**
  402. * Mimetype String of a file.
  403. *
  404. * @param path the file path
  405. * @return the mime type based on the file name
  406. */
  407. public static String getMimeTypeFromName(String path) {
  408. String extension = "";
  409. int pos = path.lastIndexOf('.');
  410. if (pos >= 0) {
  411. extension = path.substring(pos + 1);
  412. }
  413. String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
  414. return (result != null) ? result : "";
  415. }
  416. /**
  417. * Scans the default location for saving local copies of files searching for
  418. * a 'lost' file with the same full name as the {@link OCFile} received as
  419. * parameter.
  420. *
  421. * This method helps to keep linked local copies of the files when the app is uninstalled, and then
  422. * reinstalled in the device. OR after the cache of the app was deleted in system settings.
  423. *
  424. * The method is assuming that all the local changes in the file where synchronized in the past. This is dangerous,
  425. * but assuming the contrary could lead to massive unnecessary synchronizations of downloaded file after deleting
  426. * the app cache.
  427. *
  428. * This should be changed in the near future to avoid any chance of data loss, but we need to add some options
  429. * to limit hard automatic synchronizations to wifi, unless the user wants otherwise.
  430. *
  431. * @param file File to associate a possible 'lost' local file.
  432. * @param account Account holding file.
  433. */
  434. public static void searchForLocalFileInDefaultPath(OCFile file, Account account) {
  435. if (file.getStoragePath() == null && !file.isFolder()) {
  436. File f = new File(FileStorageUtils.getDefaultSavePathFor(account.name, file));
  437. if (f.exists()) {
  438. file.setStoragePath(f.getAbsolutePath());
  439. file.setLastSyncDateForData(f.lastModified());
  440. }
  441. }
  442. }
  443. public static boolean copyFile(File src, File target) {
  444. boolean ret = true;
  445. InputStream in = null;
  446. OutputStream out = null;
  447. try {
  448. in = new FileInputStream(src);
  449. out = new FileOutputStream(target);
  450. byte[] buf = new byte[1024];
  451. int len;
  452. while ((len = in.read(buf)) > 0) {
  453. out.write(buf, 0, len);
  454. }
  455. } catch (IOException ex) {
  456. ret = false;
  457. } finally {
  458. if (in != null) {
  459. try {
  460. in.close();
  461. } catch (IOException e) {
  462. Log_OC.e(TAG, "Error closing input stream during copy", e);
  463. }
  464. }
  465. if (out != null) {
  466. try {
  467. out.close();
  468. } catch (IOException e) {
  469. Log_OC.e(TAG, "Error closing output stream during copy", e);
  470. }
  471. }
  472. }
  473. return ret;
  474. }
  475. }