Java tutorial
/** * 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"); } }