/**
* This file is a part of GetNZB
*
* GetNZB - http://code.google.com/p/getnzb
* "Android NZB Search and HellaNZB client."
*
* Copyright (C) 2010: Robin van Leeuwen (robinvanleeuwen@gmail.com)
*
* 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 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.rvl.android.getnzb;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.XPatherException;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
import com.rvl.android.getnzb.R;
import com.rvl.android.getnzb.GetNZB;
import com.rvl.android.getnzb.Tags;
public class Search extends Activity {
private DefaultHttpClient httpclient = GetNZB.httpclient;
private boolean LOGGEDIN = GetNZB.LOGGEDIN;
public static String HITLIST[][];
public boolean ENABLE_NEXTBUTTON = true;
public static String SEARCHTERM;
public static String SEARCHCATEGORY;
public static String SEARCHAGE;
public int NUMSEARCHHITS;
public int CURRENT_PAGE = 1; // Start searching on page 1
public static final int MENU_GETCART = 0;
public NZBDatabase LocalNZBMetadata = new NZBDatabase(this);
public static HashMap<String,String> SEARCHCATEGORYHASHMAP = new HashMap<String,String>();
public static HashMap<String,String> SEARCHAGEHASHMAP = new HashMap<String,String>();
protected void onCreate(Bundle savedInstanceState) {
Log.d(Tags.LOG, "- Starting search activity -");
super.onCreate(savedInstanceState);
this.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(R.layout.search);
if(!SEARCHTERM.equals("")){
new searchNZB().execute(SEARCHTERM, SEARCHCATEGORY,SEARCHAGE);
return;
}
TextView statusbar = (TextView) findViewById(R.id.statusbar);
statusbar.setText("Enter searchterm.");
createSearchCategoryMapping();
createAgeMapping();
}
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()){
case MENU_GETCART:
getcart();
return true;
}
return false;
}
public void getcart(){
}
public void btn_handler(View v){
switch(v.getId()){
case R.id.btn_search:
EditText ed = (EditText) findViewById(R.id.searchterm);
SEARCHTERM = ed.getText().toString().trim().replaceAll(" ", "+");
Spinner categorySpinner = (Spinner) findViewById(R.id.spinnerCategory);
SEARCHCATEGORY = SEARCHCATEGORYHASHMAP.get(categorySpinner.getSelectedItem().toString());
Spinner ageSpinner = (Spinner) findViewById(R.id.spinnerAge);
SEARCHAGE = SEARCHAGEHASHMAP.get(ageSpinner.getSelectedItem().toString());
Log.d(Tags.LOG,"Searching in "+SEARCHCATEGORY);
new searchNZB().execute(SEARCHTERM, SEARCHCATEGORY,SEARCHAGE);
break;
case R.id.buttonMySearches:
startMySearch();
break;
case R.id.btn_next:
if(HITLIST.length == 25){
HITLIST = null;
CURRENT_PAGE++;
new searchNZB().execute(SEARCHTERM, SEARCHCATEGORY,SEARCHAGE);
}
else{
TextView statusbar = (TextView) findViewById(R.id.statusbar);
statusbar.setText("No more matches.");
}
break;
case R.id.btn_previous:
HITLIST = null;
if(CURRENT_PAGE > 1) CURRENT_PAGE--;
new searchNZB().execute(SEARCHTERM, SEARCHCATEGORY, SEARCHAGE);
break;
case R.id.btn_backtosearch:
HITLIST = null;
CURRENT_PAGE = 1;
SEARCHTERM = "";
setContentView(R.layout.search);
TextView statusbar = (TextView) findViewById(R.id.statusbar);
statusbar.setText("Enter searchterm and select category.");
break;
}
}
// searchNZB() searches nzbs.org and reads the supplied HTML page with HTMLCleaner
// to build a list of nzb-files (links) that can be sent to the HellaNZB server.
public class searchNZB extends AsyncTask<String, Void, Void>{
ProgressDialog searchDialog = new ProgressDialog(Search.this);
private int n = -1;
protected void onPreExecute(){
this.searchDialog.setMessage("Searching on nzbs.org and building list...");
this.searchDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
this.searchDialog.show();
Log.d(Tags.LOG, "Searching nzbs.org...");
}
protected Void doInBackground(final String... args){
Log.d(Tags.LOG,"* Starting searchNZB():");
if(!LOGGEDIN){
Log.d(Tags.LOG, "Search: Not logged in...");
return null;
}
String url = "";
url = "http://www.nzbs.org/index.php?action=search&q=";
url += args[0];
url += "&catid=" + args[1];
url += "&age=" + args[2];
url += "&page="+Integer.toString(CURRENT_PAGE);
Log.d(Tags.LOG,"Constructed URL:"+url);
try {
int progresscounter = 5;
this.searchDialog.setProgress(progresscounter);
HtmlCleaner cleaner = new HtmlCleaner();
CleanerProperties props = cleaner.getProperties();
props.setAllowHtmlInsideAttributes(true);
props.setAllowMultiWordAttributes(true);
props.setRecognizeUnicodeChars(true);
props.setOmitComments(true);
Log.d(Tags.LOG,"URL:"+url.toString());
HttpGet httpget = new HttpGet(url);
HttpResponse httpresponse = httpclient.execute(httpget);
HttpEntity entity = httpresponse.getEntity();
progresscounter += 5;
this.searchDialog.setProgress(progresscounter);
Log.d(Tags.LOG,"Data rerieved, parsing items...");
String xpathLink = "";
String xpathRows = "//table[@id='nzbtable']/tbody";
TagNode node = cleaner.clean(new InputStreamReader(entity.getContent()));
Object[] nzbTable = node.evaluateXPath(xpathRows);
this.n = nzbTable.length;
Log.d(Tags.LOG,"N="+Integer.toString(this.n));
if(this.n == 0){
NUMSEARCHHITS = 0;
return null;
}
TagNode row = (TagNode) nzbTable[0];
int numhits = row.getChildren().size() - 3;
NUMSEARCHHITS = numhits;
// Check if there is a next-page...
// if so, the button is enabled in buildItemList()
// since we can't do it in this thread (non-UI-operations only)...
Log.d(Tags.LOG,"Checking if next button needs to be disabled.");
if(numhits < 25){
Log.d(Tags.LOG,"Disabling next-button");
ENABLE_NEXTBUTTON = false;
}
else{
Log.d(Tags.LOG,"Enabling next-button");
ENABLE_NEXTBUTTON = true;
}
Log.d(Tags.LOG,"Found "+Integer.toString(numhits)+" items. Adding them to list.");
this.n = numhits;
int counterincrement = 88 / numhits;
String[][] hit = new String[numhits][5];
progresscounter += 5;
this.searchDialog.setProgress(progresscounter);
xpathRows = "//table[@id='nzbtable']/tbody";
nzbTable = node.evaluateXPath(xpathRows);
Log.d(Tags.LOG,"1");
TagNode tbody = (TagNode) nzbTable[0];
Object[] tempObject;
for(int c=0;c<numhits;c++){
xpathRows = "//tr["+Integer.toString(c+3)+"]";
nzbTable = tbody.evaluateXPath(xpathRows);
Log.d(Tags.LOG,"2");
// Name
tempObject = ((TagNode)nzbTable[0]).evaluateXPath("//td[2]/b/a");
hit[c][0] = ((TagNode)tempObject[0]).getText().toString();
// Category
tempObject = ((TagNode)nzbTable[0]).evaluateXPath("//td[3]/a");
hit[c][4] = ((TagNode)tempObject[0]).getText().toString();
// Days ago
tempObject = ((TagNode)nzbTable[0]).evaluateXPath("//td[4]");
hit[c][1] = ((TagNode)tempObject[0]).getText().toString();
// Size
tempObject = ((TagNode)nzbTable[0]).evaluateXPath("//td[5]");
hit[c][2] = ((TagNode)tempObject[0]).getText().toString();
// Download link
tempObject = ((TagNode)nzbTable[0]).evaluateXPath("//td[8]/b/a");
hit[c][3] = ((TagNode)tempObject[0]).getAttributeByName("href").replaceAll("&", "&");
progresscounter += counterincrement;
this.searchDialog.setProgress(progresscounter);
}
Log.d(Tags.LOG,"Added "+Integer.toString(numhits)+" items to list.");
HITLIST = hit;
} catch (MalformedURLException e) {
Log.d(Tags.LOG,"searchNZB: malformed url exception: "+e.getMessage());
} catch (IOException e) {
Log.d(Tags.LOG,"searchNZB: IO exception: "+e.getMessage());
} catch (XPatherException e) {
Log.d(Tags.LOG,"searchNZB: XPatherException: "+e.getMessage());
}
return null;
}
protected void onPostExecute(final Void unused){
this.searchDialog.dismiss();
TextView statusbar = (TextView) findViewById(R.id.statusbar);
String status = "";
if(!LOGGEDIN){
status = "Not logged in! Check NZB account settings...";
statusbar.setText(status);
Log.d(Tags.LOG,"* searchNZB() ended.");
}
else{
Log.d(Tags.LOG,"* searchNZB() ended.");
buildItemList(NUMSEARCHHITS);
}
}
}
public void buildItemList(int numhits){
String hits[][] = HITLIST;
setContentView(R.layout.links);
String item = "";
Log.d(Tags.LOG, "* buildItemList()");
if(numhits==0){
Toast.makeText(this, "No search result found!", Toast.LENGTH_LONG);
return;
}
// -- Bind the itemlist to the itemarray with the arrayadapter
ArrayList<String> items = new ArrayList<String>();
ArrayAdapter<String> aa = new SearchResultRowAdapter(this,items);
//ArrayAdapter<String> aa = new ArrayAdapter<String>(this,com.rvl.android.getnzb.R.layout.itemslist,items);
ListView itemlist = (ListView) findViewById(R.id.itemlist01);
itemlist.setCacheColorHint(00000000);
itemlist.setAdapter(aa);
registerForContextMenu(itemlist);
itemlist.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> arg0, View v, int position,
long id) {
String pos = Integer.toString(position);
Log.d(Tags.LOG,"Sending download command for position "+pos);
new downloadfile().execute(pos);
}
});
// --
Log.d(Tags.LOG, "Building hitlist...");
for(int i=0;i<hits.length;i++){
item += hits[i][0] + "#" + hits[i][1] + "#" + hits[i][2] + "#" + hits[i][4];
items.add(item);
Log.d(Tags.LOG,"item:"+item);
item = "";
}
aa.notifyDataSetChanged();
// Enable or disable the button for next results page...
Button nextbutton = (Button) findViewById(R.id.btn_next);
nextbutton.setEnabled(ENABLE_NEXTBUTTON);
}
protected void onDestroy() {
Log.d(Tags.LOG,"Leaving search activity.");
super.onDestroy();
}
public class downloadfile extends AsyncTask<String, Void, Void>{
//private static final Object[] String a = null;
ProgressDialog sdc_dialog = new ProgressDialog(Search.this);
protected void onPreExecute(){
this.sdc_dialog.setMessage("Downloading .nzb file...");
this.sdc_dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
this.sdc_dialog.show();
}
protected Void doInBackground(final String... args){
int position = Integer.parseInt(args[0]);
try{
URI uri = new URI("http://www.nzbs.org/"+HITLIST[position][3]);
HttpGet getter = new HttpGet(uri);
getter.setHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT5.1; en-US; rv:1.8.1.20) Gecko/20081217 Firefox/2.0.0.20");
HttpResponse r = httpclient.execute(getter);
Header[] headers = r.getAllHeaders();
for(Header header: headers) Log.d(Tags.LOG, header.getName()+": "+header.getValue());
HttpEntity entity = r.getEntity();
String filename = HITLIST[position][0]+".nzb";
String age = HITLIST[position][1];
String size = HITLIST[position][2];
String category = HITLIST[position][4];
Log.d(Tags.LOG,"Inserting filename and metadata in database.");
LocalNZBMetadata.openDatabase();
// Insert filename in database.
String query = "INSERT INTO file ('_id','name') VALUES(null,'"+filename+"')";
LocalNZBMetadata.myDatabase.execSQL(query);
// Retrieve file _id for the new file.
Cursor cur = LocalNZBMetadata.myDatabase.query("file",
new String[] {"_id","name"},
"name='"+filename+"'",
null, null, null, null);
int idIndex = cur.getColumnIndex("_id");
cur.moveToFirst();
int fileId = cur.getInt(idIndex);
// Insert file metadata in database.
query = "INSERT INTO meta (_id,file_id,category,age,size) VALUES (null,'"
+fileId+"','"
+category+"','"
+age+"','"
+size+"')";
LocalNZBMetadata.myDatabase.execSQL(query);
if(entity != null){
InputStream is = entity.getContent();
Log.d(Tags.LOG, "Saving file:"+filename);
Log.d(Tags.LOG, "In directory:"+getFilesDir());
FileOutputStream out = openFileOutput(filename,Activity.MODE_WORLD_WRITEABLE);
// -- Trying for custom save directory:
// Log.d(Tags.LOG, "--- In directory:"+Environment.getExternalStorageDirectory().toString());
// FileOutputStream out = openFileOutput(filename,Activity.MODE_WORLD_WRITEABLE);
// File storageFile = new File("/mnt/sdcard/"+filename);
// FileOutputStream out = new FileOutputStream(storageFile);
// Update the DB with file metadata.
int nameIndex = cur.getColumnIndex("name");
cur.moveToFirst();
do{
Log.d(Tags.LOG,"Found name: "+cur.getString(nameIndex));
} while(cur.moveToNext());
Log.d(Tags.LOG, "Created output file...");
byte buf[] = new byte[1024];
int len;
int i=0;
while((len=is.read(buf))>0){
i++;
out.write(buf, 0, len);
}
Log.d(Tags.LOG, "Done writing (wrote "+i*1024+" bytes)...");
out.close();
is.close();
}
} catch (UnsupportedEncodingException e) {
Log.d(Tags.LOG,"Unsupported Encoding Exception: "+e.getMessage());
} catch (ClientProtocolException e) {
Log.d(Tags.LOG, "Client Protocol Exception: "+e.getMessage());
} catch (IOException e) {
Log.d(Tags.LOG, "IO Exception: "+e.getMessage());
} catch (URISyntaxException e) {
Log.d(Tags.LOG, "URI Syntax exception: "+e.getMessage());
}
LocalNZBMetadata.close();
return null;
}
protected void onPostExecute(final Void unused){
this.sdc_dialog.dismiss();
return;
}
}
public void startMySearch(){
startActivity(new Intent(this,MySearch.class));
}
public void createAgeMapping(){
SEARCHAGEHASHMAP.put("All Time", "");
SEARCHAGEHASHMAP.put("1 Day", "1");
SEARCHAGEHASHMAP.put("7 Days", "7");
SEARCHAGEHASHMAP.put("14 Days", "14");
SEARCHAGEHASHMAP.put("21 Days", "21");
SEARCHAGEHASHMAP.put("50 Days", "50");
SEARCHAGEHASHMAP.put("100 Days", "100");
SEARCHAGEHASHMAP.put("140 Days", "140");
SEARCHAGEHASHMAP.put("260 Days", "260");
SEARCHAGEHASHMAP.put("365 Days", "365");
SEARCHAGEHASHMAP.put("700 Days", "700");
}
public void createSearchCategoryMapping(){
// Create search mapping for Category Spinner.
SEARCHCATEGORYHASHMAP.put("All Categories", "0");
SEARCHCATEGORYHASHMAP.put("Movies","t2");
SEARCHCATEGORYHASHMAP.put("Movies-DVD","9");
SEARCHCATEGORYHASHMAP.put("Movies-WMV-HD","12");
SEARCHCATEGORYHASHMAP.put("Movies-x264","4");
SEARCHCATEGORYHASHMAP.put("Movies-XviD","2");
SEARCHCATEGORYHASHMAP.put("TV","t1");
SEARCHCATEGORYHASHMAP.put("TV-DVD","11");
SEARCHCATEGORYHASHMAP.put("TV-FGN","24");
SEARCHCATEGORYHASHMAP.put("TV-H264","22");
SEARCHCATEGORYHASHMAP.put("TV-x264","14");
SEARCHCATEGORYHASHMAP.put("TV-XviD","1");
SEARCHCATEGORYHASHMAP.put("XXX","t4");
SEARCHCATEGORYHASHMAP.put("XXX-DVD","13");
SEARCHCATEGORYHASHMAP.put("XXX-Pack","25");
SEARCHCATEGORYHASHMAP.put("XXX-WMV","21");
SEARCHCATEGORYHASHMAP.put("XXX-x264","23");
SEARCHCATEGORYHASHMAP.put("XXX-XviD","3");
SEARCHCATEGORYHASHMAP.put("PC","t5");
SEARCHCATEGORYHASHMAP.put("PC-0day","7");
SEARCHCATEGORYHASHMAP.put("PC-ISO","6");
SEARCHCATEGORYHASHMAP.put("PC-Mac","15");
SEARCHCATEGORYHASHMAP.put("Music","t3");
SEARCHCATEGORYHASHMAP.put("Music-MP3","5");
SEARCHCATEGORYHASHMAP.put("Music-Video","10");
SEARCHCATEGORYHASHMAP.put("Console","t6");
SEARCHCATEGORYHASHMAP.put("Console-NDS","19");
SEARCHCATEGORYHASHMAP.put("Console-PSP","16");
SEARCHCATEGORYHASHMAP.put("Console-Wii","17");
SEARCHCATEGORYHASHMAP.put("Console-XBox","8");
SEARCHCATEGORYHASHMAP.put("Console-XBox360","20");
}
}
|