aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/files/LocalStorage.java
blob: 1b9891387da4b7e563fbf45ce3a96c5c629dd83a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package cgeo.geocaching.files;

import cgeo.geocaching.Settings;
import cgeo.geocaching.utils.CryptUtils;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;

import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Handle local storage issues on phone and SD card.
 *
 */
public class LocalStorage {

    /** Name of the local private directory to use to hold cached information */
    public final static String cache = ".cgeo";

    /**
     * Return the primary storage cache root (external media if mounted, phone otherwise).
     *
     * @return the root of the cache directory
     */
    public static File getStorage() {
        return getStorageSpecific(false);
    }

    /**
     * Return the primary storage cache root (phone if external media is mounted, external media otherwise).
     *
     * @return the root of the cache directory
     */
    public static File getStorageSec() {
        return getStorageSpecific(true);
    }

    private static File getStorageSpecific(boolean secondary) {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ^ secondary ?
                new File(Environment.getExternalStorageDirectory(), LocalStorage.cache) :
                new File(new File(new File(Environment.getDataDirectory(), "data"), "cgeo.geocaching"), LocalStorage.cache);
    }

    /**
     * Get the guessed file extension of an URL. A file extension can contain up-to 4 characters in addition to the dot.
     *
     * @param url
     *            the relative or absolute URL
     * @return the file extension, including the leading dot, or the empty string if none could be determined
     */
    static String getExtension(final String url) {
        final String urlExt = StringUtils.substringAfterLast(url, ".");
        if (urlExt.length() > 4) {
            return "";
        } else if (urlExt.length() > 0) {
            return "." + urlExt;
        }
        return "";
    }

    /**
     * Get the primary storage cache directory for a geocode. The directory and its parents will be created if
     * necessary. A null or empty geocode will be replaced by a default value.
     *
     * @param geocode
     *            the geocode
     * @return the cache directory
     */
    public static File getStorageDir(final String geocode) {
        return buildStorageDir(getStorage(), geocode);
    }

    /**
     * Get the secondary storage cache directory for a geocode. The directory and its parents will be created if
     * necessary. A null or empty geocode will be replaced by a default value.
     *
     * @param geocode
     *            the geocode
     * @return the cache directory
     */
    public static File getStorageSecDir(final String geocode) {
        return buildStorageDir(getStorageSec(), geocode);
    }

    private static File buildStorageDir(final File base, final String geocode) {
        final File dir = new File(base, StringUtils.defaultIfEmpty(geocode, "_others"));
        dir.mkdirs();
        return dir;
    }

    /**
     * Get the primary file corresponding to a geocode and a file name or an url. If it is an url, an appropriate
     * filename will be built by hashing it. The directory structure will be created if needed.
     * A null or empty geocode will be replaced by a default value.
     *
     * @param geocode
     *            the geocode
     * @param fileNameOrUrl
     *            the file name or url
     * @param isUrl
     *            true if an url was given, false if a file name was given
     * @return the file
     */
    public static File getStorageFile(final String geocode, final String fileNameOrUrl, final boolean isUrl) {
        return buildFile(getStorageDir(geocode), fileNameOrUrl, isUrl);
    }

    /**
     * Get the secondary file corresponding to a geocode and a file name or an url. If it is an url, an appropriate
     * filename will be built by hashing it. The directory structure will be created if needed.
     * A null or empty geocode will be replaced by a default value.
     *
     * @param geocode
     *            the geocode
     * @param fileNameOrUrl
     *            the file name or url
     * @param isUrl
     *            true if an url was given, false if a file name was given
     * @return the file
     */
    public static File getStorageSecFile(final String geocode, final String fileNameOrUrl, final boolean isUrl) {
        return buildFile(getStorageSecDir(geocode), fileNameOrUrl, isUrl);
    }

    private static File buildFile(final File base, final String fileName, final boolean isUrl) {
        return new File(base, isUrl ? CryptUtils.md5(fileName) + getExtension(fileName) : fileName);
    }

    /**
     * Save an HTTP response to a file.
     *
     * @param entity
     *            the entity whose content will be saved
     * @param targetFile
     *            the target file, which will be created if necessary
     * @return true if the operation was sucessful, false otherwise
     */
    public static boolean saveEntityToFile(final HttpEntity entity, final File targetFile) {
        if (entity == null) {
            return false;
        }

        try {
            final InputStream is = entity.getContent();
            try {
                final FileOutputStream fos = new FileOutputStream(targetFile);
                try {
                    return copy(is, fos);
                } finally {
                    fos.close();
                }
            } finally {
                is.close();
            }
        } catch (IOException e) {
            Log.e(Settings.tag, "LocalStorage.saveEntityToFile", e);
        }
        return false;
    }

    /**
     * Copy a file into another. The directory structure of target file will be created if needed.
     *
     * @param source
     *            the source file
     * @param destination
     *            the target file
     * @return true if the copy happened without error, false otherwise
     */
    public static boolean copy(final File source, final File destination) {
        destination.getParentFile().mkdirs();

        InputStream input;
        OutputStream output;
        try {
            input = new FileInputStream(source);
            output = new FileOutputStream(destination);
        } catch (FileNotFoundException e) {
            Log.e(Settings.tag, "LocalStorage.copy: could not open file", e);
            return false;
        }

        boolean copyDone = copy(input, output);

        try {
            input.close();
            output.close();
        } catch (IOException e) {
            Log.e(Settings.tag, "LocalStorage.copy: could not close file", e);
            return false;
        }

        return copyDone;
    }

    private static boolean copy(final InputStream input, final OutputStream output) {
        byte[] buffer = new byte[4096];
        int length;
        try {
            while ((length = input.read(buffer)) > 0) {
                output.write(buffer, 0, length);
            }
            output.flush(); // FIXME: is that necessary?
        } catch (IOException e) {
            Log.e(Settings.tag, "LocalStorage.copy: error when copying data", e);
            return false;
        }

        return true;
    }

    /**
     * Check if an external media (SD card) is available for use.
     *
     * @return true if the external media is properly mounted
     */
    public static boolean isExternalStorageAvailable() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

}