com.adamrosenfield.wordswithcrosses.net.DerStandardDownloader.java Source code

Java tutorial

Introduction

Here is the source code for com.adamrosenfield.wordswithcrosses.net.DerStandardDownloader.java

Source

/**
 * This file is part of Words With Crosses.
 *
 * Copyright (this file) 2014 Wolfgang Groiss
 *
 * This file is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 *
 **/

package com.adamrosenfield.wordswithcrosses.net;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.ParserConfigurationException;

import org.json.JSONException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import android.annotation.TargetApi;
import android.os.Build;

import com.adamrosenfield.wordswithcrosses.WordsWithCrossesApplication;
import com.adamrosenfield.wordswithcrosses.io.IO;
import com.adamrosenfield.wordswithcrosses.net.derstandard.CalendarDateToIdConverter;
import com.adamrosenfield.wordswithcrosses.net.derstandard.DateToIdConverter;
import com.adamrosenfield.wordswithcrosses.net.derstandard.DerStandardParser;
import com.adamrosenfield.wordswithcrosses.net.derstandard.DerStandardPuzzleMetadata;

/**
 * Downloader for derStandard.at.
 *
 * As puzzles are only available as a web application, there's some weird - and
 * easily broken if the web app changes - stuff done to actually produce PUZ files.
 *
 **/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public class DerStandardDownloader extends AbstractDownloader {
    private static final String NAME = "DerStandard.at";
    private static final String BASE_URL = "http://derstandard.at";
    private static final String PUZZLE_URL = BASE_URL + "/raetselapp/?id=";
    private static final String SOLUTION_URL = BASE_URL + "/RaetselApp/Home/GetCrosswordResult";

    private static final Pattern P_CHARSET_IN_TYPE = Pattern
            .compile("[A-Za-z0-9\\-/]+;\\s*charset=([A-Za-z0-9\\-]+)");

    private static final Logger LOG = Logger.getLogger("DerStandardDownloader");

    private final DerStandardParser parser = new DerStandardParser();
    private static final DateToIdConverter converter = new CalendarDateToIdConverter();

    public DerStandardDownloader() {
        super(BASE_URL, NAME);
    }

    public boolean isPuzzleAvailable(Calendar date) {
        return converter.getId(date) != DateToIdConverter.NONE;
    }

    @Override
    protected String createUrlSuffix(Calendar date) {
        return "";
    }

    @Override
    public void download(Calendar date) throws IOException {
        DerStandardPuzzleMetadata pm = getPuzzleByDate(date);

        if (pm == null) {
            throw new IOException("Could not find puzzle for " + date);
        }

        File targetFile = getTargetFile(pm);
        if (!targetFile.exists()) {
            try {
                downloadPuzzle(pm);
            } catch (RefreshException e) {
                LOG.log(Level.SEVERE, "Error fetching/parsing puzzle.", e);
                throw new IOException("Could not download puzzle for " + date);
            }
        }
    }

    private DerStandardPuzzleMetadata getPuzzleByDate(Calendar date) {
        int id = converter.getId(date);

        DerStandardPuzzleMetadata dspm = new DerStandardPuzzleMetadata(id, PUZZLE_URL + id, date);

        return dspm;
    }

    private void downloadPuzzle(DerStandardPuzzleMetadata pm) throws RefreshException {
        String pUrl = pm.getPuzzleUrl(BASE_URL);
        int id = pm.getId();

        boolean save = false;

        if (pUrl != null && !pm.isPuzzleAvailable()) {
            try {
                Reader r = getURLReader(getHttpConnection(pUrl));
                try {
                    InputSource input = new InputSource(r);
                    parser.parsePuzzle(pm, input);
                    save = true;
                } finally {
                    r.close();
                }
            } catch (IOException e) {
                throw new RefreshException("Fetching/Parsing puzzle for " + pUrl + ".", e);
            } catch (ParserConfigurationException e) {
                throw new RefreshException("Fetching/Parsing puzzle for " + pUrl + ".", e);
            } catch (SAXException e) {
                throw new RefreshException("Fetching/Parsing puzzle for " + pUrl + ".", e);
            }
        }

        if (pm.isPuzzleAvailable() && !pm.isSolutionAvailable()) {
            try {
                InputSource isSolution = postForSolution(id);
                if (isSolution != null) {
                    parser.parseSolution(pm, isSolution);
                    save = true;
                }
            } catch (IOException e) {
                throw new RefreshException("Fetching/Parsing solution for " + id + ".", e);
            } catch (JSONException e) {
                throw new RefreshException("Fetching/Parsing solution for " + id + ".", e);
            } catch (SAXException e) {
                throw new RefreshException("Fetching/Parsing solution for " + id + ".", e);
            }
        }

        if (save) {
            try {
                savePuzzle(pm);
            } catch (IOException ioe) {
                throw new RefreshException("Saving puzzle " + id + ".", ioe);
            }
        }
    }

    private InputSource postForSolution(int id) throws MalformedURLException, IOException {
        HttpURLConnection conn = getHttpPostConnection(SOLUTION_URL, "ExternalId=" + id);
        Reader reader = getURLReader(conn);
        return new InputSource(reader);
    }

    private void savePuzzle(DerStandardPuzzleMetadata pm) throws IOException {
        if (pm.isPuzzleAvailable()) {
            File tempFile = new File(WordsWithCrossesApplication.TEMP_DIR, getFilename(pm.getDate()));
            IO.save(pm.getPuzzle(), tempFile);

            File destFile = getTargetFile(pm);
            if (!tempFile.renameTo(destFile)) {
                throw new IOException("Failed to rename " + tempFile + " to " + destFile);
            }

        }
    }

    private File getTargetFile(DerStandardPuzzleMetadata pm) {
        File destFile = new File(WordsWithCrossesApplication.CROSSWORDS_DIR, getFilename(pm.getDate()));
        return destFile;
    }

    protected Reader getURLReader(HttpURLConnection conn)
            throws IOException, MalformedURLException, UnsupportedEncodingException {
        InputStream in = (InputStream) conn.getContent();
        String encoding = conn.getContentEncoding();
        String type = conn.getContentType();

        if (encoding == null) {
            Matcher m = P_CHARSET_IN_TYPE.matcher(type);
            if (m.matches()) {
                encoding = m.group(1);
            } else {
                encoding = "ISO-8859-1";
            }
        }

        Reader r = new InputStreamReader(in, encoding);
        return r;
    }

    private HttpURLConnection getHttpPostConnection(String url, String postData)
            throws IOException, MalformedURLException {
        HttpURLConnection conn = getHttpConnection(url);

        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        OutputStreamWriter osw = new OutputStreamWriter(conn.getOutputStream());
        try {
            osw.append(postData);
        } finally {
            osw.close();
        }

        return conn;
    }

    private HttpURLConnection getHttpConnection(String url) throws IOException, MalformedURLException {
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();

        // so we don't neccessarily get the mobile version
        conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.0; rv:8.0) Gecko/20100101 Firefox/8.0");
        conn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        return conn;
    }

    private static class RefreshException extends Exception {
        private static final long serialVersionUID = 3521756473491768245L;

        private RefreshException(String detailMessage, Throwable throwable) {
            super(detailMessage, throwable);
        }

        private RefreshException(Throwable throwable) {
            super(throwable);
        }
    }

}