FileStorageUtils.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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.annotation.SuppressLint;
  23. import android.content.Context;
  24. import android.content.SharedPreferences;
  25. import android.net.Uri;
  26. import android.os.Environment;
  27. import android.os.StatFs;
  28. import android.preference.PreferenceManager;
  29. import android.webkit.MimeTypeMap;
  30. import com.owncloud.android.MainApp;
  31. import com.owncloud.android.R;
  32. import com.owncloud.android.datamodel.OCFile;
  33. import com.owncloud.android.lib.common.utils.Log_OC;
  34. import com.owncloud.android.lib.resources.files.RemoteFile;
  35. import java.io.File;
  36. import java.util.ArrayList;
  37. import java.util.Arrays;
  38. import java.util.Collections;
  39. import java.util.Comparator;
  40. import java.util.List;
  41. import java.util.Vector;
  42. import third_parties.daveKoeller.AlphanumComparator;
  43. /**
  44. * Static methods to help in access to local file system.
  45. */
  46. public class FileStorageUtils {
  47. private static final String TAG = FileStorageUtils.class.getSimpleName();
  48. public static final Integer SORT_NAME = 0;
  49. public static final Integer SORT_DATE = 1;
  50. public static final Integer SORT_SIZE = 2;
  51. public static Integer mSortOrder = SORT_NAME;
  52. public static Boolean mSortAscending = true;
  53. /**
  54. * Takes a full path to owncloud file and removes beginning which is path to ownload data folder.
  55. * If fullPath does not start with that folder, fullPath is returned as is.
  56. */
  57. public static final String removeDataFolderPath(String fullPath) {
  58. File sdCard = Environment.getExternalStorageDirectory();
  59. String dataFolderPath = sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/";
  60. if(fullPath.indexOf(dataFolderPath) == 0) {
  61. return fullPath.substring(dataFolderPath.length());
  62. }
  63. return fullPath;
  64. }
  65. /**
  66. * Get local owncloud storage path for accountName.
  67. */
  68. public static final String getSavePath(String accountName) {
  69. File sdCard = Environment.getExternalStorageDirectory();
  70. return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/" + 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 final 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 final String getTemporalPath(String accountName) {
  86. File sdCard = Environment.getExternalStorageDirectory();
  87. return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/tmp/" + Uri.encode(accountName, "@");
  88. // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names,
  89. // that can be in the accountName since 0.1.190B
  90. }
  91. /**
  92. * Optimistic number of bytes available on sd-card. accountName is ignored.
  93. * @param accountName not used. can thus be null.
  94. * @return Optimistic number of available bytes (can be less)
  95. */
  96. @SuppressLint("NewApi")
  97. public static final long getUsableSpace(String accountName) {
  98. File savePath = Environment.getExternalStorageDirectory();
  99. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
  100. return savePath.getUsableSpace();
  101. } else {
  102. StatFs stats = new StatFs(savePath.getAbsolutePath());
  103. return stats.getAvailableBlocks() * stats.getBlockSize();
  104. }
  105. }
  106. public static final String getLogPath() {
  107. return Environment.getExternalStorageDirectory() + 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
  115. */
  116. private static String getSubpathFromDate(long date) {
  117. if (date == 0) {
  118. return "";
  119. }
  120. try {
  121. SimpleDateFormat formatter = new SimpleDateFormat(
  122. "yyyy" + OCFile.PATH_SEPARATOR + "MM" + OCFile.PATH_SEPARATOR, Locale.ENGLISH);
  123. return formatter.format(new Date(date));
  124. }
  125. catch(RuntimeException ex) {
  126. Log_OC.w(TAG, "could not extract date from timestamp");
  127. return "";
  128. }
  129. }
  130. /**
  131. * Returns the InstantUploadFilePath on the owncloud instance
  132. *
  133. * @param context
  134. * @param fileName
  135. * @param dateTaken: Time in milliseconds since 1970 when the picture was taken.
  136. * @return
  137. */
  138. public static String getInstantUploadFilePath(Context context, String fileName, long dateTaken) {
  139. SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
  140. String uploadPathdef = context.getString(R.string.instant_upload_path);
  141. String uploadPath = pref.getString("instant_upload_path", uploadPathdef);
  142. String subPath = "";
  143. if (com.owncloud.android.db.PreferenceManager.instantPictureUploadPathUseSubfolders(context)) {
  144. subPath = getSubpathFromDate(dateTaken);
  145. }
  146. return uploadPath + OCFile.PATH_SEPARATOR + subPath
  147. + (fileName == null ? "" : fileName);
  148. }
  149. /**
  150. * Gets the composed path when video is or must be stored
  151. * @param context
  152. * @param fileName: video file name
  153. * @return String: video file path composed
  154. */
  155. public static String getInstantVideoUploadFilePath(Context context, String fileName, long dateTaken) {
  156. SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
  157. String uploadVideoPathdef = context.getString(R.string.instant_upload_path);
  158. String uploadVideoPath = pref.getString("instant_video_upload_path", uploadVideoPathdef);
  159. String subPath = "";
  160. if (com.owncloud.android.db.PreferenceManager.instantVideoUploadPathUseSubfolders(context)) {
  161. subPath = getSubpathFromDate(dateTaken);
  162. }
  163. return uploadVideoPath + OCFile.PATH_SEPARATOR + subPath
  164. + (fileName == null ? "" : fileName);
  165. }
  166. public static String getParentPath(String remotePath) {
  167. String parentPath = new File(remotePath).getParent();
  168. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
  169. return parentPath;
  170. }
  171. /**
  172. * Creates and populates a new {@link OCFile} object with the data read from the server.
  173. *
  174. * @param remote remote file read from the server (remote file or folder).
  175. * @return New OCFile instance representing the remote resource described by remote.
  176. */
  177. public static OCFile fillOCFile(RemoteFile remote) {
  178. OCFile file = new OCFile(remote.getRemotePath());
  179. file.setCreationTimestamp(remote.getCreationTimestamp());
  180. if (remote.getMimeType().equalsIgnoreCase(MimeType.DIRECTORY)){
  181. file.setFileLength(remote.getSize());
  182. } else {
  183. file.setFileLength(remote.getLength());
  184. }
  185. file.setMimetype(remote.getMimeType());
  186. file.setModificationTimestamp(remote.getModifiedTimestamp());
  187. file.setEtag(remote.getEtag());
  188. file.setPermissions(remote.getPermissions());
  189. file.setRemoteId(remote.getRemoteId());
  190. return file;
  191. }
  192. /**
  193. * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}.
  194. *
  195. * @param ocFile OCFile
  196. * @return New RemoteFile instance representing the resource described by ocFile.
  197. */
  198. public static RemoteFile fillRemoteFile(OCFile ocFile){
  199. RemoteFile file = new RemoteFile(ocFile.getRemotePath());
  200. file.setCreationTimestamp(ocFile.getCreationTimestamp());
  201. file.setLength(ocFile.getFileLength());
  202. file.setMimeType(ocFile.getMimetype());
  203. file.setModifiedTimestamp(ocFile.getModificationTimestamp());
  204. file.setEtag(ocFile.getEtag());
  205. file.setPermissions(ocFile.getPermissions());
  206. file.setRemoteId(ocFile.getRemoteId());
  207. return file;
  208. }
  209. /**
  210. * Sorts all filenames, regarding last user decision
  211. */
  212. public static Vector<OCFile> sortOcFolder(Vector<OCFile> files){
  213. switch (mSortOrder){
  214. case 0:
  215. files = FileStorageUtils.sortOCFilesByName(files);
  216. break;
  217. case 1:
  218. files = FileStorageUtils.sortOCFilesByDate(files);
  219. break;
  220. case 2:
  221. files = FileStorageUtils.sortBySize(files);
  222. break;
  223. }
  224. return files;
  225. }
  226. /**
  227. * Sorts all filenames, regarding last user decision
  228. */
  229. public static File[] sortLocalFolder(File[] files){
  230. switch (mSortOrder){
  231. case 0:
  232. files = FileStorageUtils.sortLocalFilesByName(files);
  233. break;
  234. case 1:
  235. files = FileStorageUtils.sortLocalFilesByDate(files);
  236. break;
  237. case 2:
  238. // mFiles = FileStorageUtils.sortBySize(mSortAscending);
  239. break;
  240. }
  241. return files;
  242. }
  243. /**
  244. * Sorts list by Date
  245. * @param files
  246. */
  247. public static Vector<OCFile> sortOCFilesByDate(Vector<OCFile> files){
  248. final int multiplier = mSortAscending ? 1 : -1;
  249. Collections.sort(files, new Comparator<OCFile>() {
  250. public int compare(OCFile o1, OCFile o2) {
  251. if (o1.isFolder() && o2.isFolder()) {
  252. Long obj1 = o1.getModificationTimestamp();
  253. return multiplier * obj1.compareTo(o2.getModificationTimestamp());
  254. }
  255. else if (o1.isFolder()) {
  256. return -1;
  257. } else if (o2.isFolder()) {
  258. return 1;
  259. } else if (o1.getModificationTimestamp() == 0 || o2.getModificationTimestamp() == 0){
  260. return 0;
  261. } else {
  262. Long obj1 = o1.getModificationTimestamp();
  263. return multiplier * obj1.compareTo(o2.getModificationTimestamp());
  264. }
  265. }
  266. });
  267. return files;
  268. }
  269. /**
  270. * Sorts list by Date
  271. * @param filesArray
  272. */
  273. public static File[] sortLocalFilesByDate(File[] filesArray){
  274. final int multiplier = mSortAscending ? 1 : -1;
  275. List<File> files = new ArrayList<File>(Arrays.asList(filesArray));
  276. Collections.sort(files, new Comparator<File>() {
  277. public int compare(File o1, File o2) {
  278. if (o1.isDirectory() && o2.isDirectory()) {
  279. Long obj1 = o1.lastModified();
  280. return multiplier * obj1.compareTo(o2.lastModified());
  281. }
  282. else if (o1.isDirectory()) {
  283. return -1;
  284. } else if (o2.isDirectory()) {
  285. return 1;
  286. } else if (o1.lastModified() == 0 || o2.lastModified() == 0){
  287. return 0;
  288. } else {
  289. Long obj1 = o1.lastModified();
  290. return multiplier * obj1.compareTo(o2.lastModified());
  291. }
  292. }
  293. });
  294. File[] returnArray = new File[1];
  295. return files.toArray(returnArray);
  296. }
  297. // /**
  298. // * Sorts list by Size
  299. // * @param sortAscending true: ascending, false: descending
  300. // */
  301. // public static Vector<OCFile> sortBySize(Vector<OCFile> files){
  302. // final Integer val;
  303. // if (mSortAscending){
  304. // val = 1;
  305. // } else {
  306. // val = -1;
  307. // }
  308. //
  309. // Collections.sort(files, new Comparator<OCFile>() {
  310. // public int compare(OCFile o1, OCFile o2) {
  311. // if (o1.isFolder() && o2.isFolder()) {
  312. // Long obj1 = getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o1)));
  313. // return val * obj1.compareTo(getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o2))));
  314. // }
  315. // else if (o1.isFolder()) {
  316. // return -1;
  317. // } else if (o2.isFolder()) {
  318. // return 1;
  319. // } else if (o1.getFileLength() == 0 || o2.getFileLength() == 0){
  320. // return 0;
  321. // } else {
  322. // Long obj1 = o1.getFileLength();
  323. // return val * obj1.compareTo(o2.getFileLength());
  324. // }
  325. // }
  326. // });
  327. //
  328. // return files;
  329. // }
  330. Collections.sort(files, new Comparator<OCFile>() {
  331. public int compare(OCFile o1, OCFile o2) {
  332. if (o1.isFolder() && o2.isFolder()) {
  333. Long obj1 = o1.getFileLength();
  334. return multiplier * obj1.compareTo(o2.getFileLength());
  335. }
  336. else if (o1.isFolder()) {
  337. return -1;
  338. } else if (o2.isFolder()) {
  339. return 1;
  340. } else if (o1.getFileLength() == 0 || o2.getFileLength() == 0){
  341. return 0;
  342. } else {
  343. Long obj1 = o1.getFileLength();
  344. return multiplier * obj1.compareTo(o2.getFileLength());
  345. }
  346. }
  347. });
  348. return files;
  349. }
  350. /**
  351. * Sorts list by Name
  352. * @param files files to sort
  353. */
  354. public static Vector<OCFile> sortOCFilesByName(Vector<OCFile> files){
  355. final int multiplier = mSortAscending ? 1 : -1;
  356. Collections.sort(files, new Comparator<OCFile>() {
  357. public int compare(OCFile o1, OCFile o2) {
  358. if (o1.isFolder() && o2.isFolder()) {
  359. return multiplier * new AlphanumComparator().compare(o1, o2);
  360. } else if (o1.isFolder()) {
  361. return -1;
  362. } else if (o2.isFolder()) {
  363. return 1;
  364. }
  365. return multiplier * new AlphanumComparator().compare(o1, o2);
  366. }
  367. });
  368. return files;
  369. }
  370. /**
  371. * Sorts list by Name
  372. * @param filesArray files to sort
  373. */
  374. public static File[] sortLocalFilesByName(File[] filesArray){
  375. final int multiplier = mSortAscending ? 1 : -1;
  376. List<File> files = new ArrayList<File>(Arrays.asList(filesArray));
  377. Collections.sort(files, new Comparator<File>() {
  378. public int compare(File o1, File o2) {
  379. if (o1.isDirectory() && o2.isDirectory()) {
  380. return multiplier * o1.getPath().toLowerCase().compareTo(o2.getPath().toLowerCase());
  381. } else if (o1.isDirectory()) {
  382. return -1;
  383. } else if (o2.isDirectory()) {
  384. return 1;
  385. }
  386. return multiplier * new AlphanumComparator().compare(o1.getPath().toLowerCase(),
  387. o2.getPath().toLowerCase());
  388. }
  389. });
  390. File[] returnArray = new File[1];
  391. return files.toArray(returnArray);
  392. }
  393. /**
  394. * Local Folder size
  395. * @param dir File
  396. * @return Size in bytes
  397. */
  398. public static long getFolderSize(File dir) {
  399. if (dir.exists()) {
  400. long result = 0;
  401. File[] fileList = dir.listFiles();
  402. for(int i = 0; i < fileList.length; i++) {
  403. if(fileList[i].isDirectory()) {
  404. result += getFolderSize(fileList[i]);
  405. } else {
  406. result += fileList[i].length();
  407. }
  408. }
  409. return result;
  410. }
  411. return 0;
  412. }
  413. /**
  414. * Mimetype String of a file
  415. * @param path
  416. * @return
  417. */
  418. public static String getMimeTypeFromName(String path) {
  419. String extension = "";
  420. int pos = path.lastIndexOf('.');
  421. if (pos >= 0) {
  422. extension = path.substring(pos + 1);
  423. }
  424. String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
  425. return (result != null) ? result : "";
  426. }
  427. /**
  428. * Scans the default location for saving local copies of files searching for
  429. * a 'lost' file with the same full name as the {@link OCFile} received as
  430. * parameter.
  431. *
  432. * This method helps to keep linked local copies of the files when the app is uninstalled, and then
  433. * reinstalled in the device. OR after the cache of the app was deleted in system settings.
  434. *
  435. * The method is assuming that all the local changes in the file where synchronized in the past. This is dangerous,
  436. * but assuming the contrary could lead to massive unnecessary synchronizations of downloaded file after deleting
  437. * the app cache.
  438. *
  439. * This should be changed in the near future to avoid any chance of data loss, but we need to add some options
  440. * to limit hard automatic synchronizations to wifi, unless the user wants otherwise.
  441. *
  442. * @param file File to associate a possible 'lost' local file.
  443. * @param account Account holding file.
  444. */
  445. public static void searchForLocalFileInDefaultPath(OCFile file, Account account) {
  446. if (file.getStoragePath() == null && !file.isFolder()) {
  447. File f = new File(FileStorageUtils.getDefaultSavePathFor(account.name, file));
  448. if (f.exists()) {
  449. file.setStoragePath(f.getAbsolutePath());
  450. file.setLastSyncDateForData(f.lastModified());
  451. }
  452. }
  453. }
  454. }