/**
* MementoBrowser.java
*
* Copyright 2010 Frank McCown
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* This is the Memento Browser activity which houses a customized web browser for
* performing http queries using Memento.
*
* Learn more about Memento:
* http://mementoweb.org/
*/
package dev.memento;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnDismissListener;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebHistoryItem;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
public class MementoBrowser extends Activity {
public static final String LOG_TAG = "MementoBrowser_tag";
static final int DIALOG_DATE = 0;
static final int DIALOG_ERROR = 1;
static final int DIALOG_MEMENTO_DATES = 2;
static final int DIALOG_MEMENTO_YEARS = 3;
static final int DIALOG_HELP = 4;
private String mDefaultTimegateUri;
private String[] mTimegateUris;
private WebView mWebview;
private TextView mLocation;
private Button mNextButton;
private Button mPreviousButton;
// For showing the page loading progress
private ProgressBar mProgressBar;
private TextView mDateChosenButton;
private TextView mDateDisplayedView;
private SimpleDateTime mDateChosen;
private SimpleDateTime mDateDisplayed;
private SimpleDateTime mToday;
private TimeBundle mTimeBundle;
private TimeMap mTimeMap;
private Memento mFirstMemento;
private Memento mLastMemento;
private MementoList mMementos;
private final int MAX_NUM_MEMENTOS_IN_LIST = 20;
// Used when selecting a memento
int mSelectedYear = 0;
// Used in http requests
public String mUserAgent;
// Hold favicons for certain websites. This can be removed when we figure out
// how to access the favicons for the WebView. This is an outstanding problem
// that I've solicited for help on StackOverflow:
// http://stackoverflow.com/questions/3462582/display-the-android-webviews-favicon
private HashMap<String,Bitmap> mFavicons;
// The original URL that we are visiting
private String mOriginalUrl;
// The URL currently displayed in the browser
private String mCurrentUrl;
private String mPageTitle;
// private String mRedirectUrl;
private CharSequence mErrorMessage;
// Need handler for callbacks to the UI thread
final Handler mHandler = new Handler();
// Create runnable for posting
final Runnable mUpdateResults = new Runnable() {
public void run() {
updateResultsInUi();
}
};
final Runnable mUpdateNextPrev = new Runnable() {
public void run() {
if (mErrorMessage == null) {
setEnableForNextPrevButtons();
}
else {
mNextButton.setEnabled(false);
mPreviousButton.setEnabled(false);
displayError(mErrorMessage.toString());
}
// Since making requests are over, hide progress bar
// BUT... the page may still be downloading, so don't hide
//mProgressBar.setVisibility(View.GONE);
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
mUserAgent = getApplicationContext().getText(R.string.user_agent).toString();
// Set the date and time format
SimpleDateTime.mDateFormat = android.text.format.DateFormat.getDateFormat(getApplicationContext());
SimpleDateTime.mTimeFormat = android.text.format.DateFormat.getTimeFormat(getApplicationContext());
mDateChosenButton = (Button) findViewById(R.id.dateChosen);
mDateDisplayedView = (TextView) findViewById(R.id.dateDisplayed);
// Launch the DatePicker dialog box
mDateChosenButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
showDialog(DIALOG_DATE);
}
});
// Set the current date
mToday = new SimpleDateTime();
// Handle change in orientation gracefully
if (savedInstanceState == null) {
mCurrentUrl = getApplicationContext().getText(R.string.homepage).toString();
mOriginalUrl = mCurrentUrl;
setChosenDate(mToday);
setDisplayedDate(mToday);
mMementos = new MementoList();
}
else {
mCurrentUrl = savedInstanceState.getString("mCurrentUrl");
mDateChosen = (SimpleDateTime) savedInstanceState.getSerializable("mDateChosen");
mDateDisplayed = (SimpleDateTime) savedInstanceState.getSerializable("mDateDisplayed");
setChosenDate(mDateChosen);
setDisplayedDate(mDateDisplayed);
}
mTimegateUris = getResources().getStringArray(R.array.listTimegates);
// Add some favicons of web archives used by proxy server
mFavicons = new HashMap<String, Bitmap>();
mFavicons.put("ia", BitmapFactory.decodeResource(getResources(),
R.drawable.ia_favicon));
mFavicons.put("webcite", BitmapFactory.decodeResource(getResources(),
R.drawable.webcite_favicon));
mFavicons.put("national-archives", BitmapFactory.decodeResource(getResources(),
R.drawable.national_archives_favicon));
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mProgressBar.setVisibility(View.GONE);
mLocation = (TextView) findViewById(R.id.locationEditText);
mLocation.setSelectAllOnFocus(true);
mLocation.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
// Replace title with URL when focus is lost
if (hasFocus)
mLocation.setText(mCurrentUrl);
else if (mPageTitle.length() > 0)
mLocation.setText(mPageTitle);
}
});
mLocation.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
//Log.d(LOG_TAG, "keyCode = " + keyCode + " event = " + event.getAction());
// Go to URL if user presses Go button
if (event.getAction() == KeyEvent.ACTION_DOWN &&
keyCode == KeyEvent.KEYCODE_ENTER) {
mOriginalUrl = fixUrl(mLocation.getText().toString());
// Access live version if date is today or in the future
if (mToday.compareTo(mDateChosen) <= 0) {
Log.d(LOG_TAG, "Browsing to " + mOriginalUrl);
mWebview.loadUrl(mOriginalUrl);
// Clear since we are visiting a different page in the present
mMementos.clear();
}
else
makeMementoRequests();
// Hide the virtual keyboard
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
.hideSoftInputFromWindow(mLocation.getWindowToken(), 0);
return true;
}
return false;
}
});
// TEST
/*
Context context = getBaseContext();
Drawable image = getImage(context, "http://web.archive.org/favicon.ico");
if (image == null) {
System.out.println("image is null !!");
}
else {
//image.setBounds(5, 5, 5, 5);
//ImageView imgView = new ImageView(context);
//ImageView imgView = (ImageView)findViewById(R.id.imagetest);
//imgView.setImageDrawable(image);
mLocation.setCompoundDrawablesWithIntrinsicBounds(image, null, null, null);
}
*/
mNextButton = (Button) findViewById(R.id.next);
mNextButton.setEnabled(false);
mNextButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Advance to next Memento
// This could happen if the index has not been set yet
if (mMementos.getCurrentIndex() < 0) {
int index = mMementos.getIndex(mDateDisplayed);
if (index < 0) {
Log.d(LOG_TAG, "Could not find next Memento after date " + mDateDisplayed);
return;
}
else
mMementos.setCurrentIndex(index);
}
// Locate the next Memento in the list
Memento nextMemento = mMementos.getNext();
if (nextMemento == null) {
Log.d(LOG_TAG, "Still could not find next Memento!");
Log.d(LOG_TAG, "Current index is " + mMementos.getCurrentIndex());
}
else {
SimpleDateTime date = nextMemento.getDateTime();
setChosenDate(nextMemento.getDateTime());
showToast("Time travelling to next Memento on " + mDateChosen.dateFormatted());
mDateDisplayed = date;
String redirectUrl = nextMemento.getUrl();
Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
mWebview.loadUrl(redirectUrl);
// Just in case it wasn't already enabled
mPreviousButton.setEnabled(true);
// If this is the last memento, disable button
if (mMementos.isLast(date))
mNextButton.setEnabled(false);
}
}
});
mPreviousButton = (Button) findViewById(R.id.previous);
mPreviousButton.setEnabled(false);
mPreviousButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Advance to previous Memento
// This could happen if the index has not been set yet
if (mMementos.getCurrentIndex() < 0) {
int index = mMementos.getIndex(mDateDisplayed);
if (index < 0) {
Log.d(LOG_TAG, "Could not find previous Memento before date " + mDateDisplayed);
return;
}
else
mMementos.setCurrentIndex(index);
}
// Locate the prev Memento in the list
Memento prevMemento = mMementos.getPrevious();
if (prevMemento == null) {
Log.d(LOG_TAG, "Still could not find previous Memento!");
Log.d(LOG_TAG, "Current index is " + mMementos.getCurrentIndex());
}
else {
SimpleDateTime date = prevMemento.getDateTime();
setChosenDate(date);
showToast("Time travelling to previous Memento on " + mDateChosen.dateFormatted());
mDateDisplayed = date;
String redirectUrl = prevMemento.getUrl();
Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
mWebview.loadUrl(redirectUrl);
// Just in case it wasn't already enabled
mNextButton.setEnabled(true);
// If this is the first memento, disable button
if (mMementos.isFirst(date))
mPreviousButton.setEnabled(false);
}
}
});
mWebview = (WebView) findViewById(R.id.webview);
mWebview.setWebViewClient(new MementoWebViewClient());
mWebview.setWebChromeClient(new MementoWebChromClient());
mWebview.getSettings().setJavaScriptEnabled(true);
mWebview.loadUrl(mCurrentUrl);
mWebview.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// Set focus here so focus is removed from the location text field
// which will change the URL into the page's title.
// There really should be a better way to do this, but it's a general
// problem that other developers have ran into as well:
// http://groups.google.com/group/android-developers/browse_thread/thread/9d1681a01f05e782?pli=1
if (mLocation.hasFocus()) {
mWebview.requestFocus();
return true;
}
// Hide the virtual keyboard
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
.hideSoftInputFromWindow(mLocation.getWindowToken(), 0);
return false;
}
});
//testMementos();
}
@Override
public void onResume() {
super.onResume();
// Get default timegate that was selected in the settings
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mDefaultTimegateUri = prefs.getString("defaultTimegate", mTimegateUris[0]);
Log.d(LOG_TAG, "mDefaultTimegateUri = " + mDefaultTimegateUri);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(LOG_TAG, "-- onSaveInstanceState");
outState.putSerializable("mDateChosen", mDateChosen);
outState.putSerializable("mDateDisplayed", mDateDisplayed);
outState.putString("mCurrentUrl", mCurrentUrl);
outState.putString("mOriginalUrl", mOriginalUrl);
outState.putString("mPageTitle", mPageTitle);
Log.d(LOG_TAG, "** Num of mementos: " + mMementos.size());
outState.putSerializable("mMementos", mMementos);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(LOG_TAG, "-- onRestoreInstanceState");
mOriginalUrl = savedInstanceState.getString("mOriginalUrl");
mPageTitle = savedInstanceState.getString("mPageTitle");
mMementos = (MementoList) savedInstanceState.getSerializable("mMementos");
Log.d(LOG_TAG, "** Num of mementos: " + mMementos.size());
if (mMementos != null) {
mFirstMemento = mMementos.getFirst();
mLastMemento = mMementos.getLast();
}
// Only enable buttons if viewing Mementos
if (!mToday.equalsDate(mDateChosen))
setEnableForNextPrevButtons();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
// Only enable the "List" menu if there are Memento dates to display
MenuItem item = menu.findItem(R.id.menu_list);
item.setEnabled(mMementos.size() != 0);
// Only enable "Return to Present" if we are not viewing the present
item = menu.findItem(R.id.menu_off);
item.setEnabled(!mToday.equalsDate(mDateChosen));
return true;
}
private void returnToPresent() {
mToday = new SimpleDateTime();
setChosenDate(mToday);
setDisplayedDate(mToday);
showToast("Returning to the present.");
mNextButton.setEnabled(false);
mPreviousButton.setEnabled(false);
// It's possible if the user was going back a page to
// be viewing an archived page. This is just a hack for IA pages,
// so a more comprehensive solution should be implemented.
if (mOriginalUrl.startsWith("http://web.archive.org/"))
mOriginalUrl = convertIaUrlBack(mOriginalUrl);
mWebview.loadUrl(mOriginalUrl);
mMementos.setCurrentIndex(-1);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_off:
// Turn off Memento and return to the present
returnToPresent();
return true;
case R.id.menu_settings:
startActivityForResult(new Intent(this, Settings.class), 0);
return true;
case R.id.menu_list:
// We don't want to overwhelm the user with too many choices
if (mMementos.size() > MAX_NUM_MEMENTOS_IN_LIST)
showDialog(DIALOG_MEMENTO_YEARS);
else
showDialog(DIALOG_MEMENTO_DATES);
return true;
case R.id.menu_help:
// Open a browser to the project's Help page
String url = getApplicationContext().getText(R.string.help_page).toString();
Uri uri = Uri.parse(url);
startActivity(new Intent(Intent.ACTION_VIEW, uri));
return true;
}
return false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(LOG_TAG, "--onActivityResult requestCode = " + requestCode);
if (requestCode == 0) {
// If the date/time settings were changed
SimpleDateTime.mDateFormat = android.text.format.DateFormat.getDateFormat(getApplicationContext());
SimpleDateTime.mTimeFormat = android.text.format.DateFormat.getTimeFormat(getApplicationContext());
refreshChosenDate();
refreshDisplayedDate();
}
}
public Object fetchUrl(String address) throws MalformedURLException,IOException {
URL url = new URL(address);
Object content = url.getContent();
return content;
}
private void setEnableForNextPrevButtons() {
// Making these use equalsDate instead of equals could mean that the buttons
// are disabled when there are multiple mementos with the same date at the
// front or back of the list, but there's no great way to set this otherwise
// since we are dealing with date granularity at times.
// Make prev and next enabled only if we're not viewing the first and last mementos
if (mFirstMemento != null)
mPreviousButton.setEnabled(!mFirstMemento.getDateTime().equals(mDateDisplayed));
else
Log.d(LOG_TAG, "mFirstMemento is null !!");
if (mLastMemento != null)
mNextButton.setEnabled(!mLastMemento.getDateTime().equals(mDateDisplayed));
else
Log.d(LOG_TAG, "mLastMemento is null !!");
}
private String fixUrl(String url) {
if (!url.startsWith("http://") && !url.startsWith("https://"))
url = "http://" + url;
return url;
}
private void setChosenDate(SimpleDateTime date) {
mDateChosen = date;
mDateChosenButton.setText(mDateChosen.dateFormatted());
}
/**
* Set the chosen (request) date button.
* @param day
* @param month
* @param year
*/
private void setChosenDate(int day, int month, int year) {
Log.d(LOG_TAG, "setChosenDate: " + day + ":" + month + ":" + year);
setChosenDate(new SimpleDateTime(day, month, year));
}
private void refreshChosenDate() {
mDateChosenButton.setText(mDateChosen.dateFormatted());
}
private void setDisplayedDate(SimpleDateTime date) {
mDateDisplayed = date;
mDateDisplayedView.setText(mDateDisplayed.dateFormatted());
}
private void refreshDisplayedDate() {
mDateDisplayedView.setText(mDateDisplayed.dateFormatted());
}
/**
* Start http requests on a new thread to retrieve the Mementos for the current URL.
*/
protected void makeMementoRequests() {
// Ideally we should show some type of progress bar, but probably not the browsers'
// which is downloading pages.
//mProgressBar.setVisibility(View.VISIBLE);
// Fire off a thread to do some work that we shouldn't do directly in the UI thread
Thread t = new Thread() {
public void run() {
makeHttpRequests(mOriginalUrl);
// Enable or disable Next and Previous buttons
mHandler.post(mUpdateNextPrev);
}
};
t.start();
}
/**
* Ran from other threads to update the UI. Shows a dialog box if there's an error.
*/
private void updateResultsInUi() {
// Back in the UI thread
if (mErrorMessage == null) {
// If we couldn't load the exact requested date, show the date
// that's being loaded.
if (!mDateDisplayed.equalsDate(mDateChosen)) {
showToast("Closest available is " + mDateDisplayed.dateFormatted());
this.refreshDisplayedDate();
}
}
else
showDialog(DIALOG_ERROR);
}
/**
* Make http requests using the Memento protocol to obtain a Memento or list
* of Mementos.
*/
private void makeHttpRequests(String initUrl) {
// Contact Memento proxy with chosen Accept-Datetime:
// http://mementoproxy.lanl.gov/aggr/timegate/http://example.com/
// Accept-Datetime: Tue, 24 Jul 2001 15:45:04 GMT
HttpClient httpclient = new DefaultHttpClient();
// Disable automatic redirect handling so we can process the 302 ourself
httpclient.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, false);
String url = mDefaultTimegateUri + initUrl;
HttpGet httpget = new HttpGet(url);
// Change the request date to 23:00:00 if this is the first memento.
// Otherwise we'll be out of range.
String acceptDatetime;
if (mFirstMemento != null && mFirstMemento.getDateTime().equals(mDateChosen)) {
Log.d(LOG_TAG, "Changing chosen time to 23:59 since datetime matches first Memento.");
SimpleDateTime dt = new SimpleDateTime(mDateChosen);
dt.setToLastHour();
acceptDatetime = dt.longDateFormatted();
}
else {
acceptDatetime = mDateChosen.longDateFormatted();
}
httpget.setHeader("Accept-Datetime", acceptDatetime);
httpget.setHeader("User-Agent", mUserAgent);
//Log.d(LOG_TAG, getHeadersAsString(response.getAllHeaders()));
Log.d(LOG_TAG, "Accessing: " + httpget.getURI());
Log.d(LOG_TAG, "Accept-Datetime: " + acceptDatetime);
HttpResponse response = null;
try {
response = httpclient.execute(httpget);
Log.d(LOG_TAG, "Response code = " + response.getStatusLine());
//Log.d(LOG_TAG, getHeadersAsString(response.getAllHeaders()));
} catch (ClientProtocolException e) {
mErrorMessage = "Unable to contact proxy server. ClientProtocolException exception.";
Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
return;
} catch (IOException e) {
mErrorMessage = "Unable to contact proxy server. IOException exception.";
Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
return;
} finally {
// Deallocate all system resources
httpclient.getConnectionManager().shutdown();
}
// Get back:
// 300 (TCN: list with multiple Mementos to choose from)
// or 302 (TCN: choice)
// or 404 (no Mementos for this URL)
// or 406 (TCN: list with only first and last Mementos)
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 300) {
// TODO: Implement. Right now the lanl proxy doesn't appear to be returning this
// code, so let's just ignore it for now.
Log.d(LOG_TAG, "Pick a URL from list");
}
else if (statusCode == 302) {
// Send browser to Location URL
// Note that the date/time of this memento is not given in the Location.
Header[] headers = response.getHeaders("Location");
if (headers.length == 0) {
mErrorMessage = "Sorry, but there was an unexpected error that will " +
"prevent the Memento from being displayed. Try again in 5 minutes.";
Log.e(LOG_TAG, "Error: Location header not found in response headers.");
}
else {
String redirectUrl = headers[0].getValue();
// Find out the datetime of this resource
/*SimpleDateTime d = getResourceDatetime(redirectUrl);
if (d != null)
mDateDisplayed = d;
*/
Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
mWebview.loadUrl(redirectUrl);
// We can't update the view directly since we're running
// in a thread, so use mUpdateResults to show a toast message
// if accessing a different date than what was requested.
//mHandler.post(mUpdateResults);
// Parse various Links
headers = response.getHeaders("Link");
if (headers.length == 0) {
Log.e(LOG_TAG, "Error: Link header not found in response headers.");
mErrorMessage = "Sorry, but the Memento could not be accessed. Try again in 5 minutes.";
}
else {
String linkValue = headers[0].getValue();
mTimeMap = null;
mTimeBundle = null;
// Get the datetime of this mememnto which should be supplied in the
// Link: headers
mDateDisplayed = parseCsvLinks(linkValue);
// Now that we know the date, update the UI to reflect it
mHandler.post(mUpdateResults);
if (mTimeMap != null)
if (!accessTimeMap())
mErrorMessage = "There were problems accessing the Memento's TimeMap.";
}
}
}
else if (statusCode == 404) {
mErrorMessage = "Sorry, but there are no Mementos for this URL.";
}
else if (statusCode == 406) {
// Parse various Links
Header[] headers = response.getHeaders("Link");
if (headers.length == 0) {
Log.d(LOG_TAG, "Error: Link header not found in 406 response headers.");
//mErrorMessage = "Sorry, but there was an error in retreiving this Memento.";
// The lanl proxy has it wrong. It should return 404 when the URL is not
// present, so we'll just pretend this is a 404.
mErrorMessage = "Sorry, but there are no Mementos for this URL.";
//Log.d(LOG_TAG, "BODY: " + EntityUtils.toString(response.getEntity());
}
else {
String linkValue = headers[0].getValue();
mTimeMap = null;
mTimeBundle = null;
parseCsvLinks(linkValue);
if (mTimeMap != null)
accessTimeMap();
if (mFirstMemento == null || mLastMemento == null) {
Log.e(LOG_TAG, "Could not find first or last Memento in 406 response for " + url);
mErrorMessage = "Sorry, but there was an error in retreiving this Memento.";
}
else {
Log.d(LOG_TAG, "Not available in this date range (" + mFirstMemento.getDateTimeSimple() +
" to " + mLastMemento.getDateTimeSimple() + ")");
// According to Rob Sanderson (LANL), we will only get 406 when the date is too
// early, so redirect to first Memento
mDateDisplayed = new SimpleDateTime(mFirstMemento.getDateTime());
String redirectUrl = mFirstMemento.getUrl();
Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
mWebview.loadUrl(redirectUrl);
mHandler.post(mUpdateResults);
}
}
}
else {
mErrorMessage = "Sorry, but there was an unexpected error that will " +
"prevent the Memento from being displayed. Try again in 5 minutes.";
Log.e(LOG_TAG, "Unexpected response code in makeHttpRequests = " + statusCode);
}
}
/**
* Parse the links in CSV format and return the date of the last item with rel="memento" since
* this information is needed when getting a 302 and needing to find the resource's datetime.
*
* Example data:
* <http://mementoproxy.lanl.gov/aggr/timebundle/http://www.harding.edu/fmccown/>;rel="timebundle",
* <http://www.harding.edu/fmccown/>;rel="original",
* <http://web.archive.org/web/20010724154504/www.harding.edu/fmccown/>;rel="first memento";datetime="Tue, 24 Jul 2001 15:45:04 GMT",
* <http://web.archive.org/web/20010910203350/www.harding.edu/fmccown/>;rel="memento";datetime="Mon, 10 Sep 2001 20:33:50 GMT",
*
* Another example:
* <http://mementoproxy.lanl.gov/google/timebundle/http://www.digitalpreservation.gov/>;rel="timebundle",
* <http://www.digitalpreservation.gov/>;rel="original",
* <http://mementoproxy.lanl.gov/google/timemap/link/http://www.digitalpreservation.gov/>;rel="timemap";type="application/link-format",
* <http://webcache.googleusercontent.com/search?q=cache:http://www.digitalpreservation.gov/>;rel="first last memento";datetime="Tue, 07 Sep 2010 11:54:29 GMT"
*
* @param links
* @return The datetime of the last item marked rel="memento"
*/
public SimpleDateTime parseCsvLinks(String links) {
mMementos.clear();
mFirstMemento = null;
mLastMemento = null;
SimpleDateTime date = null;
// Use a temporary list instead of the actual mMemento list so that we don't
// show a list of available dates until they have all been parsed.
MementoList tempList = new MementoList();
Log.d(LOG_TAG, "Start parsing links");
String[] linkStrings = links.split("\",");
// Place all Links into the array and then sort it based on date
for (String linkStr : linkStrings) {
// Add back "
if (!linkStr.endsWith("\""))
linkStr += "\"";
//Log.d(LOG_TAG, linkStr);
linkStr = linkStr.trim();
Link link = new Link(linkStr);
String rel = link.getRel();
if (rel.contains("memento")) {
Memento m = new Memento(link);
tempList.add(m);
//Log.d(LOG_TAG, "Added memento " + m.toString());
// Peel out all values in rel which are separated by white space
String[] items = link.getRelArray();
for (String r : items) {
r = r.toLowerCase();
//Log.d(LOG_TAG, "Processing rel [" + r + "]");
// Change the Showing date to the memento's date
//if (link.mRel.equals("first-memento"))
if (r.contains("first")) {
mFirstMemento = m;
}
if (r.contains("last")) {
mLastMemento = m;
}
if (r.equals("memento")) {
date = link.getDatetime();
}
}
}
else if (rel.equals("timemap")) {
mTimeMap = new TimeMap(link);
}
else if (rel.equals("timebundle")) {
mTimeBundle = new TimeBundle(link);
}
}
// Sorting can take a time. Since the Lanl proxy already sorts them, let's
// comment this out for now.
//Log.d(LOG_TAG, "Starting sort...");
//Collections.sort(tempList);
Log.d(LOG_TAG, "Finished parsing, found " + tempList.size() + " links");
synchronized (mMementos) {
mMementos = tempList;
}
if (date != null)
Log.d(LOG_TAG, "parseCsvLinks returning " + date.toString());
else
Log.d(LOG_TAG, "parseCsvLinks returning null");
return date;
}
/**
* Callback when the user sets the date
*/
private DatePickerDialog.OnDateSetListener mDateSetListener =
new DatePickerDialog.OnDateSetListener() {
public void onDateSet(DatePicker view, int year,
int monthOfYear, int dayOfMonth) {
setChosenDate(dayOfMonth, monthOfYear + 1, year);
if (mToday.equalsDate(mDateChosen)) {
returnToPresent();
}
else if (mToday.compareTo(mDateChosen) < 0) {
showToast("We can't see the future.\nHow about the present?");
returnToPresent();
}
else {
showToast("Time travelling to " + mDateChosen.dateFormatted());
makeMementoRequests();
}
}
};
/**
* Display toast message.
* @param message to display
*/
private void showToast(String message) {
Toast.makeText(getBaseContext(), message, Toast.LENGTH_LONG).show();
}
/**
* Show error message in a dialog box.
* @param errorMsg
*/
private void displayError(String errorMsg) {
mErrorMessage = errorMsg;
showDialog(DIALOG_ERROR);
}
/**
* Change IA URLs back to their original.
*
* Example of IA URLs:
*
* http://www.foo.org.wstub.archive.org/links.html
* http://web.archive.org/web/20071222090517/http://www.foo.org/
* http://web.archive.org/web/20070127071850rn_1/www.harding.edu/USER/fmccown/WWW/
*/
private String convertIaUrlBack(String iaUrl) {
String url = iaUrl;
url = url.replace(".wstub.archive.org", "");
String pattern = "^http://web.archive.org/.+\\d{14}.*?/";
// Create a Pattern object
Pattern r = Pattern.compile(pattern);
// Now create matcher object.
Matcher m = r.matcher(iaUrl);
if (m.find()) {
System.out.println("Found value: " + m.group(0));
url = m.replaceFirst("");
}
if (!url.startsWith("http://") && !url.startsWith("https://"))
url = "http://" + url;
return url;
}
/**
* This is used to get the favicon from the web page, but it is not working...
* the onReceivedIcon() method is never called.
*
*/
private class MementoWebChromClient extends WebChromeClient {
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
Log.d(LOG_TAG, "onReceivedIcon icon = " + icon.toString());
}
@Override
public void onReceivedTitle (WebView view, String title) {
Log.d(LOG_TAG, "onReceivedTitle title = " + title);
// Since we are replacing the URL with the page's title,
// we should swap the URL back when the user tries to enter a new URL.
if (title.length() > 0)
mLocation.setText(title);
mPageTitle = title;
}
}
/**
* Callbacks for state changes in the WebView.
*
*/
private class MementoWebViewClient extends WebViewClient {
// Note: this method is *not* called when calling WebView's loadUrl().
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d(LOG_TAG, "-- shouldOverrideUrlLoading");
// Fix partial URLs
if (!url.startsWith("http://") && !url.startsWith("https://"))
url = "http://" + url;
Log.d(LOG_TAG, "Click on link " + url);
// Only time travel if selected date is in the past!
if (mToday.compareTo(mDateChosen) <= 0) {
view.loadUrl(url);
// It's possible if the user was going back a page to
// be viewing an archived page. This is just a hack for IA pages,
// so a more comprehensive solution should be implemented.
if (url.startsWith("http://web.archive.org/"))
url = convertIaUrlBack(url);
mCurrentUrl = url;
Log.d(LOG_TAG, "mOriginalUrl = " + mOriginalUrl);
//if (!mMementos.getAssociatedUrl().equals(url)) {
// Log.d(LOG_TAG, "(1) Clearing all Mementos for new URL " + url);
// mMementos.clear();
//}
if (!mOriginalUrl.equals(url)) {
Log.d(LOG_TAG, "(2) Clearing all Mementos for new URL " + url);
mMementos.clear();
}
mOriginalUrl = url;
}
else {
// User has clicked on a URL in an archived page, so we need the original
// URL so we can find all its mementos
url = convertIaUrlBack(url);
Log.d(LOG_TAG, "Converted IA URL = " + url);
mOriginalUrl = url;
makeMementoRequests();
}
return true;
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
//displayError(description);
Log.d(LOG_TAG, "WebViewClient Error: [code=" + errorCode + "] " + description +
" [URL=" + failingUrl + "]");
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.d(LOG_TAG, "-- onPageStarted");
mCurrentUrl = url;
mProgressBar.setVisibility(View.VISIBLE);
// Show date here because it can take a long time before it finishes downloading
Log.d(LOG_TAG, "mDateDisplayed: " + mDateDisplayed.dateFormatted());
mDateDisplayedView.setText(mDateDisplayed.dateFormatted());
/* THIS WAS A HACK TO GET THE FAVICON, BUT I DO NOT SUGGEST USING IT
* SINCE THERE ARE MANY DIFFERENT METHODS USED TO PUBLISH FAVICONS, AND
* THIS METHOD IS NOT USING CACHING.
*
// Grab website favicon
Context context = view.getContext();
Drawable image = getImage(context, getBaseUrl(url) + "favicon.ico");
if (image == null) {
System.out.println("image is null !!");
}
else {
mLocation.setCompoundDrawablesWithIntrinsicBounds(image, null, null, null);
}
*/
if (favicon == null) {
// Display a favicon for some of the web archives
Log.d(LOG_TAG, "No favicon - null");
// Use our built-in favicons since this isn't working
if (url.startsWith("http://webcitation.org"))
favicon = mFavicons.get("webcite");
else if (url.startsWith("http://web.archive.org"))
favicon = mFavicons.get("ia");
else if (url.startsWith("http://webarchive.nationalarchives"))
favicon = mFavicons.get("national-archives");
else
mLocation.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
if (favicon != null) {
BitmapDrawable bd = new BitmapDrawable(favicon);
mLocation.setCompoundDrawablesWithIntrinsicBounds(bd, null, null, null);
}
}
else {
Log.d(LOG_TAG, "favicon h and w = " + favicon.getHeight() + " " + favicon.getWidth());
BitmapDrawable bd = new BitmapDrawable(favicon);
mLocation.setCompoundDrawablesWithIntrinsicBounds(bd, null, null, null);
}
/* THIS CODE IS NOT WORKING EITHER
favicon = view.getFavicon();
if (favicon == null) {
Log.d(LOG_TAG, "No favicon from getFavicon - null");
}
else {
Log.d(LOG_TAG, "getFavicon favicon h and w = " + favicon.getHeight() + " " + favicon.getWidth());
BitmapDrawable bd = new BitmapDrawable(favicon);
mLocation.setCompoundDrawablesWithIntrinsicBounds(bd, null, null, null);
}
*/
}
@Override
public void onPageFinished(WebView view, String url) {
mProgressBar.setVisibility(View.GONE);
//if (mLocation.isInEditMode()) {
if (mLocation.isSelected())
Log.d(LOG_TAG, "Editor is in edit mode, so don't erase text!");
//else
// mLocation.setText(url);
Log.d(LOG_TAG, "-- onPageFinished... mDateDisplayed: " + mDateDisplayed.dateFormatted());
/*
* THIS CODE IS NOT WORKING EITHER.
Bitmap favicon = view.getFavicon();
if (favicon == null) {
Log.d(LOG_TAG, "No favicon in onPageFinished - null");
}
else {
Log.d(LOG_TAG, "onPageFinished favicon h and w = " + favicon.getHeight() + " " + favicon.getWidth());
BitmapDrawable bd = new BitmapDrawable(favicon);
mLocation.setCompoundDrawablesWithIntrinsicBounds(bd, null, null, null);
}
*/
}
}
@Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
switch (id) {
case DIALOG_DATE:
dialog = new DatePickerDialog(this, mDateSetListener,
mDateChosen.getYear(), mDateChosen.getMonth() - 1, mDateChosen.getDay());
break;
case DIALOG_ERROR:
builder = new AlertDialog.Builder(this);
builder.setMessage("error message")
.setCancelable(false)
.setPositiveButton("OK", null);
dialog = builder.create();
break;
case DIALOG_MEMENTO_YEARS:
builder = new AlertDialog.Builder(this);
final CharSequence[] years = mMementos.getAllYears();
// Select the year of the Memento currently displayed
int selectedYear = -1;
for (int i = 0; i < years.length; i++) {
if (mDateDisplayed.getYear() == Integer.parseInt(years[i].toString())) {
selectedYear = i;
break;
}
}
builder.setSingleChoiceItems(years, selectedYear, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
dialog.dismiss();
mSelectedYear = Integer.parseInt(years[item].toString());
showDialog(DIALOG_MEMENTO_DATES);
}
});
dialog = builder.create();
// Cause the dialog to be freed whenever it is dismissed.
// This is necessary because the items are dynamic.
dialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface arg0) {
removeDialog(DIALOG_MEMENTO_YEARS);
}
});
break;
case DIALOG_MEMENTO_DATES:
builder = new AlertDialog.Builder(this);
final CharSequence[] dates;
if (mSelectedYear == 0)
dates = mMementos.getAllDates();
else
dates = mMementos.getDatesForYear(mSelectedYear);
Log.d(LOG_TAG, "Number of dates = " + dates.length);
// This shouldn't happen, but just in case.
if (dates.length == 0) {
showToast("No Mementos to select from.");
return null;
}
int selected = -1;
// Select the radio button for the current Memento if it's in the selected year.
if (mSelectedYear == 0 || mSelectedYear == mDateDisplayed.getYear()) {
int index = mMementos.getIndex(mDateDisplayed);
if (index < 0)
Log.d(LOG_TAG, "Could not find Memento in the list with date " + mDateDisplayed);
else
mMementos.setCurrentIndex(index);
Memento m = mMementos.getCurrent();
if (m != null) {
for (int i = 0; i < dates.length; i++) {
if (m.getDateTime().dateAndTimeFormatted().equals(dates[i])) {
selected = i;
break;
}
}
}
else
Log.d(LOG_TAG, "There is no current Memento");
}
Log.d(LOG_TAG, "Selected index = " + selected);
builder.setSingleChoiceItems(dates, selected, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
dialog.dismiss();
int index = mMementos.getIndex(dates[item].toString());
Memento m = mMementos.get(index);
if (m == null) {
Log.e(LOG_TAG, "Could not find Memento with date " + mDateChosen + ".");
displayError("The date selected could not be accessed. Please select another.");
}
else {
// Display this Memento
Log.d(LOG_TAG, "index for [" + dates[item] + "] is " + index);
SimpleDateTime d = m.getDateTime();
setChosenDate(d);
if (index == mMementos.getCurrentIndex()) {
showToast("Memento is already displayed.");
}
else {
mMementos.setCurrentIndex(index);
showToast("Time travelling to " + mDateChosen.dateFormatted());
// Find the Memento URL for the selected date
mDateDisplayed = new SimpleDateTime(mDateChosen);
String redirectUrl = m.getUrl();
Log.d(LOG_TAG, "Sending browser to " + redirectUrl);
mWebview.loadUrl(redirectUrl);
setEnableForNextPrevButtons();
}
}
}
});
dialog = builder.create();
// Cause the dialog to be freed whenever it is dismissed.
// This is necessary because the items are dynamic. I couldn't find
// a better way to solve this problem.
dialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface arg0) {
removeDialog(DIALOG_MEMENTO_DATES);
}
});
break;
case DIALOG_HELP:
Context context = getApplicationContext();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.about_dialog, null);
builder.setView(layout);
builder.setPositiveButton("OK", null);
dialog = builder.create();
break;
}
return dialog;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
switch (id) {
case DIALOG_DATE:
// To fix a bug described here: http://www.zunisoft.com/?p=1140
DatePickerDialog dlg = (DatePickerDialog) dialog;
DateFormat longDateFormat = new SimpleDateFormat("EEEE, MMMM d, yyyy");
dlg.setTitle(longDateFormat.format(mDateChosen.getDate()));
dlg.updateDate(mDateChosen.getYear(), mDateChosen.getMonth() - 1,
mDateChosen.getDay());
break;
case DIALOG_ERROR:
AlertDialog ad = (AlertDialog) dialog;
ad.setMessage(mErrorMessage);
mErrorMessage = null;
break;
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebview.canGoBack()) {
// Get the previous URL to update our internal copy
WebBackForwardList list = mWebview.copyBackForwardList();
int curr = list.getCurrentIndex();
WebHistoryItem item = list.getItemAtIndex(curr - 1);
Bitmap favicon = item.getFavicon();
if (favicon == null)
Log.d(LOG_TAG, "No favicon in WebHistoryItem - null");
else
Log.d(LOG_TAG, "WebHistoryItem favicon W = " + favicon.getWidth());
mOriginalUrl = item.getUrl();
Log.d(LOG_TAG, "GO BACK TO " + mOriginalUrl);
mWebview.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
/**
* Retrieve the TimeMap from the Web and parse out the Mementos.
* Currently this only recognizes TimeMaps using CSV formats.
* Other formats to be implemented: RDF/XML, N3, and HTML.
* @return true if TimeMap was successfully retreived, false otherwise.
*/
private boolean accessTimeMap() {
HttpClient httpclient = new DefaultHttpClient();
String url = mTimeMap.getUrl();
HttpGet httpget = new HttpGet(url);
httpget.setHeader("User-Agent", mUserAgent);
Log.d(LOG_TAG, "Accessing TimeMap: " + httpget.getURI());
HttpResponse response = null;
try {
response = httpclient.execute(httpget);
Log.d(LOG_TAG, "Response code = " + response.getStatusLine());
//Log.d(LOG_TAG, getHeadersAsString(response.getAllHeaders()));
} catch (ClientProtocolException e) {
Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
return false;
} catch (IOException e) {
Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
return false;
}
// Should get back 200
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
// See if MIME type is the same as Type
Header type = response.getFirstHeader("Content-Type");
if (type == null)
Log.w(LOG_TAG, "Could not find the Content-Type for " + url);
else if (!type.getValue().contains(mTimeMap.getType()))
Log.w(LOG_TAG, "Content-Type is [" + type.getValue() + "] but TimeMap type is [" +
mTimeMap.getType() + "] for " + url);
// Timemap MUST be "application/link-format", but leave csv for
// backwards-compatibility with earlier Memento implementations
if (mTimeMap.getType().equals("text/csv") ||
mTimeMap.getType().equals("application/link-format")) {
try {
String responseBody = EntityUtils.toString(response.getEntity());
parseCsvLinks(responseBody);
} catch (ParseException e) {
Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
return false;
} catch (IOException e) {
Log.e(LOG_TAG, getExceptionStackTraceAsString(e));
return false;
}
}
else {
Log.e(LOG_TAG, "Unable to handle TimeMap type " + mTimeMap.getType());
return false;
}
}
else {
Log.d(LOG_TAG, "Unexpected response code in accessTimeMap = " + statusCode);
return false;
}
// Deallocate all system resources
httpclient.getConnectionManager().shutdown();
return true;
}
public static String getExceptionStackTraceAsString(Exception exception) {
StringWriter sw = new StringWriter();
exception.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
/**
* Purely for testing.
*/
@SuppressWarnings("unused")
private void testMementos() {
String[] urls = {
"http://www.foo.org.wstub.archive.org/links.html",
"http://web.archive.org/web/20071222090517/http://www.foo.org/",
"http://web.archive.org/web/20070127071850rn_1/www.harding.edu/USER/fmccown/WWW/"
};
for (String u : urls) {
System.out.println("Convert " + u + " to " + convertIaUrlBack(u));
}
SimpleDateTime date = new SimpleDateTime();
System.out.println("date = " + date);
SimpleDateTime date2 = new SimpleDateTime();
System.out.println("date2 = " + date2);
int comp = date.compareTo(date2);
System.out.println("compareTo = " + comp);
date = new SimpleDateTime(31, 12, 2010);
date.setDateFormat(android.text.format.DateFormat.getDateFormat(getApplicationContext()));
date.setTimeFormat(android.text.format.DateFormat.getTimeFormat(getApplicationContext()));
System.out.println("date formatted = " + date.dateFormatted());
System.out.println("date and time formatted = " + date.dateAndTimeFormatted());
System.out.println("long formatted = " + date.longDateFormatted());
//System.exit(0);
//this.finish();
String url = "<http://web.archive.org/web/20010910203350/www.harding.edu/fmccown/>;rel=\"memento\";datetime=\"Mon, 10 Sep 2001 20:33:50 GMT\"";
Memento m1 = new Memento(new Link(url));
System.out.println("m1=" + m1.toString());
System.out.println("getDateTimeString: " + m1.getDateTimeString());
System.out.println("getDateTimeSimple: " + m1.getDateTimeSimple());
Memento m2 = new Memento(new Link(url));
System.out.println("m2=" + m2.toString());
System.out.println("getDateTimeString: " + m2.getDateTimeString());
System.out.println("getDateTimeSimple: " + m2.getDateTimeSimple());
System.out.println("\ncompare m1,m2: " + m1.compareTo(m2));
System.out.println("\ncompare m2,m1: " + m2.compareTo(m1));
String newDatetime = "Sun, 09 Sep 2001 20:33:50 GMT";
m2.setDateTime(newDatetime);
System.out.println("getDateTimeString: " + m2.getDateTimeString());
System.out.println("getDateTimeSimple: " + m2.getDateTimeSimple());
System.out.println("\ncompare m1,m2: " + m1.compareTo(m2));
System.out.println("\ncompare m2,m1: " + m2.compareTo(m1));
m2.getDateTime().setToLastHour();
System.out.println("New value for getDateTimeString: " + m2.getDateTimeString());
String links =
"<http://mementoproxy.lanl.gov/aggr/timebundle/http://www.harding.edu/fmccown/>;rel=\"timebundle\"," +
"<http://www.harding.edu/fmccown/>;rel=\"original\",<http://mementoproxy.lanl.gov/aggr/timemap/link/http://www.harding.edu/fmccown/>;rel=\"timemap\";type=\"text/csv\"," +
"<http://web.archive.org/web/20010724154504/www.harding.edu/fmccown/>;rel=\"first prev memento\";datetime=\"Tue, 24 Jul 2001 15:45:04 GMT\"," +
"<http://web.archive.org/web/20071222090517/www.harding.edu/fmccown/>;rel=\"last memento\";datetime=\"Sat, 22 Dec 2007 09:05:17 GMT\"," +
"<http://web.archive.org/web/20020104194811/www.harding.edu/fmccown/>;rel=\"next memento\";datetime=\"Fri, 04 Jan 2002 19:48:11 GMT\"," +
"<http://web.archive.org/web/20010910203350/www.harding.edu/fmccown/>;rel=\"memento\";datetime=\"Mon, 10 Sep 2001 20:33:50 GMT\"," +
"<http://webcache.googleusercontent.com/search?q=cache:http://www.digitalpreservation.gov/>;rel=\"first last memento\";datetime=\"Tue, 07 Sep 2010 11:54:29 GMT\"";
parseCsvLinks(links);
mMementos.displayAll();
System.exit(0);
System.out.println(mTimeMap.toString());
System.out.println(mTimeBundle.toString());
System.out.println("\nAll years:");
for (CharSequence year : mMementos.getAllYears()) {
System.out.println(year);
}
System.out.println("\nAll for 2001:");
for (CharSequence year : mMementos.getDatesForYear(2001)) {
System.out.println(year);
}
System.out.println("\nAll for 2000:");
for (CharSequence year : mMementos.getDatesForYear(2000)) {
System.out.println(year);
}
//date = getResourceDatetimeForWebcite("http://webcitation.org/query?id=1218127693715930");
//System.out.println("Date returned from getResourceDatetimeForWebcite: " + date.toString());
//accessTimeMap();
}
}
|