package dk.dr.radio.diverse; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Date; import java.util.zip.GZIPInputStream; import dk.dr.radio.diverse.App; import dk.dr.radio.diverse.Log; /** * @author Jacob Nordfalk */ public class FilCache { private static final int BUFFERSTR = 4 * 1024; private static String lagerDir; public static int byteHentetOverNetværk = 0; private static void log(String tekst) { Log.d("FilCache:" + tekst); } public static void init(File dir) { if (lagerDir != null) { return; // vi skifter ikke lager midt i det hele } lagerDir = dir.getPath(); dir.mkdirs(); try { // skjul lyd og billeder for MP3-afspillere o.lign. new File(dir, ".nomedia").createNewFile(); } catch (IOException ex) { ex.printStackTrace(); } } /** * Henter en fil fra cachen eller fra webserveren * @param url * @param ændrerSigIkke Hvis true vil cachen aldrig forsøge at kontakte serveren hvis der er en lokal fil. * God til f.eks. billeder og andre ting der ikke ændrer sig * @return Stien til hvor filen findes lokalt * @throws IOException */ public static String hentFil(String url, boolean ændrerSigIkke) throws IOException { return hentFil(url, ændrerSigIkke, false, Long.MAX_VALUE); } /** * Henter en fil fra cachen eller fra webserveren. * Sker der en netværksfejl bliver der forsøgt 2 gange mere. * Dette løser et kendt problem med at URLConnection fejler engang imellem på visse enheder. * Tjekker om data er komprimeret ("Content-Encoding: gzip") og dekomprimerer dem. * @param url * @param ændrerSigIkke Hvis true vil cachen aldrig forsøge at kontakte serveren hvis der er en lokal fil. * God til f.eks. billeder og andre ting der ikke ændrer sig * Hvis false og der er en lokal fil vil serveren blive spurgt om der er en nyere fil (IfModifiedSince) * @param brugLokalTid brug telefonens tidsstempel i stedet for serverens. * @param maxAlder maksimal alder som lokal fil må have for at kunne anvendes uden at spørge serveren. Bruges kun hvis brugLokalTid = true * @return Stien til hvor filen findes lokalt */ public static String hentFil(String url, boolean ændrerSigIkke, boolean brugLokalTid, long maxAlder) throws IOException { String cacheFilnavn = findLokaltFilnavn(url); File cacheFil = new File(cacheFilnavn); if (App.fejlsøgning) log("cacheFil lastModified " + new Date(cacheFil.lastModified()) + " for " + url); long nu = System.currentTimeMillis(); if (cacheFil.exists() && ændrerSigIkke) { if (App.fejlsøgning) log("Læser " + cacheFilnavn); return cacheFilnavn; } else { long hentHvisNyereEnd = cacheFil.lastModified(); int prøvIgen = 3; while (prøvIgen > 0) { prøvIgen = prøvIgen - 1; if (App.fejlsøgning) log("Kontakter " + url); HttpURLConnection httpForb = (HttpURLConnection) new URL(url).openConnection(); if (cacheFil.exists()) { if (brugLokalTid) { if (nu - cacheFil.lastModified() < maxAlder) { //log("Lokal fil er nyere end maxAlder, så den bruger vi"); return cacheFilnavn; } } else { httpForb.setIfModifiedSince(hentHvisNyereEnd); } } httpForb.addRequestProperty("Accept-Encoding", "gzip"); httpForb.setConnectTimeout(10000); // 10 sekunder try { httpForb.connect(); } catch (IOException e) { if (!cacheFil.exists()) { throw e; // netværksfejl - og vi har ikke en lokal kopi } log("Netværksfejl, men der er cachet kopi i " + cacheFilnavn); return cacheFilnavn; } int responseCode = 0; try { //Try-catch hack due to many exceptions.. this actually helped a lot with erroneous image loadings responseCode = httpForb.getResponseCode(); } catch (IOException e) { httpForb.disconnect(); if (prøvIgen == 0) { throw e; } continue; } if (responseCode == 400 && cacheFil.exists()) { httpForb.disconnect(); log("Netværksfejl, men der er cachet kopi i " + cacheFilnavn); return cacheFilnavn; } if (responseCode == 304) { httpForb.disconnect(); log("Der er cachet kopi i " + cacheFilnavn); return cacheFilnavn; } if (responseCode != 200) { if (prøvIgen == 0) throw new IOException(responseCode + " " + httpForb.getResponseMessage() + " for " + url); // Prøv igen log("Netværksfejl, vi venter lidt og prøver igen"); log(responseCode + " " + httpForb.getResponseMessage() + " for " + url); try { Thread.sleep(1000); // Det kan tage op mod 3 sekunder for DRs servere at svare hvis det ikke er cachet i Varnish } catch (InterruptedException ex) { } // try { Thread.sleep(100); } catch (InterruptedException ex) { } continue; } if (App.fejlsøgning) log("Henter " + url + " og gemmer i " + cacheFilnavn); InputStream is = httpForb.getInputStream(); FileOutputStream fos = new FileOutputStream(cacheFilnavn + "_tmp"); String indkodning = httpForb.getHeaderField("Content-Encoding"); if (App.fejlsøgning) Log.d("indkodning: " + indkodning); if ("gzip".equals(indkodning)) { is = new GZIPInputStream(is); // Pak data ud } kopierOgLuk(is, fos); if (App.fejlsøgning) log(httpForb.getHeaderField("Content-Length") + " blev til " + new File(cacheFilnavn).length()); cacheFil.delete(); new File(cacheFilnavn + "_tmp").renameTo(cacheFil); if (!brugLokalTid) { long lastModified = httpForb.getHeaderFieldDate("last-modified", nu); log("last-modified " + new Date(lastModified)); cacheFil.setLastModified(lastModified); } return cacheFilnavn; } } throw new IllegalStateException("Dette burde aldrig ske!"); } /** * Giver filnavn på hvor URL er gemt i cachet. * Hvis filen ikke findes i cachen vil der stadig blive returneret et filnavn. * Brug new File(FilCache.findLokaltFilnavn(url)).exists() for at afgøre om en URL findes cachet lokalt * @param url * @return Stien til hvor filen (muligvis) findes lokalt. */ public static String findLokaltFilnavn(String url) { // String cacheFilnavn = url.substring(url.lastIndexOf('/') + // 1).replace('?', '_').replace('/', '_').replace('&', '_'); // f.eks. // byvejr_dag1?by=2500&mode=long String cacheFilnavn = url.replaceFirst("http://","").replace('=', '_').replace('?', '_').replace('/', '_').replace('&', '_'); // f.eks. // byvejr_dag1?by=2500&mode=long String suf = url.substring(url.lastIndexOf('.')+1); if ("txt jpg gif png".indexOf(suf)==-1) cacheFilnavn+=".xml"; cacheFilnavn = lagerDir + "/" + cacheFilnavn; if (App.fejlsøgning) log("URL: " + url + " -> " + cacheFilnavn); return cacheFilnavn; } public static void kopierOgLuk(InputStream in, OutputStream out) throws IOException { try { byte[] b = new byte[BUFFERSTR]; int read; while ((read = in.read(b)) != -1) { out.write(b, 0, read); byteHentetOverNetværk += read; } } finally { luk(in); luk(out); } } public static void luk(Closeable stream) { if (stream != null) { try { stream.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void rydCache() { for (File f : new File(lagerDir).listFiles()) { Log.d("Sletter " + f); f.delete(); } } }