GetDownload.java :  » Client » androidfreetorrent » com » drakewill » freetorrent » Android Open Source

Android Open Source » Client » androidfreetorrent 
androidfreetorrent » com » drakewill » freetorrent » GetDownload.java
package com.drakewill.freetorrent;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Random;

import android.app.Activity;
import android.app.ActivityGroup;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.StatFs;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.RemoteViews;
import android.widget.TextView;
import android.widget.Toast;
import atorrentapi.Constants;
import atorrentapi.DownloadManager;
import atorrentapi.TorrentFile;
import atorrentapi.TorrentProcessor;
import atorrentapi.Utils;

//DW 10-12-10 - Used to be an Activity
public class GetDownload extends ActivityGroup implements Runnable 
{
  
  //DW 12-9-10 TODO: Check for connection changes (WiFi/WiMax/3G) and enable/disable communications as appropriate.
  //TODO: Calculate and display a correct transfer rate.
  //TODO: Fix hashfails. Either track down why so many fail or force it to only request each chunk of a piece once. [new array for requested]
  
  private ProgressBar mProgress;
  private RandomAccessFile[] output_files;
  private float TOTAL = 0;
  private int PEERS;
  private int PIECES = 0;
  private int COMPLETEDPIECES = 0;
  private static final int MEGABYTE = 1024 * 1024;
  private TorrentFile t;
  public DownloadManager dm;
  private Button closebutton;
  public int dlcontinue = 1; //DW 10-15-10 Was 0, let's try enabled.
  public int n;
  
  //DW added
  private int TotalPeers = 0;
  NotificationManager nm;
  static int TorrentCount = 0;
  PowerManager PM;
  SharedPreferences readSettings;
  private boolean initializingFiles;
  private float percentInitialized;
  private int filesInitialized;
  private boolean Seeding = false;
  private int pieceNum = 0;
  private int totalPieces = 0;
  private boolean checkingPieces = false;
  private Notification update_status_bar;
  private boolean downloadOK = true; //Is it OK to download?

  @Override
  public void onCreate(Bundle savedInstanceState) 
  {
    // need to override the saved instance so they come back here until the download is done.
    //DW - what did he mean by this? 
    try
    {
    super.onCreate(savedInstanceState);
    nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    PM = (PowerManager)getSystemService(Context.POWER_SERVICE);
    readSettings = getSharedPreferences("FreeTorrent", Activity.MODE_PRIVATE);
    
    //DW 10-17-10 TODO - Check to make sure the SD Card is mounted before trying to get info on it
    }
    catch (Exception ex)
    {
      Log.e("FreeTorrent", "GetDownload - " + ex.getMessage());
    }
    
    //DW - Moving all local declaration here for clarity
    String thetorrent = null;
    boolean exists = false;
    Bundle extras = getIntent().getExtras();
    TorrentProcessor tp = new TorrentProcessor();
    final long total_length;
    ArrayList<?> name;
    long megs;
    File path = Environment.getExternalStorageDirectory();
    StatFs stat = new StatFs(path.getPath());
    long freeSpace = 0; 
    Constants.SAVEPATH = path + "/FreeTorrent/";
    boolean sufficientFreeSpace = true;
    Toast toast;
    boolean SDCardSane = true;
    
    //DW 10-18-10 - We need to check our intent more closely now
      Intent me = getIntent();
      extras = me.getExtras();
      
      //DW 10-18-10 - Intent check
      //10-22-10 TODO - these probably cause null pointer exceptions, since DM isnt likely initialized correctly
      if (me.getAction() == Intent.ACTION_MEDIA_BAD_REMOVAL ||
          me.getAction() == Intent.ACTION_MEDIA_EJECT ||
          me.getAction() == Intent.ACTION_MEDIA_REMOVED ||
          me.getAction() == Intent.ACTION_MEDIA_SHARED ||
          me.getAction() == Intent.ACTION_MEDIA_UNMOUNTED ||
          me.getAction() == Intent.ACTION_MEDIA_UNMOUNTABLE)
      {
        //We need to stop writing to the SD card and pause/stop activities for now.
        dm.killWrites();
      }
      else if (me.getAction() == Intent.ACTION_MEDIA_MOUNTED)
      {
        //We can start or restart any activities now.
        dm.resumeWrites();
      }
    
    //This is a workaround for long = (int * int) overflowing and returning invalid values
    freeSpace = stat.getBlockSize();
    freeSpace = freeSpace * stat.getAvailableBlocks();
    
    // get torrent file from intent
    if (extras != null) {
      thetorrent = extras.getString("torrent");
    }
    
    if (thetorrent != null) 
    {
      File file = new File(thetorrent);
      exists = file.exists();
    }
    if (!exists) 
    {
      //change view
      setContentView(R.layout.error);
      
      TextView text = (TextView) findViewById(R.id.TextView01);// header
      text.setText(R.string.no_file);
    } 
    else 
    {
      setContentView(R.layout.main2);
      TextView text = (TextView) findViewById(R.id.TextView01);// header
      TextView text2 = (TextView) findViewById(R.id.TextView02);// filename
      closebutton = (Button) this.findViewById(R.id.Button01);
      text.setText(R.string.file_size);

      //DW 10-13-10 - This causes a few seconds of lag. Check into optimizing this.
      t = tp.getTorrentFile(tp.parseTorrent(thetorrent));
      
      //DW 11-22-10 - Forgot to check this for null returns. Should have noticed this earlier.
      if (t == null)
      {
        //11-22-10 DW - I thought I put this block into a function somewhere. Can't find it.
        Dialog d = new Dialog(this);
           TextView tv = new TextView(this);
           tv.setPadding(5,5,5,5);
           tv.setGravity(Gravity.LEFT);
           tv.setText(R.string.invalid_file);
           Window w = d.getWindow();
           w.setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
           d.setTitle(R.string.error);
           d.setContentView(tv);
           d.show();
        
        return ;
      }

      // here's where we get the data
      total_length = t.total_length;
      name = t.name;

      megs = total_length / MEGABYTE;
      
      //DW 10/11/10 - My first change: Making sure we have the free space to download the torrent
      if ( freeSpace < t.total_length) //Compare against actual sizes, not MB size
      {
        text.setTextColor(android.graphics.Color.RED);
        closebutton.setEnabled(false);
        sufficientFreeSpace = false;
        
        toast = Toast.makeText(this, R.string.no_space, Toast.LENGTH_SHORT);
        toast.show();
      }
      
      text.setText(getString(R.string.file_size) + megs + "Mb [" + getString(R.string.free_space) + " " + (freeSpace / MEGABYTE)  + "Mb]");
      int namecount = name.size();

      text2.setText(getString(R.string.downloading) + " : " + namecount + " " + getString(R.string.files) + " \n \n");

      mProgress = (ProgressBar) findViewById(R.id.ProgressBar);

      //DW 10-19-10 - Adding check for SD card sanity
      if(!android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
      {
        text.setTextColor(android.graphics.Color.RED);
        closebutton.setEnabled(false);
        SDCardSane = false;
        
        toast = Toast.makeText(this, R.string.no_card, Toast.LENGTH_SHORT);
        toast.show();
      }

      //DW 10/11/10 - Here is where we should stop if there's insufficient space
      if (sufficientFreeSpace && SDCardSane)
      {
      final Thread downloadthread = new Thread(this);
      downloadthread.start();
      // System.out.println("Thread should be started now");

      closebutton.setOnClickListener(new OnClickListener() 
      {
        public void onClick(View v) 
        {
          //DW 11-20-10 - If 0 pieces are downloaded, delete the files.
          if (COMPLETEDPIECES == 0)
          {
            if (dm.output_files.length == 1)
            {
              try 
              {
                dm.output_files[0].close();
                File f = new File(Constants.SAVEPATH + ((String) (dm.torrent.name.get(0))));
                f.delete();
              } 
              catch (IOException e) 
              {
                // TODO Auto-generated catch block
                //e.printStackTrace();
              }
            }
            else
            {
              for (int i = 0; i < dm.output_files.length; i++) 
              {
                try 
                {
                  dm.output_files[i].close();
                  File f = new File(Constants.SAVEPATH + "/" + ((String) (dm.torrent.name.get(i))));
                  f.delete();
                } 
                catch (IOException e) 
                {
                  // TODO Auto-generated catch block
                  //e.printStackTrace();
                }
              }
            }
          }
          
          nm.cancel(0);
          System.exit(1);

          // exit and return to tab 0 ?

        }
      });
      }//if sufficientfreespace

    }// end else if for card check //DW What card check?

  }// end function on create

  public void run() 
  {
    //DW 10-12-10 - Adding notification item.
    update_status_bar = new Notification(R.drawable.icon, getString(R.string.torrent_started), System.currentTimeMillis());
    update_status_bar.flags = update_status_bar.flags | Notification.FLAG_ONGOING_EVENT;
    update_status_bar.contentView = new RemoteViews(this.getPackageName(), R.layout.notification_layout);
    Intent intent = new Intent(this, Freetorrent.class);
    PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, 0);
    update_status_bar.contentIntent = pIntent;
    nm.notify(0, update_status_bar);
    boolean Finished = false; //DW 11-2-10
    
    //Power Management
    final PowerManager.WakeLock wl = PM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "FreeTorrent");
    wl.acquire();
    Looper.prepare();
    
    //DW 10-13-10 - This runs both the creation of new files (if not present)
    //and checking the files for existing pieces downloaded. It takes a very 
    //long time, and doesn't give any feedback until we start downloading
    //11-22-10 TODO: This is faster, but needs more feedback.
    dm = new DownloadManager(t, Utils.generateID(), dlcontinue);
    
    //10-22-10 - Fixing a crash introduced in 1.5 by calling warnUser in DownloadManager instead of GetDownload.
    //We need a GUI thread and to have called onCreate() to do that correctly.
    if (dm.warnUser == true)
      warnUser("This torrent uses large sized pieces, which may run slowly or crash FreeTorrent. FreeTorrent will still attempt to download it.");
    
    if (downloadOK == false)
    {
      //cancel downloading, user decided against it.
      dm = null;
      return;      
    }
    
    //DW 10-16-10 - moved file setup out of DownloadManager and into GetDownload, so
    //the user can see the progress of setting up files and pieces.
    handler.sendEmptyMessage(0);
    PIECES = dm.nbPieces;

    //DW 10-16-10 - Calling this now, so we can see its progress.
    //DW 11-2-10 - This might not be verifying the last piece correctly,
    //Or I'm not saving the last piece correctly. one of the two.
    if (!makeFiles())
      checkExistingPieces();

    // generate a random number 5 digit number
    Random generator = new Random();
    n = generator.nextInt(12343);

    int startport = n;
    int endport = (n + 1000);

    dm.startListening(startport, endport);
    dm.startTrackerUpdate();
    dm.unchokePeers();
    TOTAL = 0;

    byte[] b = new byte[0];
    int x = 0;
    int stallCount = 0;
    while (true)  
    {
      try 
      {
        synchronized (b) 
        {
          b.wait(3000);
          //DW 10-13-10 - and finally, part 3 of shoddy background thread fix.
          if (readSettings.getBoolean("isRunning", true) == false)
          {
            ///DW 10-13-10 The user wants the entire activity to stop.
            //This will be replaced with better background thread management in a future release.
            wl.release();
            nm.cancel(0);
            System.exit(0); //11-22-10 DW - Restored this
          }
          TOTAL = dm.totaldl;

          PEERS = dm.connectedpeers;
          COMPLETEDPIECES = dm.totalcomplete;
          TotalPeers = dm.peerCount();
          
          initializingFiles = dm.initializingFiles;
          filesInitialized = dm.filesInitialized;
          percentInitialized = dm.percentInitialized;
          
          update_status_bar.contentView.setProgressBar(R.id.status_progress, 100, (int) TOTAL, false);
          update_status_bar.contentView.setTextViewText(R.id.status_text, getString(R.string.dl_progress) + ": " + TOTAL + "%");
          //DW 11-2-10 - Found why a finished torrent loops the alert. Fixed.
          if (!Finished) 
            nm.notify(0, update_status_bar);
          
          handler.sendEmptyMessage(0);

          if (x == 50) //at 100, this unchokes every 5 minutes (and optimistically unchokes once every 15. Let's drop that in half.
          {
            dm.unchokePeers();

            b.notifyAll();
            x = 0;
          }
          
          if (PEERS == 0)
          {
            stallCount++;
            if (stallCount == 10) //30 seconds of nothing.
            {
              dm.restartPeerTracker();
              stallCount = 0;
            }
          }
          
          x++;
        }
      } 
      catch (Exception e) 
      {
        e.printStackTrace();
      }
      if (dm.isComplete() && !Seeding && !Finished)
      {
        try
        {
          Finished = true;
          wl.release();
        }
        catch (Exception ex)
        {
          Log.e("FreeTorrent", "GetDownload - WakeLock Release Failed");
        }
        imdone();
      }
    }
  }

  private Handler handler = new Handler() 
{
    @Override
    public void handleMessage(Message msg) 
    {

      TextView pieces = (TextView) findViewById(R.id.pieces);
      pieces.setText(COMPLETEDPIECES + " " + getString(R.string.pieces_done) + " " +PIECES);


      TextView warning = (TextView) findViewById(R.id.TextView06);
      warning.setText(getString(R.string.downloading) + " : " + TOTAL + "%" );
      
      //DW 10-16-10 - Change button text when complete
      if (Seeding)
      {
        Button cancelButton = (Button)findViewById(R.id.Button01);
        cancelButton.setText(R.string.stop_seeding);
      }
      
      TextView text3 = (TextView) findViewById(R.id.TextView03); // file
      if (initializingFiles)
      {
        text3.setText(getString(R.string.initializing) + " " + filesInitialized + " (" + percentInitialized + "%)");
      }
      else 
      if (checkingPieces)
      {
        text3.setText(getString(R.string.check_piece) + " " + pieceNum + " / " + totalPieces);
      }
      else
      {
        mProgress.setProgress((int) TOTAL);
        text3.setText(getString(R.string.connected) + " " + PEERS + " " + getString(R.string.peers) + " (" + TotalPeers + " " + getString(R.string.available) + ")");
      }
    }
  };

  public void imdone() 
  {
    int icon = R.drawable.icon;
    CharSequence tickerText = getText(R.string.complete);
    long when = System.currentTimeMillis();

    update_status_bar = new Notification(icon, tickerText, when);

    Context context = getApplicationContext();
    CharSequence contentTitle = getText(R.string.complete);
    CharSequence contentText = getText(R.string.finished_message);
    Intent notificationIntent = new Intent(this, Freetorrent.class);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0,notificationIntent, 0);
    update_status_bar.setLatestEventInfo(context, contentTitle, contentText,  contentIntent);
    
    Seeding = true;

    nm.notify(0, update_status_bar); //DW 11-2-10 Was notification_number, decided it needs to always be 0 [I won't run multiples at once]
    handler.sendEmptyMessage(0);
  }
  
  //DW 10-16-10 - This was moved from DownloadManager
  private boolean makeFiles()
  {
    initializingFiles = true;
    boolean returnValue = true; //If all files are created, don't bother checking pieces.
    
      //DW 10-13-10 - This appears to be the major lag issue when downloading.
        String saveas = Constants.SAVEPATH; // Should be configurable
        if (dm.nbOfFiles > 1)
            saveas += dm.torrent.saveAs + "/";
        new File(saveas).mkdirs();
        for (int i = 0; i < dm.nbOfFiles; i++) 
        {  
          File temp = new File(saveas + ((String) (dm.torrent.name.get(i))));
            
          if(temp.exists())
          {
            //DW 10-14-10 TODO - This should examine the files, and mark down which pieces are 
            //already complete. Instead, it just redownloads the whole file.
            returnValue = false;
            try 
            {
          dm.output_files[i] = new RandomAccessFile(temp, "rw");
        } 
            catch (FileNotFoundException e) 
            {
          // TODO Why does this cause more crashes?
          //Log.e("FreeTorrent", "checkTempFiles - " + e.getMessage());
        } //DW This needs set here too.
          }
          else
          {
          try 
          {
            //DW 10-13-10 - Found some of the lag. RandomAccessFile is amazingly slow in Java as a whole.
            //Replacing the 2 lines below this with an alternate file write.
               // this.output_files[i] = new RandomAccessFile(temp, "rw");
                //this.output_files[i].setLength((Integer)this.torrent.length.get(i));
            
            //Now, new method: Write bytes directly to the disk, see if it's any faster.
            //At the least, we can get some feedback on progress this way.
            //Note that 512kb is completely arbitrary, and can be adjusted in the future
            initializingFiles = true;
            int j = 0; //counter
            final int WRITESIZE = 262144; //DW 10-20-10 - was 524288, trying to be more efficient.
            int fileSize = (Integer) dm.torrent.length.get(i);
            int fileRemainder = fileSize % WRITESIZE;
            byte[] fileBytes = new byte[WRITESIZE];
            FileOutputStream fos = new FileOutputStream(temp, true);
            
              for( j = 0; j < fileSize; j+= WRITESIZE)
                {
                if (fileSize < WRITESIZE)
                {
                  fileBytes = new byte[(Integer) dm.torrent.length.get(j)];
                        fos.write(fileBytes);
                        percentInitialized = (fileSize / j * WRITESIZE);
                }
                else
                {
                        fos.write(fileBytes);
                }
                }
              //If the file was over 250kb, we need one more write to make sure the file's complete
              if (fileRemainder != fileSize)
              {
              fileBytes = new byte[fileRemainder];
                    fos.write(fileBytes);
              }
              fos.close();
              fileBytes = null;
              //Now that we've written the file in a slightly more friendly way, we still need to open the RandomAccessFile.
                    this.output_files[i] = new RandomAccessFile(temp, "rw");
                    //sanity check here:
                    if ((int) this.output_files[i].length() == (Integer) dm.torrent.length.get(i))
                    {
                      //DW 10-20-10 perform real error reporting.
                      //String S = "ERROR";
                    }
                    filesInitialized++;
          }
          catch (IOException ioe) 
          {
            //DW 10-19-10 - The Log.e calls are causing a lot of crashes for some reason.
//                System.err.println("Could not create temp files");
//                ioe.printStackTrace();
            //Log.e("FreeTorrent", ioe.getMessage());
                initializingFiles = false;
            }
          catch (Exception ex)
          {
            //if (ex.getMessage() != null)
              //Log.e("FreeTorrent", ex.getMessage());
            //else
              //Log.e("FreeTorrent", "makeFiles - unknown error");
            initializingFiles = false;
          }
        }
        }
    initializingFiles = false;
    return returnValue;
  }
  
  private void checkExistingPieces()
  {
    checkingPieces = true;
    totalPieces = dm.nbPieces;
    
    if(dlcontinue==1)
        {
      byte[] testPiece;
      //DW 10-21-10 - This ran the GC every loop because of testComplete. 
      //Moved some objects here to reduce that.
          for (int i = 0; i < dm.nbPieces; i++)
          {  
            //DW 11-2-10 - The fact that this works indicates that this code is good for comparing hashes. The large number of 
            //hashfails are likely cauesd by something else.
            testPiece = dm.getPieceFromFiles(i);
            
            //DW 11-30-10 - This didn't trigger null results before 1.9.1. Did I change something that broke this?
            if (testPiece != null)
            {
              if (Utils.byteArrayToByteString(Utils.hash(testPiece)).matches(Utils.byteArrayToByteString(dm.pieceList[i].sha1)))
                //  if (dm.testComplete(i)) 
              {
                Log.d("FreeTorrent", "Piece " + i  + " is already complete");
                dm.setComplete(i, true);
                dm.isComplete.set(i, true);
                dm.isRequested.clear(i);
                dm.totalcomplete++;
                dm.totaldl = (float) (((float) (100.0)) * ((float) (dm.isComplete.cardinality())) / ((float) (dm.nbPieces)));
                dm.left -= dm.pieceList[i].getLength();
                //  DW 10-28-10 - UI Improvement.
                COMPLETEDPIECES++;
              }
            }
              pieceNum = i + 1;
            handler.sendEmptyMessage(0);
          }
        }
    checkingPieces = false;
  }
  
    //DW 10-21-10 TODO - I use this block of code a lot, but it's so useful. I should move it to Utils.
    private void warnUser(String string) 
    {
      AlertDialog.Builder builder = new AlertDialog.Builder(this);
      builder.setMessage(string)
             .setCancelable(false)
             .setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() 
             {
                 public void onClick(DialogInterface dialog, int id) {
                      //contine downloading the torrent
                   downloadOK = true;
                 }
             })
             .setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() 
             {
                 public void onClick(DialogInterface dialog, int id) {
                   //cancel this download.
                   downloadOK = false;
                      //dialog.cancel();
                 }
             });
      AlertDialog alert = builder.create();
      alert.show(); //Necessary?
  }
    
    @Override
    public void onNewIntent(Intent i)
    {
      if (i.getBooleanExtra("ConnectionPresent", true) == false)
        dm.stopTrackerUpdate();
      else
        dm.startTrackerUpdate();
    }

}// end class
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.