/*
* Copyright (C) 2010 Josh Guilfoyle <jasta@devtcg.org>
*
* This program 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 2, 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.
*/
package org.devtcg.five.activity;
import java.io.IOException;
import java.security.MessageDigest;
import java.text.ParseException;
import java.util.regex.Pattern;
import org.devtcg.five.Constants;
import org.devtcg.five.R;
import org.devtcg.five.provider.Five;
import org.devtcg.five.provider.util.SourceItem;
import org.devtcg.five.service.CacheManager;
import org.devtcg.five.service.MetaService;
import org.devtcg.five.util.StringUtils;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class SourceAdd extends Activity
{
private static final int REQUEST_CHECK_SETTINGS = 1;
private Button mNext;
private EditText mHostname;
private EditText mPassword;
/* The source we're editing if called that way. */
private Uri mExisting;
public static void actionAddSource(Context context)
{
context.startActivity(new Intent(context, SourceAdd.class));
}
public static void actionEditSource(Context context, Uri sourceUri)
{
context.startActivity(new Intent(Intent.ACTION_EDIT, sourceUri, context, SourceAdd.class));
}
@Override
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.source_add);
mHostname = (EditText)findViewById(R.id.source_host);
mPassword = (EditText)findViewById(R.id.source_password);
mNext = (Button)findViewById(R.id.next);
mNext.setOnClickListener(mNextClick);
mHostname.requestFocus();
/* Differentiates the add versus edit cases. */
handleIntent(getIntent());
}
private void handleIntent(Intent intent)
{
if (Intent.ACTION_EDIT.equals(intent.getAction()))
{
SourceItem source = SourceItem.getInstance(this, intent.getData());
if (source == null)
throw new IllegalArgumentException("No source found at " + intent.getData());
try {
mExisting = intent.getData();
mHostname.setText(source.getHostLabel());
mPassword.setHint(R.string.existing_password);
} finally {
source.close();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data)
{
switch (requestCode)
{
case REQUEST_CHECK_SETTINGS:
if (resultCode == RESULT_OK)
{
/*
* When the source is first added, automatically initiate a
* sync and return to the settings screen to observe.
*/
if (!Intent.ACTION_EDIT.equals(getIntent().getAction()))
{
/*
* We think this is the first time Five has been
* initialized and is syncing, but maybe a previous
* instance's data is still around? Remove it just to be
* sure.
*/
try {
CacheManager.getInstance().wipeAll();
} catch (IOException e) {
Log.e(Constants.TAG, "Failed to clear " + Constants.sBaseStorageDir +
", syncing may not work properly", e);
}
/* Now begin the first time sync. */
MetaService.startSync(this);
MetaService.scheduleAutoSync(this);
}
finish();
}
break;
}
}
private OnClickListener mNextClick = new OnClickListener()
{
private void parseHostAndPopulate(ContentValues values, String host)
throws ParseException
{
int colon;
if ((colon = host.indexOf(':')) >= 0)
{
String port = host.substring(colon + 1);
if (colon == 0 || Pattern.matches("^\\d+$", port) == false)
throw new ParseException("Unable to parse hostname, expected <hostname>[:<port>]", colon);
values.put(Five.Sources.HOST, host.substring(0, colon));
values.put(Five.Sources.PORT, Integer.parseInt(port));
}
else
{
values.put(Five.Sources.HOST, host);
values.put(Five.Sources.PORT, Constants.DEFAULT_SERVER_PORT);
}
}
private String hashString(String string)
{
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(string.getBytes("UTF-8"));
return StringUtils.byteArrayToHexString(md.digest());
} catch (Exception e) {
/*
* This shouldn't happen, both exceptions that can be thrown are
* related to unsupported algorithm or encoding, but we know
* confidently that Android does support what we've requested.
*/
throw new UnsupportedOperationException(e);
}
}
private void hashPasswordAndPopulate(ContentValues values, String password)
{
values.put(Five.Sources.PASSWORD, hashString(password));
}
private void populateContentValues(ContentValues values)
throws ParseException
{
parseHostAndPopulate(values, mHostname.getText().toString().trim());
String password = mPassword.getText().toString().trim();
if (mExisting == null || password.length() > 0)
hashPasswordAndPopulate(values, password);
}
public void onClick(View v)
{
ContentValues values = new ContentValues();
try {
populateContentValues(values);
} catch (ParseException e) {
Toast.makeText(SourceAdd.this, e.getMessage(),
Toast.LENGTH_LONG).show();
return;
}
Uri uri;
if (mExisting == null)
{
uri = getContentResolver()
.insert(Five.Sources.CONTENT_URI, values);
/* If check settings fails, we'll be returned to this
* activity and the user will be clicking next multiple
* times until they get it right. We want to make sure
* we're using update() not insert() for this case. */
mExisting = uri;
}
else
{
uri = mExisting;
getContentResolver().update(uri, values, null, null);
}
/* Store the result as if success, but as a convenience for
* the user we'll go ahead and check that there is a server
* waiting for us. If not, we'll return back to this edit
* screen. */
SourceCheckSettings.actionCheckSettings(SourceAdd.this, uri, REQUEST_CHECK_SETTINGS);
}
};
}