FileStorageUtils.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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. package com.owncloud.android.utils;
  20. import android.accounts.Account;
  21. import android.net.Uri;
  22. import android.util.Log;
  23. import android.webkit.MimeTypeMap;
  24. import com.owncloud.android.MainApp;
  25. import com.owncloud.android.datamodel.FileDataStorageManager;
  26. import com.owncloud.android.datamodel.OCFile;
  27. import com.owncloud.android.lib.common.utils.Log_OC;
  28. import com.owncloud.android.lib.resources.files.RemoteFile;
  29. import java.io.File;
  30. import java.io.FileInputStream;
  31. import java.io.FileOutputStream;
  32. import java.io.IOException;
  33. import java.io.InputStream;
  34. import java.io.OutputStream;
  35. import java.text.DateFormat;
  36. import java.text.SimpleDateFormat;
  37. import java.util.Collections;
  38. import java.util.Date;
  39. import java.util.List;
  40. import java.util.Locale;
  41. import java.util.TimeZone;
  42. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  43. /**
  44. * Static methods to help in access to local file system.
  45. */
  46. public final class FileStorageUtils {
  47. private static final String TAG = FileStorageUtils.class.getSimpleName();
  48. public static final String PATTERN_YYYY_MM = "yyyy/MM/";
  49. private FileStorageUtils() {
  50. // utility class -> private constructor
  51. }
  52. /**
  53. * Get local owncloud storage path for accountName.
  54. */
  55. public static String getSavePath(String accountName) {
  56. return MainApp.getStoragePath()
  57. + File.separator
  58. + MainApp.getDataFolder()
  59. + File.separator
  60. + Uri.encode(accountName, "@");
  61. // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names,
  62. // that can be in the accountName since 0.1.190B
  63. }
  64. /**
  65. * Get local path where OCFile file is to be stored after upload. That is,
  66. * corresponding local path (in local owncloud storage) to remote uploaded
  67. * file.
  68. */
  69. public static String getDefaultSavePathFor(String accountName, OCFile file) {
  70. return getSavePath(accountName) + file.getDecryptedRemotePath();
  71. }
  72. /**
  73. * Get absolute path to tmp folder inside datafolder in sd-card for given accountName.
  74. */
  75. public static String getTemporalPath(String accountName) {
  76. return MainApp.getStoragePath()
  77. + File.separator
  78. + MainApp.getDataFolder()
  79. + File.separator
  80. + "tmp"
  81. + File.separator
  82. + Uri.encode(accountName, "@");
  83. // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names,
  84. // that can be in the accountName since 0.1.190B
  85. }
  86. /**
  87. * Optimistic number of bytes available on sd-card. accountName is ignored.
  88. *
  89. * @return Optimistic number of available bytes (can be less)
  90. */
  91. public static long getUsableSpace() {
  92. File savePath = new File(MainApp.getStoragePath());
  93. return savePath.getUsableSpace();
  94. }
  95. /**
  96. * Returns the a string like 2016/08/ for the passed date. If date is 0 an empty
  97. * string is returned
  98. *
  99. * @param date: date in microseconds since 1st January 1970
  100. * @return string: yyyy/mm/
  101. */
  102. private static String getSubpathFromDate(long date, Locale currentLocale) {
  103. if (date == 0) {
  104. return "";
  105. }
  106. Date d = new Date(date);
  107. DateFormat df = new SimpleDateFormat(PATTERN_YYYY_MM, currentLocale);
  108. df.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
  109. return df.format(d);
  110. }
  111. /**
  112. * Returns the InstantUploadFilePath on the nextcloud instance
  113. *
  114. * @param fileName complete file name
  115. * @param dateTaken: Time in milliseconds since 1970 when the picture was taken.
  116. * @return instantUpload path, eg. /Camera/2017/01/fileName
  117. */
  118. public static String getInstantUploadFilePath(Locale current,
  119. String remotePath,
  120. String fileName,
  121. long dateTaken,
  122. Boolean subfolderByDate) {
  123. String subPath = "";
  124. if (subfolderByDate) {
  125. subPath = getSubpathFromDate(dateTaken, current);
  126. }
  127. return remotePath + OCFile.PATH_SEPARATOR + subPath + (fileName == null ? "" : fileName);
  128. }
  129. public static String getParentPath(String remotePath) {
  130. String parentPath = new File(remotePath).getParent();
  131. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
  132. return parentPath;
  133. }
  134. /**
  135. * Creates and populates a new {@link OCFile} object with the data read from the server.
  136. *
  137. * @param remote remote file read from the server (remote file or folder).
  138. * @return New OCFile instance representing the remote resource described by remote.
  139. */
  140. public static OCFile fillOCFile(RemoteFile remote) {
  141. OCFile file = new OCFile(remote.getRemotePath());
  142. file.setCreationTimestamp(remote.getCreationTimestamp());
  143. if (MimeType.DIRECTORY.equalsIgnoreCase(remote.getMimeType())) {
  144. file.setFileLength(remote.getSize());
  145. } else {
  146. file.setFileLength(remote.getLength());
  147. }
  148. file.setMimetype(remote.getMimeType());
  149. file.setModificationTimestamp(remote.getModifiedTimestamp());
  150. file.setEtag(remote.getEtag());
  151. file.setPermissions(remote.getPermissions());
  152. file.setRemoteId(remote.getRemoteId());
  153. file.setFavorite(remote.getIsFavorite());
  154. if (file.isFolder()) {
  155. file.setEncrypted(remote.getIsEncrypted());
  156. }
  157. file.setMountType(remote.getMountType());
  158. return file;
  159. }
  160. /**
  161. * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}.
  162. *
  163. * @param ocFile OCFile
  164. * @return New RemoteFile instance representing the resource described by ocFile.
  165. */
  166. public static RemoteFile fillRemoteFile(OCFile ocFile) {
  167. RemoteFile file = new RemoteFile(ocFile.getRemotePath());
  168. file.setCreationTimestamp(ocFile.getCreationTimestamp());
  169. file.setLength(ocFile.getFileLength());
  170. file.setMimeType(ocFile.getMimeType());
  171. file.setModifiedTimestamp(ocFile.getModificationTimestamp());
  172. file.setEtag(ocFile.getEtag());
  173. file.setPermissions(ocFile.getPermissions());
  174. file.setRemoteId(ocFile.getRemoteId());
  175. file.setFavorite(ocFile.isFavorite());
  176. return file;
  177. }
  178. public static List<OCFile> sortOcFolderDescDateModified(List<OCFile> files) {
  179. final int multiplier = -1;
  180. Collections.sort(files, (o1, o2) -> {
  181. @SuppressFBWarnings(value = "Bx", justification = "Would require stepping up API level")
  182. Long obj1 = o1.getModificationTimestamp();
  183. return multiplier * obj1.compareTo(o2.getModificationTimestamp());
  184. });
  185. return FileSortOrder.sortCloudFilesByFavourite(files);
  186. }
  187. /**
  188. * Local Folder size.
  189. *
  190. * @param dir File
  191. * @return Size in bytes
  192. */
  193. public static long getFolderSize(File dir) {
  194. if (dir.exists() && dir.isDirectory()) {
  195. File[] files = dir.listFiles();
  196. if (files != null) {
  197. long result = 0;
  198. for (File f : files) {
  199. if (f.isDirectory()) {
  200. result += getFolderSize(f);
  201. } else {
  202. result += f.length();
  203. }
  204. }
  205. return result;
  206. }
  207. }
  208. return 0;
  209. }
  210. /**
  211. * Mimetype String of a file.
  212. *
  213. * @param path the file path
  214. * @return the mime type based on the file name
  215. */
  216. public static String getMimeTypeFromName(String path) {
  217. String extension = "";
  218. int pos = path.lastIndexOf('.');
  219. if (pos >= 0) {
  220. extension = path.substring(pos + 1);
  221. }
  222. String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase(Locale.ROOT));
  223. return (result != null) ? result : "";
  224. }
  225. /**
  226. * Scans the default location for saving local copies of files searching for
  227. * a 'lost' file with the same full name as the {@link OCFile} received as
  228. * parameter.
  229. *
  230. * This method helps to keep linked local copies of the files when the app is uninstalled, and then
  231. * reinstalled in the device. OR after the cache of the app was deleted in system settings.
  232. *
  233. * The method is assuming that all the local changes in the file where synchronized in the past. This is dangerous,
  234. * but assuming the contrary could lead to massive unnecessary synchronizations of downloaded file after deleting
  235. * the app cache.
  236. *
  237. * This should be changed in the near future to avoid any chance of data loss, but we need to add some options
  238. * to limit hard automatic synchronizations to wifi, unless the user wants otherwise.
  239. *
  240. * @param file File to associate a possible 'lost' local file.
  241. * @param account Account holding file.
  242. */
  243. public static void searchForLocalFileInDefaultPath(OCFile file, Account account) {
  244. if (file.getStoragePath() == null && !file.isFolder()) {
  245. File f = new File(FileStorageUtils.getDefaultSavePathFor(account.name, file));
  246. if (f.exists()) {
  247. file.setStoragePath(f.getAbsolutePath());
  248. file.setLastSyncDateForData(f.lastModified());
  249. }
  250. }
  251. }
  252. @SuppressFBWarnings(value="OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE",
  253. justification="False-positive on the output stream")
  254. public static boolean copyFile(File src, File target) {
  255. boolean ret = true;
  256. InputStream in = null;
  257. OutputStream out = null;
  258. try {
  259. in = new FileInputStream(src);
  260. out = new FileOutputStream(target);
  261. byte[] buf = new byte[1024];
  262. int len;
  263. while ((len = in.read(buf)) > 0) {
  264. out.write(buf, 0, len);
  265. }
  266. } catch (IOException ex) {
  267. ret = false;
  268. } finally {
  269. if (in != null) {
  270. try {
  271. in.close();
  272. } catch (IOException e) {
  273. Log_OC.e(TAG, "Error closing input stream during copy", e);
  274. }
  275. }
  276. if (out != null) {
  277. try {
  278. out.close();
  279. } catch (IOException e) {
  280. Log_OC.e(TAG, "Error closing output stream during copy", e);
  281. }
  282. }
  283. }
  284. return ret;
  285. }
  286. public static boolean moveFile(File sourceFile, File targetFile) throws IOException {
  287. if (copyFile(sourceFile, targetFile)) {
  288. return sourceFile.delete();
  289. } else {
  290. return false;
  291. }
  292. }
  293. public static void deleteRecursively(File file, FileDataStorageManager storageManager) {
  294. if (file.isDirectory()) {
  295. for (File child : file.listFiles()) {
  296. deleteRecursively(child, storageManager);
  297. }
  298. }
  299. storageManager.deleteFileInMediaScan(file.getAbsolutePath());
  300. file.delete();
  301. }
  302. public static void checkIfFileFinishedSaving(OCFile file) {
  303. long lastModified = 0;
  304. long lastSize = 0;
  305. File realFile = new File(file.getStoragePath());
  306. if (realFile.lastModified() != file.getModificationTimestamp() && realFile.length() != file.getFileLength()) {
  307. while (realFile.lastModified() != lastModified && realFile.length() != lastSize) {
  308. lastModified = realFile.lastModified();
  309. lastSize = realFile.length();
  310. try {
  311. Thread.sleep(1000);
  312. } catch (InterruptedException e) {
  313. Log.d(TAG, "Failed to sleep for a bit");
  314. }
  315. }
  316. }
  317. }
  318. /**
  319. * Checks and returns true if file itself or ancestor is encrypted
  320. *
  321. * @param file file to check
  322. * @param storageManager up to date reference to storage manager
  323. * @return true if file itself or ancestor is encrypted
  324. */
  325. public static boolean checkEncryptionStatus(OCFile file, FileDataStorageManager storageManager) {
  326. if (file.isEncrypted()) {
  327. return true;
  328. }
  329. while (!OCFile.ROOT_PATH.equals(file.getRemotePath())) {
  330. if (file.isEncrypted()) {
  331. return true;
  332. }
  333. file = storageManager.getFileById(file.getParentId());
  334. }
  335. return false;
  336. }
  337. }