Ticket #19549: 19549.patch

File 19549.patch, 9.7 KB (added by taylor.smock, 5 years ago)

Initial RFC patch

  • src/org/openstreetmap/josm/io/CachedFile.java

     
    1616import java.nio.file.Files;
    1717import java.nio.file.InvalidPathException;
    1818import java.nio.file.StandardCopyOption;
     19import java.nio.file.attribute.BasicFileAttributes;
    1920import java.security.MessageDigest;
    2021import java.security.NoSuchAlgorithmException;
    2122import java.util.ArrayList;
    22 import java.util.Arrays;
     23import java.util.Collection;
     24import java.util.Collections;
     25import java.util.EnumSet;
    2326import java.util.Enumeration;
    2427import java.util.List;
    2528import java.util.Map;
    2629import java.util.Optional;
     30import java.util.Set;
    2731import java.util.concurrent.ConcurrentHashMap;
    2832import java.util.concurrent.TimeUnit;
    2933import java.util.zip.ZipEntry;
     
    7175        IfModifiedSince
    7276    }
    7377
     78    /**
     79     * Options that may be useful to more accurately control cached files.
     80     *
     81     * @author Taylor Smock
     82     * @since xxx
     83     */
     84    public enum Options {
     85        /**
     86         * Fail quickly (~1 second)
     87         */
     88        FastFail,
     89        /**
     90         * Delete the files on graceful exit
     91         */
     92        DeleteOnExit,
     93    }
     94
    7495    protected String name;
    7596    protected long maxAge;
    7697    protected String destDir;
     
    7798    protected String httpAccept;
    7899    protected CachingStrategy cachingStrategy;
    79100
    80     private boolean fastFail;
    81101    private HttpClient activeConnection;
    82102    protected File cacheFile;
    83103    protected boolean initialized;
    84104    protected String parameter;
    85105
     106    protected final Set<Options> options = EnumSet.noneOf(Options.class);
     107
    86108    public static final long DEFAULT_MAXTIME = -1L;
    87109    public static final long DAYS = TimeUnit.DAYS.toSeconds(1); // factor to get caching time in days
    88110
     
    177199     * @param fastFail whether opening HTTP connections should fail fast
    178200     */
    179201    public void setFastFail(boolean fastFail) {
    180         this.fastFail = fastFail;
     202        if (fastFail) {
     203            options.add(Options.FastFail);
     204        } else {
     205            options.remove(Options.FastFail);
     206        }
    181207    }
    182208
    183209    /**
     
    189215        this.parameter = parameter;
    190216    }
    191217
     218    /**
     219     * Get the name of the file. If a URL parameter is set,
     220     * some characters will be deleted.
     221     *
     222     * @return The name of the file
     223     */
    192224    public String getName() {
    193225        if (parameter != null)
    194226            return name.replaceAll("%<(.*)>", "");
     
    205237        return maxAge;
    206238    }
    207239
     240    /**
     241     * The destination directory of the downloaded file
     242     * @return The destination directory. If {@code null}, a default directory
     243     * will be selected on download.
     244     */
    208245    public String getDestDir() {
    209246        return destDir;
    210247    }
     
    425462    }
    426463
    427464    private File checkLocal(URL url) throws IOException {
    428         String prefKey = getPrefKey(url, destDir);
    429465        String urlStr = url.toExternalForm();
    430466        if (parameter != null)
    431467            urlStr = urlStr.replaceAll("%<(.*)>", "");
     
    432468        long age = 0L;
    433469        long maxAgeMillis = TimeUnit.SECONDS.toMillis(maxAge);
    434470        Long ifModifiedSince = null;
    435         File localFile = null;
    436         List<String> localPathEntry = new ArrayList<>(Config.getPref().getList(prefKey));
    437471        boolean offline = NetworkManager.isOffline(urlStr);
    438         if (localPathEntry.size() == 2) {
    439             localFile = new File(localPathEntry.get(1));
    440             if (!localFile.exists()) {
    441                 localFile = null;
    442             } else {
    443                 if (maxAge == DEFAULT_MAXTIME
    444                         || maxAge <= 0 // arbitrary value <= 0 is deprecated
    445                 ) {
    446                     maxAgeMillis = TimeUnit.SECONDS.toMillis(Config.getPref().getLong("mirror.maxtime", TimeUnit.DAYS.toSeconds(7)));
    447                 }
    448                 age = System.currentTimeMillis() - Long.parseLong(localPathEntry.get(0));
    449                 if (offline || age < maxAgeMillis) {
    450                     return localFile;
    451                 } else if (NetworkManager.isOffline(OnlineResource.CACHE_UPDATES)) {
    452                     Logging.warn(OfflineAccessException.forResource(tr("Cache update for {0}", urlStr)).getMessage());
    453                     return localFile;
    454                 }
    455                 if (cachingStrategy == CachingStrategy.IfModifiedSince) {
    456                     ifModifiedSince = Long.valueOf(localPathEntry.get(0));
    457                 }
     472
     473        /** Start check for local file */
     474        boolean nullDestDir = destDir == null;
     475        if (nullDestDir) {
     476            destDir = Config.getDirs().getCacheDirectory(true).getPath();
     477        }
     478        String localPath = getFilePath(urlStr, destDir);
     479        File localFile = new File(destDir, localPath);
     480        if (!localFile.exists()) {
     481            localFile = null;
     482        }
     483        if (nullDestDir) {
     484            destDir = null;
     485        }
     486        /** End check for local file */
     487
     488        if (localFile != null && !localFile.exists()) {
     489            localFile = null;
     490        } else if (localFile != null) {
     491            if (maxAge == DEFAULT_MAXTIME
     492                    || maxAge <= 0 // arbitrary value <= 0 is deprecated
     493            ) {
     494                maxAgeMillis = TimeUnit.SECONDS.toMillis(Config.getPref().getLong("mirror.maxtime", TimeUnit.DAYS.toSeconds(7)));
    458495            }
     496
     497            BasicFileAttributes attributes = Files.readAttributes(localFile.toPath(), BasicFileAttributes.class);
     498            Long fileAge = attributes.lastModifiedTime().toMillis();
     499            age = System.currentTimeMillis() - fileAge;
     500            if (offline || age < maxAgeMillis) {
     501                return localFile;
     502            } else if (NetworkManager.isOffline(OnlineResource.CACHE_UPDATES)) {
     503                Logging.warn(OfflineAccessException.forResource(tr("Cache update for {0}", urlStr)).getMessage());
     504                return localFile;
     505            }
     506            if (cachingStrategy == CachingStrategy.IfModifiedSince) {
     507                ifModifiedSince = fileAge;
     508            }
    459509        }
    460510        if (destDir == null) {
    461511            destDir = Config.getDirs().getCacheDirectory(true).getPath();
     
    483533                url = new URL(uc);
    484534        }
    485535
    486         String a = urlStr.replaceAll("[^A-Za-z0-9_.-]", "_");
    487         String localPath = "mirror_" + a;
    488         localPath = truncatePath(destDir, localPath);
     536        localPath = getFilePath(urlStr, destDir);
    489537        destDirFile = new File(destDir, localPath + ".tmp");
    490538        try {
    491539            activeConnection = HttpClient.create(url)
     
    492540                    .setAccept(httpAccept)
    493541                    .setIfModifiedSince(ifModifiedSince == null ? 0L : ifModifiedSince)
    494542                    .setHeaders(httpHeaders);
    495             if (fastFail) {
     543            if (options.contains(Options.FastFail)) {
    496544                activeConnection.setReadTimeout(1000);
    497545            }
    498546            final HttpClient.Response con = activeConnection.connect();
    499547            if (ifModifiedSince != null && con.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
    500548                Logging.debug("304 Not Modified ({0})", urlStr);
    501                 Config.getPref().putList(prefKey,
    502                         Arrays.asList(Long.toString(System.currentTimeMillis()), localPathEntry.get(1)));
    503549                return localFile;
    504550            } else if (con.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
    505551                throw new IOException(tr("The requested URL {0} was not found", urlStr));
     
    510556            activeConnection = null;
    511557            localFile = new File(destDir, localPath);
    512558            if (PlatformManager.getPlatform().rename(destDirFile, localFile)) {
    513                 Config.getPref().putList(prefKey,
    514                         Arrays.asList(Long.toString(System.currentTimeMillis()), localFile.toString()));
     559                if (options.contains(Options.DeleteOnExit)) {
     560                    localFile.deleteOnExit();
     561                }
    515562            } else {
    516563                Logging.warn(tr("Failed to rename file {0} to {1}.",
    517564                destDirFile.getPath(), localFile.getPath()));
     
    528575        return localFile;
    529576    }
    530577
     578    private static String getFilePath(String urlStr, String destDir) {
     579        String a = urlStr.replaceAll("[^A-Za-z0-9_.-]", "_");
     580        String localPath = "mirror_" + a;
     581        return truncatePath(destDir, localPath);
     582    }
     583
    531584    private static String truncatePath(String directory, String fileName) {
    532585        if (directory.length() + fileName.length() > 255) {
    533586            // Windows doesn't support paths longer than 260, leave 5 chars as safe buffer, 4 will be used by ".tmp"
     
    586639            Utils.deleteFile(f);
    587640        }
    588641    }
     642
     643    /**
     644     * Add an option to be used prior to getting the file
     645     *
     646     * @param option The option to add
     647     * @since xxx
     648     */
     649    public void addOption(Options option) {
     650        options.add(option);
     651    }
     652
     653    /**
     654     * Remove an option to be used prior to getting the file
     655     *
     656     * @param option The option to remove
     657     * @since xxx
     658     */
     659    public void removeOption(Options option) {
     660        options.remove(option);
     661    }
     662
     663    /**
     664     * CachedFile uses options when getting the actual file.
     665     * These should generally be set prior to any action which may get a file.
     666     *
     667     * @return The options used by this CachedFile in an unmodifiable collection
     668     * @since xxx
     669     */
     670    public Collection<Options> getOptions() {
     671        return Collections.unmodifiableSet(options);
     672    }
    589673}