FileStorageUtils.java 20 KB

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