// -*- mode: cpp; mode: fold -*-
// Description								/*{{{*/
// $Id: acqprogress.cc,v 1.24 2003/04/27 01:56:48 doogie Exp $
/* ######################################################################

   Acquire Progress - Command line progress meter 
   
   ##################################################################### */
									/*}}}*/
// Include files							/*{{{*/
#include<config.h>

#include <apt-pkg/acquire.h>
#include <apt-pkg/acquire-item.h>
#include <apt-pkg/acquire-worker.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/strutl.h>
#include <apt-pkg/error.h>

#include <apt-private/acqprogress.h>

#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <iostream>
#include <unistd.h>

#include <apti18n.h>
									/*}}}*/

using namespace std;

// AcqTextStatus::AcqTextStatus - Constructor				/*{{{*/
// ---------------------------------------------------------------------
/* */
AcqTextStatus::AcqTextStatus(unsigned int &ScreenWidth,unsigned int const Quiet) :
    pkgAcquireStatus(), ScreenWidth(ScreenWidth), ID(0), Quiet(Quiet)
{
   BlankLine[0] = 0;
   // testcases use it to disable pulses without disabling other user messages
   if (Quiet == 0 && _config->FindB("quiet::NoUpdate", false) == true)
      this->Quiet = 1;
}
									/*}}}*/
// AcqTextStatus::Start - Downloading has started			/*{{{*/
// ---------------------------------------------------------------------
/* */
void AcqTextStatus::Start()
{
   pkgAcquireStatus::Start();
   BlankLine[0] = 0;
   ID = 1;
}
									/*}}}*/
// AcqTextStatus::IMSHit - Called when an item got a HIT response	/*{{{*/
// ---------------------------------------------------------------------
/* */
void AcqTextStatus::IMSHit(pkgAcquire::ItemDesc &Itm)
{
   if (Quiet > 1)
      return;

   if (Quiet <= 0)
      cout << '\r' << BlankLine << '\r';

   cout << _("Hit ") << Itm.Description;
   cout << endl;
   Update = true;
}
									/*}}}*/
// AcqTextStatus::Fetch - An item has started to download		/*{{{*/
// ---------------------------------------------------------------------
/* This prints out the short description and the expected size */
void AcqTextStatus::Fetch(pkgAcquire::ItemDesc &Itm)
{
   Update = true;
   if (Itm.Owner->Complete == true)
      return;

   Itm.Owner->ID = ID++;

   if (Quiet > 1)
      return;

   if (Quiet <= 0)
      cout << '\r' << BlankLine << '\r';

   cout << _("Get:") << Itm.Owner->ID << ' ' << Itm.Description;
   if (Itm.Owner->FileSize != 0)
      cout << " [" << SizeToStr(Itm.Owner->FileSize) << "B]";
   cout << endl;
}
									/*}}}*/
// AcqTextStatus::Done - Completed a download				/*{{{*/
// ---------------------------------------------------------------------
/* We don't display anything... */
void AcqTextStatus::Done(pkgAcquire::ItemDesc &/*Itm*/)
{
   Update = true;
}
									/*}}}*/
// AcqTextStatus::Fail - Called when an item fails to download		/*{{{*/
// ---------------------------------------------------------------------
/* We print out the error text  */
void AcqTextStatus::Fail(pkgAcquire::ItemDesc &Itm)
{
   if (Quiet > 1)
      return;

   // Ignore certain kinds of transient failures (bad code)
   if (Itm.Owner->Status == pkgAcquire::Item::StatIdle)
      return;

   if (Quiet <= 0)
      cout << '\r' << BlankLine << '\r';

   if (Itm.Owner->Status == pkgAcquire::Item::StatDone)
   {
      cout << _("Ign ") << Itm.Description << endl;
      if (Itm.Owner->ErrorText.empty() == false &&
	    _config->FindB("Acquire::Progress::Ignore::ShowErrorText", false) == true)
	 cout << "  " << Itm.Owner->ErrorText << endl;
   }
   else
   {
      cout << _("Err ") << Itm.Description << endl;
      cout << "  " << Itm.Owner->ErrorText << endl;
   }

   Update = true;
}
									/*}}}*/
// AcqTextStatus::Stop - Finished downloading				/*{{{*/
// ---------------------------------------------------------------------
/* This prints out the bytes downloaded and the overall average line
   speed */
void AcqTextStatus::Stop()
{
   pkgAcquireStatus::Stop();
   if (Quiet > 1)
      return;

   if (Quiet <= 0)
      cout << '\r' << BlankLine << '\r' << flush;

   if (_config->FindB("quiet::NoStatistic", false) == true)
      return;

   if (FetchedBytes != 0 && _error->PendingError() == false)
      ioprintf(cout,_("Fetched %sB in %s (%sB/s)\n"),
	       SizeToStr(FetchedBytes).c_str(),
	       TimeToStr(ElapsedTime).c_str(),
	       SizeToStr(CurrentCPS).c_str());
}
									/*}}}*/
// AcqTextStatus::Pulse - Regular event pulse				/*{{{*/
// ---------------------------------------------------------------------
/* This draws the current progress. Each line has an overall percent
   meter and a per active item status meter along with an overall 
   bandwidth and ETA indicator. */
bool AcqTextStatus::Pulse(pkgAcquire *Owner)
{
   pkgAcquireStatus::Pulse(Owner);

   if (Quiet > 0)
      return true;

   enum {Long = 0,Medium,Short} Mode = Medium;

   char Buffer[sizeof(BlankLine)];
   char *End = Buffer + sizeof(Buffer);
   char *S = Buffer;
   if (ScreenWidth >= sizeof(Buffer))
      ScreenWidth = sizeof(Buffer)-1;

   // Put in the percent done
   sprintf(S,"%.0f%%", Percent);

   bool Shown = false;
   for (pkgAcquire::Worker *I = Owner->WorkersBegin(); I != 0;
	I = Owner->WorkerStep(I))
   {
      S += strlen(S);

      // There is no item running
      if (I->CurrentItem == 0)
      {
	 if (I->Status.empty() == false)
	 {
	    snprintf(S,End-S," [%s]",I->Status.c_str());
	    Shown = true;
	 }

	 continue;
      }

      Shown = true;

      // Add in the short description
      if (I->CurrentItem->Owner->ID != 0)
	 snprintf(S,End-S," [%lu %s",I->CurrentItem->Owner->ID,
		  I->CurrentItem->ShortDesc.c_str());
      else
	 snprintf(S,End-S," [%s",I->CurrentItem->ShortDesc.c_str());
      S += strlen(S);

      // Show the short mode string
      if (I->CurrentItem->Owner->ActiveSubprocess.empty() == false)
      {
	 snprintf(S,End-S, " %s", I->CurrentItem->Owner->ActiveSubprocess.c_str());
	 S += strlen(S);
      }

      // Add the current progress
      if (Mode == Long)
	 snprintf(S,End-S," %llu",I->CurrentSize);
      else
      {
	 if (Mode == Medium || I->TotalSize == 0)
	    snprintf(S,End-S," %sB",SizeToStr(I->CurrentSize).c_str());
      }
      S += strlen(S);

      // Add the total size and percent
      if (I->TotalSize > 0 && I->CurrentItem->Owner->Complete == false)
      {
	 if (Mode == Short)
	    snprintf(S,End-S," %.0f%%",
		     (I->CurrentSize*100.0)/I->TotalSize);
	 else
	    snprintf(S,End-S,"/%sB %.0f%%",SizeToStr(I->TotalSize).c_str(),
		     (I->CurrentSize*100.0)/I->TotalSize);
      }
      S += strlen(S);
      snprintf(S,End-S,"]");
   }

   // Show something..
   if (Shown == false)
      snprintf(S,End-S,_(" [Working]"));

   /* Put in the ETA and cps meter, block off signals to prevent strangeness
      during resizing */
   sigset_t Sigs,OldSigs;
   sigemptyset(&Sigs);
   sigaddset(&Sigs,SIGWINCH);
   sigprocmask(SIG_BLOCK,&Sigs,&OldSigs);

   if (CurrentCPS != 0)
   {
      char Tmp[300];
      unsigned long long ETA = (TotalBytes - CurrentBytes)/CurrentCPS;
      sprintf(Tmp," %sB/s %s",SizeToStr(CurrentCPS).c_str(),TimeToStr(ETA).c_str());
      unsigned int Len = strlen(Buffer);
      unsigned int LenT = strlen(Tmp);
      if (Len + LenT < ScreenWidth)
      {
	 memset(Buffer + Len,' ',ScreenWidth - Len);
	 strcpy(Buffer + ScreenWidth - LenT,Tmp);
      }
   }
   Buffer[ScreenWidth] = 0;
   BlankLine[ScreenWidth] = 0;
   sigprocmask(SIG_SETMASK,&OldSigs,0);

   // Draw the current status
   if (_config->FindB("Apt::Color", false) == true)
      cout << _config->Find("APT::Color::Yellow");
   if (strlen(Buffer) == strlen(BlankLine))
      cout << '\r' << Buffer << flush;
   else
      cout << '\r' << BlankLine << '\r' << Buffer << flush;
   if (_config->FindB("Apt::Color", false) == true)
      cout << _config->Find("APT::Color::Neutral") << flush;

   memset(BlankLine,' ',strlen(Buffer));
   BlankLine[strlen(Buffer)] = 0;

   Update = false;

   return true;
}
									/*}}}*/
// AcqTextStatus::MediaChange - Media need to be swapped		/*{{{*/
// ---------------------------------------------------------------------
/* Prompt for a media swap */
bool AcqTextStatus::MediaChange(string Media,string Drive)
{
   // If we do not output on a terminal and one of the options to avoid user
   // interaction is given, we assume that no user is present who could react
   // on your media change request
   if (isatty(STDOUT_FILENO) != 1 && Quiet >= 2 &&
       (_config->FindB("APT::Get::Assume-Yes",false) == true ||
	_config->FindB("APT::Get::Force-Yes",false) == true ||
	_config->FindB("APT::Get::Trivial-Only",false) == true))

      return false;

   if (Quiet <= 0)
      cout << '\r' << BlankLine << '\r';
   ioprintf(cout,_("Media change: please insert the disc labeled\n"
		   " '%s'\n"
		   "in the drive '%s' and press enter\n"),
	    Media.c_str(),Drive.c_str());

   char C = 0;
   bool bStatus = true;
   while (C != '\n' && C != '\r')
   {
      int len = read(STDIN_FILENO,&C,1);
      if(C == 'c' || len <= 0)
	 bStatus = false;
   }

   if(bStatus)
      Update = true;
   return bStatus;
}
									/*}}}*/