home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / boot / i386 / root / usr / share / YaST2 / modules / SlideShow.ycp < prev    next >
Text File  |  2006-11-29  |  50KB  |  1,898 lines

  1. /**
  2.  * Module:        SlideShow.ycp
  3.  *
  4.  * Purpose:        Slide show during package installation
  5.  *
  6.  * Author:        Stefan Hundhammer <sh@suse.de>
  7.  */
  8. {
  9.     module "SlideShow";
  10.  
  11.     textdomain "packager";
  12.  
  13.     import "Installation";
  14.     import "Label";
  15.     import "Language";
  16.     import "Stage";
  17.     import "String";
  18.     import "Wizard";
  19.  
  20.     global list<list<integer> > total_sizes_per_cd_per_src    = [];    // total sizes per inst-src: [ [42, 43, 44], [12, 13, 14] ]
  21.     global list<list<integer> > remaining_sizes_per_cd_per_src    = [];    // remaining sizes
  22.     global list<list<integer> > remaining_times_per_cd_per_src    = [];    // remaining times
  23.     global list<string>        inst_src_names            = [];    // a list of strings identifying each installation source
  24.     global list<list<integer> > total_pkg_count_per_cd_per_src    = [];    // number of pkgs per inst-src: [ [7, 5, 3], [2, 3, 4] ]
  25.     global list<list<integer> > remaining_pkg_count_per_cd_per_src = []; // remaining number of pkgs
  26.     global map<integer,integer> srcid_to_current_src_no = $[];
  27.     global string    media_type        = "CD";
  28.     global integer    total_size_installed    = 0;
  29.     global integer    total_size_to_install    = 0;
  30.     global integer    total_time_elapsed    = 0;
  31.     global integer    start_time        = -1;
  32.     global integer    initial_recalc_delay    = 60;    // const - seconds before initially calculating remaining times
  33.     global integer    recalc_interval        = 30;    // const - seconds between "remaining time" recalculations
  34.     global integer    min_time_per_cd        = 10;    // const - minimum time displayed per CD if there is something to install
  35.     global integer    max_time_per_cd        = 7200; // const - seconds to cut off predicted time (it's bogus anyway)
  36.     global integer    size_column        = 1;    // const - column number for remaining size per CD
  37.     global integer    pkg_count_column    = 2;    // const - column number for remaining number of packages per CD
  38.     global integer    time_column        = 3;    // const - column number for remaining time per CD
  39.     global integer    next_recalc_time    = -1;
  40.     global integer    current_src_no        = -1;    // 1..n
  41.     global integer    current_cd_no        = -1;    // 1..n
  42.     global integer    next_src_no        = -1;
  43.     global integer    next_cd_no        = -1;
  44.     global boolean    last_cd            = false;
  45.     global integer    total_cd_count        = 0;
  46.     global boolean    unit_is_seconds        = false;    // begin with package sizes
  47.     global integer    bytes_per_second    = 1;
  48.  
  49.     global integer    current_slide_no    = 0;
  50.     global integer    slide_start_time    = 0;
  51.     global integer    slide_min_interval    = 30;    // const - minimum seconds between slide changes
  52.     global integer    slide_max_interval    = 3*60; // const - maximum seconds between slide changes
  53.     global string    slide_base_path        = Installation::sourcedir + "/suse/setup/slide";
  54.  
  55.     global string    slide_txt_path        = "";
  56.     global string    slide_pic_path        = "";
  57.     global integer    slide_interval        = slide_min_interval;
  58.     global list<string> slides            = [];
  59.     global string    language        = Language::language;
  60.     global boolean    init_pkg_data_complete    = false;
  61.     global boolean    widgets_created        = false;
  62.     global boolean    user_switched_to_details = false;
  63.     global boolean    opened_own_wizard    = false;
  64.     global string    inst_log        = "";
  65.     global boolean    debug            = false;
  66.  
  67.  
  68.     /**
  69.      * Constructor
  70.      **/
  71.     global void SlideShow()
  72.     {
  73.     y2milestone( "SlideShow constructor" );
  74.     next_recalc_time = time();
  75.     }
  76.  
  77.  
  78.     /**
  79.      * Start the internal (global) timer.
  80.      **/
  81.     global void StartTimer()
  82.     {
  83.     start_time = time();
  84.     }
  85.  
  86.  
  87.     /**
  88.      * Reset the internal (global) timer.
  89.      **/
  90.     global void ResetTimer()
  91.     {
  92.     start_time = time();
  93.     }
  94.  
  95.  
  96.     /**
  97.      * Stop the internal (global) timer and account elapsed time.
  98.      **/
  99.     global void StopTimer()
  100.     {
  101.     if ( start_time < 0 )
  102.     {
  103.         y2error( "StopTimer(): No timer running." );
  104.         return;
  105.     }
  106.  
  107.     integer elapsed = time() - start_time;
  108.     start_time    = -1;
  109.     total_time_elapsed = total_time_elapsed + elapsed;
  110.     y2debug("StopTimer(): Elapsed this time: %1 sec; total: %2 sec (%3:%4)",
  111.          elapsed, total_time_elapsed,
  112.          total_time_elapsed / 60,    // min
  113.          total_time_elapsed % 60 );    // sec
  114.     }
  115.  
  116.  
  117.     /**
  118.      * Check if currently the "Details" page is shown
  119.      * @return true if showing details, false otherwise
  120.      **/
  121.     boolean ShowingDetails()
  122.     {
  123.     return widgets_created && UI::WidgetExists(`detailsPage );
  124.     }
  125.  
  126.  
  127.     /**
  128.      * Check if currently the "Slide Show" page is shown
  129.      * @return true if showing details, false otherwise
  130.      **/
  131.     boolean ShowingSlide()
  132.     {
  133.     return widgets_created && UI::WidgetExists(`slidesPage );
  134.     }
  135.  
  136.  
  137.     /**
  138.      * Sum up all list items
  139.      **/
  140.     integer ListSum( list<integer> sizes )
  141.     {
  142.     integer sum = 0;
  143.  
  144.     foreach( integer item, sizes, ``{
  145.         if ( item != -1 )
  146.         sum = sum + item;
  147.     });
  148.  
  149.     return sum;
  150.     }
  151.  
  152.  
  153.     /**
  154.      * Sum up all positive list items, but cut off individual items at a maximum value.
  155.      * Negative return values indicate overflow of any individual item at "max_cutoff".
  156.      * In this case, the real sum is the absolute value of the return value.
  157.      **/
  158.     integer ListSumCutOff( list<integer> sizes, integer max_cutoff )
  159.     {
  160.     boolean overflow = false;
  161.     integer sum = 0;
  162.  
  163.     foreach( integer item, sizes, ``{
  164.         if ( item > 0 )
  165.         {
  166.         if ( item > max_cutoff )
  167.         {
  168.             overflow = true;
  169.             sum = sum + max_cutoff;
  170.         }
  171.         else
  172.             sum = sum + item;
  173.         }
  174.     });
  175.  
  176.     if ( overflow )
  177.         sum = -sum;
  178.  
  179.     return sum;
  180.     }
  181.  
  182.  
  183.     integer TotalRemainingSize()
  184.     {
  185.     return ListSum( flatten( remaining_sizes_per_cd_per_src ) );
  186.     }
  187.  
  188.  
  189.     integer TotalRemainingTime()
  190.     {
  191.     return ListSumCutOff( flatten( remaining_times_per_cd_per_src ),
  192.                   max_time_per_cd );
  193.     }
  194.  
  195.  
  196.     integer TotalRemainingPkgCount()
  197.     {
  198.     return ListSum( flatten( remaining_pkg_count_per_cd_per_src ) );
  199.     }
  200.  
  201.  
  202.     integer TotalInstalledSize()
  203.     {
  204.     return total_size_to_install - TotalRemainingSize();
  205.     }
  206.  
  207.  
  208.     /**
  209.      * Format an integer number as (at least) two digits; use leading zeroes if
  210.      * necessary.
  211.      * @return number as two-digit string
  212.      **/
  213.     string FormatTwoDigits( integer x )
  214.     {
  215.     return x < 10 && x >= 0 ?
  216.         sformat( "0%1", x ) :
  217.         sformat(  "%1", x );
  218.     }
  219.  
  220.  
  221.     /**
  222.      * Format an integer seconds value with min:sec or hours:min:sec
  223.      **/
  224.     string FormatTime( integer seconds )
  225.     {
  226.     if ( seconds < 0 )
  227.         return "";
  228.  
  229.     if ( seconds < 3600 )    // Less than one hour
  230.     {
  231.         return sformat( "%1:%2", FormatTwoDigits( seconds / 60 ), FormatTwoDigits( seconds % 60 ) );
  232.     }
  233.     else    // More than one hour - we don't hope this will ever happen, but who knows?
  234.     {
  235.         integer hours = seconds / 3600;
  236.         seconds = seconds % 3600;
  237.         return sformat( "%1:%2:%3", hours, FormatTwoDigits( seconds / 60 ), FormatTwoDigits( seconds % 60 ) );
  238.     }
  239.     }
  240.  
  241.  
  242.     /**
  243.      * Format an integer seconds value with min:sec or hours:min:sec
  244.      *
  245.      * Negative values are interpreted as overflow - ">" is prepended and the
  246.      * absolute value is used.
  247.      **/
  248.     string FormatTimeShowOverflow( integer seconds )
  249.     {
  250.     string text = "";
  251.  
  252.     if ( seconds < 0 )    // Overflow (indicated by negative value)
  253.     {
  254.         // When data throughput goes downhill (stalled network connection etc.),
  255.         // cut off the predicted time at a reasonable maximum.
  256.         // "%1" is a predefined maximum time.
  257.  
  258.         text = sformat( _(">%1"), FormatTime( -seconds ) );
  259.     }
  260.     else
  261.     {
  262.         text = FormatTime( seconds );
  263.     }
  264.  
  265.     return text;
  266.     }
  267.  
  268.  
  269.     /**
  270.      * Format number of remaining bytes to be installed as string.
  271.      * @param remaining        bytes remaining, -1 for 'done'
  272.      * @return            string human readable remaining time or byte / kB/ MB size
  273.      **/
  274.     string FormatRemainingSize( integer remaining )
  275.     {
  276.     if ( remaining <  0 )
  277.     {
  278.         // Nothing more to install from this CD (very concise - little space!!)
  279.         return _("Done.");
  280.     }
  281.     if ( remaining == 0 )
  282.     {
  283.         return "";
  284.     }
  285.  
  286.     return String::FormatSize( remaining );
  287.     }
  288.  
  289.  
  290.     /**
  291.      * Format number of remaining packages to be installed as string.
  292.      * @param remaining        bytes remaining, -1 for 'done'
  293.      * @return            string human readable remaining time or byte / kB/ MB size
  294.      **/
  295.     string FormatRemainingCount( integer remaining )
  296.     {
  297.     if ( remaining <  0 )
  298.     {
  299.         // Nothing more to install from this CD (very concise - little space!!)
  300.         return _("Done.");
  301.     }
  302.     if ( remaining == 0 )
  303.     {
  304.         return "";
  305.     }
  306.  
  307.     return sformat( "%1", remaining );
  308.     }
  309.  
  310.  
  311.     string FormatTotalRemaining()
  312.     {
  313.     // (Short!) headline for the total remaining time or MBs to install
  314.     return sformat( _("Remaining:\n%1"),
  315.             unit_is_seconds ?
  316.             FormatTimeShowOverflow( TotalRemainingTime() ) :
  317.             FormatRemainingSize( TotalRemainingSize() ) );
  318.     }
  319.  
  320.  
  321.     string FormatNextMedia()
  322.     {
  323.     string text = "";
  324.  
  325.     if ( next_src_no >= 0 && next_cd_no >= 0 )
  326.     {
  327.         string next_media_name = sformat( "%1 %2 %3",
  328.                           inst_src_names[ next_src_no ]:"",
  329.                           media_type, next_cd_no+1 );
  330.  
  331.         if ( unit_is_seconds )
  332.         {
  333.         // Status line informing about the next CD that will be used
  334.         // %1: Media type ("CD" / "DVD", ???)
  335.         // %2: Media name ("SuSE Linux Professional CD 2" )
  336.         // %3: Time remaining until this media will be needed
  337.         text = sformat( _("Next %1: %2 -- %3"), media_type, next_media_name,
  338.                 FormatTime( remaining_times_per_cd_per_src[ current_src_no-1, current_cd_no-1 ]: 1) );
  339.         }
  340.         else
  341.         {
  342.         // Status line informing about the next CD that will be used
  343.         // %1: Media type ("CD" / "DVD", ???)
  344.         // %2: Media name ("SuSE Linux Professional CD 2" )
  345.         text = sformat( _("Next %1: %2"), media_type, next_media_name );
  346.         }
  347.     }
  348.  
  349.     return text;
  350.     }
  351.  
  352.  
  353.     /**
  354.      * Normalize a CD size list like [ [ 111, 0, 333 ], [ 211, 222, 0 ] ]
  355.      * to a flat single list that doesn't contain any 0 items (but -1 if there are any)
  356.      * If the resulting list would be completely empty, use a simple fallback: [ 1 ]
  357.      **/
  358.     list<integer> FlattenNoZeroes( list< list<integer> > sizesPerSourceList )
  359.     {
  360.     list <integer> normalizedList =
  361.         filter( integer item, flatten( sizesPerSourceList ),
  362.             ``{ return item != 0; });
  363.  
  364.     if ( size( normalizedList ) < 1 )
  365.         normalizedList = [ 1 ];
  366.  
  367.     return normalizedList;
  368.     }
  369.  
  370.  
  371.     /**
  372.      * Get a list of available slides (images) for the slide show.
  373.      * @return list slides
  374.      **/
  375.     list<string> GetSlideList( string lang )
  376.     {
  377.     string txt_path = sformat( "%1/txt/%2", slide_base_path, lang );
  378.     list<string> slide_list = (list<string>) SCR::Read (.target.dir, txt_path );
  379.  
  380.     if ( slide_list == nil )
  381.     {
  382.         y2debug( "Directory %1 does not exist", txt_path );
  383.         if ( size( lang ) > 2 )
  384.         {
  385.         lang = substring( lang, 0, 2 );
  386.         txt_path = sformat( "%1/txt/%2", slide_base_path, lang );
  387.         slide_list = (list<string>) SCR::Read (.target.dir, txt_path );
  388.         }
  389.     }
  390.  
  391.     if ( slide_list == nil )
  392.     {
  393.         y2milestone( "Slideshow directory %1 does not exist", txt_path );
  394.     }
  395.     else
  396.     {
  397.         slide_list = sort( filter( string filename, slide_list, ``{
  398.         // Check for valid extensions - ignore editor save files and other leftover stuff
  399.         return regexpmatch( filename, ".*\.(rtf|RTF|html|HTML|htm|HTM)$" );
  400.         } ) );
  401.  
  402.         y2debug( "GetSlideList(): Slides at %1: %2", txt_path, slide_list );
  403.     }
  404.  
  405.     if ( slide_list != nil && size( slide_list ) > 0 )    // Slide texts found
  406.     {
  407.         slide_txt_path    = txt_path;
  408.         slide_pic_path    = slide_base_path + "/pic";
  409.     }
  410.     else                            // No slide texts found
  411.     {
  412.         y2debug( "No slides found at %1", txt_path );
  413.  
  414.         if ( lang != "en" )
  415.         {
  416.         y2debug( "Trying to load slides from fallback: en" );
  417.         slide_list = GetSlideList( "en" );
  418.         }
  419.     }
  420.  
  421.     return slide_list;
  422.     }
  423.  
  424.  
  425.     /**
  426.      * Check if showing slides is supported.
  427.      *
  428.      * Not to be confused with HaveSlides() which checks if there are slides available.
  429.      **/
  430.     boolean HaveSlideSupport()
  431.     {
  432.     map disp = UI::GetDisplayInfo();
  433.  
  434.     if (disp != nil        // This shouldn't happen, but who knows?
  435.         && disp["HasImageSupport"]:false
  436.         && disp["DefaultWidth"]:-1    >= 800
  437.         && disp["DefaultHeight"]:-1 >= 600
  438.         && disp["Depth"]:-1        >= 8  )
  439.     {
  440.         return true;
  441.     }
  442.     else
  443.     {
  444.         return false;
  445.     }
  446.     }
  447.  
  448.  
  449.     /**
  450.      * Check if slides are available.
  451.      *
  452.      * Not to be confused with HaveSlideSupport() which checks
  453.      * if slides could be displayed if there are any.
  454.      **/
  455.     boolean HaveSlides()
  456.     {
  457.     return size( slides ) > 0;
  458.     }
  459.  
  460.  
  461.     /**
  462.      * Check if the dialog is currently set up so the user could switch to the slide page.
  463.      **/
  464.     boolean HaveSlideWidget()
  465.     {
  466.     return UI::WidgetExists(`dumbTab);
  467.     }
  468.  
  469.  
  470.     /**
  471.      * Check if the slide show is available. This must be called before trying
  472.      * to access any slides; some late initialization is done here.
  473.      **/
  474.     global void CheckForSlides()
  475.     {
  476.     slides = [];
  477.  
  478.     map tmp = (map) WFM::Read(.local.stat, slide_base_path);
  479.     if (! tmp["isdir"]:false)
  480.     {
  481.         slide_base_path = "/var/adm/YaST/InstSrcManager/tmp/CurrentMedia/suse/setup/slide";
  482.     }
  483.  
  484.     if ( debug && false )
  485.     {
  486.         y2milestone( "Debug mode - using faster time recalc values" );
  487.         initial_recalc_delay    = 7;
  488.         recalc_interval        = 5;
  489.         slide_min_interval        = 5;
  490.     }
  491.  
  492.  
  493.     if ( Stage::initial () || Stage::cont () )
  494.     {
  495.         if ( HaveSlideSupport() )
  496.         {
  497.         y2milestone( "Display OK for slide show" );
  498.  
  499.         slides = GetSlideList( language );
  500.         }
  501.         else
  502.         {
  503.         y2warning( "Disabling slide show - insufficient display capabilities" );
  504.         }
  505.     }
  506.     }
  507.  
  508.  
  509.     /**
  510.      * Set the slide show text.
  511.      * @param text
  512.      **/
  513.     void SetSlideText( string text )
  514.     {
  515.     if ( UI::WidgetExists(`slideText ) )
  516.     {
  517.         //
  518.         // Fix <img src> tags: Replace image path with current slide_pic_path
  519.         //
  520.  
  521.         while (true)
  522.         {
  523.         string replaced = regexpsub( text, "(.*)&imagedir;(.*)",
  524.                          sformat("\\1%1\\2", slide_pic_path ) );
  525.         if ( replaced == nil ) break;
  526.         text = replaced;
  527.         }
  528.  
  529.         UI::ChangeWidget(`slideText, `Value, text );
  530.     }
  531.     }
  532.  
  533.  
  534.     /**
  535.      * Load one slide from files complete with image and textual description.
  536.      * @param text_file_name file name + path of the text file (rich text / HTML)
  537.      * @return true if OK, false if error
  538.      **/
  539.     boolean LoadSlideFile( string text_file_name )
  540.     {
  541.     string text = (string) SCR::Read( .target.string, [text_file_name, ""] );
  542.  
  543.     if ( text == "" )
  544.     {
  545.         return false;
  546.     }
  547.     else
  548.     {
  549.         y2debug( "Loading slide show text from %1", text_file_name);
  550.         SetSlideText( text );
  551.         return true;
  552.     }
  553.     }
  554.  
  555.  
  556.     /**
  557.      * Get version info for a package (without build no.)
  558.      *
  559.      * @param pkg_name name of the package without path and ".rpm" extension
  560.      * @return version string
  561.      **/
  562.     global string StripReleaseNo( string pkg_name )
  563.     {
  564.     integer build_no_pos = findlastof (pkg_name, "-" );    // find trailing build no.
  565.  
  566.     if ( build_no_pos != nil && build_no_pos > 0 )
  567.     {
  568.         // cut off trailing build no.
  569.         pkg_name = substring( pkg_name , 0, build_no_pos );
  570.     }
  571.  
  572.     return pkg_name;
  573.     }
  574.  
  575.     /**
  576.      * Get package file name from path
  577.      *
  578.      * @param pkg_name location of the package
  579.      * @return string package file name
  580.      **/
  581.     global string StripPath(string pkg_name)
  582.     {
  583.     if (pkg_name == nil)
  584.     {
  585.         return nil;
  586.     }
  587.  
  588.     integer file_pos = findlastof(pkg_name, "/");
  589.  
  590.     if (file_pos != nil && file_pos > 0 )
  591.     {
  592.         // return just the file name
  593.         pkg_name = substring(pkg_name, file_pos + 1);
  594.     }
  595.  
  596.     return pkg_name;
  597.     }
  598.  
  599.  
  600.     /**
  601.      * set media type "CD" or "DVD"
  602.      */
  603.     global void SetMediaType (string new_media_type)
  604.     {
  605.     media_type = new_media_type;
  606.     }
  607.  
  608.  
  609.     /**
  610.      * Set the slide show directory
  611.      */
  612.     global void SetSlideDir( string dir )
  613.     {
  614.     slide_base_path = dir;
  615.  
  616.     map tmp = (map) WFM::Read (.local.stat, slide_base_path);
  617.  
  618.     if ( ! tmp["isdir"]:false )
  619.         slide_base_path = "/var/adm/YaST/InstSrcManager/tmp/CurrentMedia/suse/setup/slide";
  620.  
  621.     y2milestone( "SetSlideDir: %1", slide_base_path );
  622.     }
  623.  
  624.  
  625.     /**
  626.      * Perform sanity check for correct initialzation etc.
  627.      * @param silent    don't complain in log file
  628.      * @return        true if OK, false if any error
  629.      **/
  630.     boolean SanityCheck( boolean silent )
  631.     {
  632.     if ( ! init_pkg_data_complete )
  633.     {
  634.         if ( ! silent )
  635.         {
  636.         y2error( "SlideShow::SanityCheck(): Slide show not correctly initialized: " +
  637.              "SlideShow::InitPkgData() never called!" );
  638.         }
  639.         return false;
  640.     }
  641.  
  642.     if ( current_src_no < 1 || current_cd_no < 1 )
  643.     {
  644.         y2error( "SlideShow::SanityCheck(): Illegal values for current_src (%1) or current_cd (%2)",
  645.              current_src_no, current_cd_no );
  646.         y2milestone( "total sizes: %1", total_sizes_per_cd_per_src );
  647.         return false;
  648.     }
  649.  
  650.     return true;
  651.     }
  652.  
  653.     global void SlideGenericProvideStart (string pkg_name, integer sz,
  654.     string pattern, boolean remote)
  655.     {
  656.     if ( ! SanityCheck( false ) )    return;
  657.     if ( ! ShowingDetails() )    return;
  658.  
  659.     if ( UI::WidgetExists(`progressCurrentPackage) )
  660.     {
  661.         UI::ChangeWidget(`progressCurrentPackage, `Label, pkg_name);
  662.         UI::ChangeWidget(`progressCurrentPackage, `Value, 0);
  663.     }
  664.     //
  665.     // Update (user visible) installation log
  666.     // for remote download only
  667.     //
  668.     
  669.     if( ! remote ) return;
  670.  
  671.     y2milestone( "Package '%1' is remote", pkg_name );
  672.     
  673.     string strsize = String::FormatSize (sz);
  674.     // message in the installatino log, %1 is package name,
  675.     // %2 is package size
  676.     string msg = sformat (pattern, pkg_name, strsize);
  677.     string log_line = "\n" + msg;
  678.     inst_log = inst_log + log_line;
  679.  
  680.     if ( ShowingDetails() )
  681.     {
  682.         if ( UI::WidgetExists( `instLog ) )
  683.         UI::ChangeWidget(`instLog, `LastLine, log_line );
  684.     }
  685.  
  686.     }
  687.  
  688.     global void SlideDeltaApplyStart (string pkg_name) {
  689.     if ( ! SanityCheck( false ) )    return;
  690.     if ( ! ShowingDetails() )    return;
  691.  
  692.     if ( UI::WidgetExists(`progressCurrentPackage) )
  693.     {
  694.         UI::ChangeWidget(`progressCurrentPackage, `Label, pkg_name);
  695.         UI::ChangeWidget(`progressCurrentPackage, `Value, 0);
  696.     }
  697.  
  698.     string msg = sformat (_("Applying delta RPM: %1"), pkg_name);
  699.     string log_line = "\n" + msg;
  700.     inst_log = inst_log + log_line;
  701.  
  702.     if ( ShowingDetails() )
  703.     {
  704.         if ( UI::WidgetExists( `instLog ) )
  705.         UI::ChangeWidget(`instLog, `LastLine, log_line );
  706.     }
  707.     }
  708.  
  709.  
  710.     /**
  711.      * Package providal start
  712.      */
  713.     global void SlideProvideStart (string pkg_name, integer sz, boolean remote)
  714.     {
  715.     // message in the installatino log, %1 is package name,
  716.     // %2 is package size
  717.     SlideGenericProvideStart (pkg_name, sz, _("Downloading %1 (download size %2)"),
  718.         remote);
  719.     }
  720.  
  721.  
  722.     /**
  723.      * Set the curent language. Must be called once during initialization.
  724.      **/
  725.     global void SetLanguage( string new_language )
  726.     {
  727.     language = new_language;
  728.     }
  729.  
  730.     /**
  731.      * Append message to the installation log
  732.      */
  733.     global void AppendMessageToInstLog (string msg)
  734.     {
  735.     string log_line = "\n" + msg;
  736.     inst_log = inst_log + log_line;
  737.  
  738.     if ( ShowingDetails() )
  739.     {
  740.         if ( UI::WidgetExists( `instLog ) )
  741.         UI::ChangeWidget(`instLog, `LastLine, log_line );
  742.     }
  743.     }
  744.  
  745.     /**
  746.      * Update internal bookkeeping: subtract size of one package from the
  747.      * global list of remaining sizes per CD
  748.      **/
  749.     void SubtractPackageSize( integer pkg_size )
  750.     {
  751.     integer remaining = remaining_sizes_per_cd_per_src [ current_src_no-1, current_cd_no-1 ]: 1;
  752.     remaining = remaining - pkg_size;
  753.     total_size_installed = total_size_installed + pkg_size;
  754.  
  755.     if ( remaining <= 0 )
  756.     {
  757.         // -1 is the indicator for "done with this CD" - not to be
  758.         // confused with 0 for "nothing to install from this CD".
  759.         remaining = -1;
  760.     }
  761.  
  762.     remaining_sizes_per_cd_per_src [ current_src_no-1, current_cd_no-1 ] = remaining;
  763.     remaining_pkg_count_per_cd_per_src [ current_src_no-1, current_cd_no-1 ] =
  764.         remaining_pkg_count_per_cd_per_src [ current_src_no-1, current_cd_no-1 ]:0 -1;
  765.  
  766.     if ( unit_is_seconds )
  767.     {
  768.         integer seconds = 0;
  769.  
  770.         if ( remaining > 0 && bytes_per_second > 0 )
  771.         seconds = remaining / bytes_per_second;
  772.  
  773.         remaining_times_per_cd_per_src [ current_src_no-1, current_cd_no-1 ] = seconds;
  774.     }
  775.  
  776.     if ( debug )
  777.         y2milestone( "SubtractPackageSize( %1 ) -> %2", pkg_size, remaining_sizes_per_cd_per_src);
  778.     }
  779.  
  780.  
  781.     /**
  782.      * Return a CD's progress bar ID
  783.      * @param src_no number of the installation source (from 0 on)
  784.      * @param cd_no number of the CD within that installation source (from 0 on)
  785.      **/
  786.     string CdProgressId( integer src_no, integer cd_no )
  787.     {
  788.     return sformat( "Src %1 CD %2", src_no, cd_no );
  789.     }
  790.  
  791.  
  792.     /**
  793.      * Recalculate remaining times per CD based on package sizes remaining
  794.      * and data rate so far. Recalculation is only done each 'recalc_interval'
  795.      * seconds unless 'force_recalc' is set to 'true'.
  796.      *
  797.      * @param force_recalc force recalculation even if timeout not reached yet
  798.      * @return true if recalculated, false if not
  799.      **/
  800.     boolean RecalcRemainingTimes( boolean force_recalc )
  801.     {
  802.     if ( ! force_recalc
  803.         && time() < next_recalc_time )
  804.     {
  805.         // Nothing to do (yet) - simply return
  806.         return false;
  807.     }
  808.  
  809.  
  810.     // Actually do recalculation
  811.  
  812.     integer elapsed = total_time_elapsed;
  813.  
  814.     if ( start_time >= 0 )
  815.     {
  816.         elapsed = elapsed + time() - start_time;
  817.     }
  818.  
  819.     if ( elapsed == 0 )
  820.     {
  821.         // Called too early - no calculation possible yet.
  822.         // This happens regularly during initialization, so an error
  823.         // message wouldn't be a good idea here.
  824.  
  825.         return false;
  826.     }
  827.  
  828.  
  829.     // This is the real thing.
  830.  
  831.     integer real_bytes_per_second = total_size_installed / elapsed;
  832.  
  833.     // But this turns out to be way to optimistic - RPM gets slower and
  834.     // slower while installing. So let's add some safety margin to make
  835.     // sure initial estimates are on the pessimistic side - the
  836.     // installation being faster than initially estimated will be a
  837.     // pleasant surprise to the user. Most users don't like it the other
  838.     // way round.
  839.     //
  840.     // The "pessimistic factor" progressively decreases as the installation
  841.     // proceeds.  It begins with about 1.7, i.e. the data transfer rate is
  842.     // halved to what it looks like initially. It decreases to 1.0 towards
  843.     // the end.
  844.  
  845.     float pessimistic_factor = 1.0;
  846.  
  847.     if ( total_size_to_install > 0 )
  848.         pessimistic_factor = 1.7 - tofloat( total_size_installed ) / tofloat( total_size_to_install );
  849.     bytes_per_second = tointeger( tofloat( real_bytes_per_second ) / pessimistic_factor + 0.5 );
  850.  
  851.     if ( bytes_per_second < 1 )
  852.         bytes_per_second = 1;
  853.  
  854.     remaining_times_per_cd_per_src = [];
  855.  
  856.     // Recalculate remaining times for the individual CDs
  857.  
  858.     foreach ( list<integer> remaining_sizes_list, remaining_sizes_per_cd_per_src,
  859.     ``{
  860.         list<integer> remaining_times_list = [];
  861.         integer remaining_time = -1;
  862.  
  863.         foreach ( integer remaining_size, remaining_sizes_list,
  864.         ``{
  865.         remaining_time = remaining_size;
  866.  
  867.         if ( remaining_size > 0 )
  868.         {
  869.             remaining_time = remaining_size / bytes_per_second;
  870.  
  871.             if ( remaining_time < min_time_per_cd )
  872.             {
  873.             // It takes at least this long for the CD drive to spin up and
  874.             // for RPM to do _anything_. Times below this values are
  875.             // ridiculously unrealistic.
  876.             remaining_time = min_time_per_cd;
  877.             }
  878.             else if ( remaining_time > max_time_per_cd ) // clip off at 2 hours
  879.             {
  880.             // When data throughput goes downhill (stalled network connection etc.),
  881.             // cut off the predicted time at a reasonable maximum.
  882.             remaining_time = max_time_per_cd;
  883.             }
  884.         }
  885.         remaining_times_list = add( remaining_times_list, remaining_time );
  886.         });
  887.  
  888.         remaining_times_per_cd_per_src = add( remaining_times_per_cd_per_src, remaining_times_list );
  889.     });
  890.  
  891.  
  892.     // Recalculate slide interval
  893.  
  894.     if ( size( slides ) > 0 )
  895.     {
  896.         integer slides_remaining = size( slides ) - current_slide_no - 1;
  897.  
  898.         if ( slides_remaining > 0 )
  899.         {
  900.         // The remaining time for the rest of the slides depends on the
  901.         // remaining time for the current CD only: This is where the
  902.         // slide images and texts reside. Normally, only CD1 has slides
  903.         // at all, i.e. the slide show must be finished when CD1 is
  904.         // done.
  905.         //
  906.         // In addition to that, take elapsed time for current slide
  907.         // into account so all slides get about the same time.
  908.  
  909.         integer time_remaining = remaining_times_per_cd_per_src[current_src_no-1, current_cd_no-1]:1 + time() - slide_start_time;
  910.         slide_interval = time_remaining / slides_remaining;
  911.         y2debug( "New slide interval: %1 - slides remaining: %2 - remaining time: %3",
  912.              slide_interval, slides_remaining, time_remaining );
  913.  
  914.         if ( slide_interval < slide_min_interval )
  915.         {
  916.             slide_interval = slide_min_interval;
  917.             y2debug( "Resetting slide interval to min slide interval: %1", slide_interval );
  918.         }
  919.  
  920.         if ( slide_interval > slide_max_interval )
  921.         {
  922.             slide_interval = slide_max_interval;
  923.             y2debug( "Resetting slide interval to max slide interval: %1", slide_interval );
  924.         }
  925.         }
  926.     }
  927.  
  928.     next_recalc_time = time() + recalc_interval;
  929.  
  930.     return true;
  931.     }
  932.  
  933.  
  934.     /**
  935.      * Create one single item for the CD statistics table
  936.      **/
  937.     term TableItem( string id, string col1, string col2, string col3, string col4 )
  938.     {
  939.     return `item(`id( id ), col1, col2, col3, col4 );
  940.     }
  941.  
  942.  
  943.     /**
  944.      * Returns a table widget item list for CD statistics
  945.      **/
  946.     list<term> CdStatisticsTableItems()
  947.     {
  948.     list<term> itemList = [];
  949.  
  950.     //
  951.     // Add "Total" item - at the top so it is visible by default even if there are many items
  952.     //
  953.  
  954.     {
  955.         // List column header for total remaining MB and time to install
  956.         string caption     =  _("Total");
  957.         integer remaining     = TotalRemainingSize();
  958.         string rem_size    = FormatRemainingSize( remaining );
  959.         string rem_count     = FormatRemainingCount( TotalRemainingPkgCount() );
  960.         string rem_time     = "";
  961.  
  962.         if ( unit_is_seconds && bytes_per_second > 0 )
  963.         {
  964.         rem_time = FormatTimeShowOverflow( TotalRemainingTime() );
  965.         }
  966.  
  967.         itemList = add( itemList, TableItem( "total", caption, "   " + rem_size, "   " + rem_count, "   " + rem_time ) );
  968.     }
  969.  
  970.  
  971.     //
  972.     // Now go through all installation sources
  973.     //
  974.  
  975.     integer src_no = 0;
  976.  
  977.     foreach ( list<integer> inst_src, remaining_sizes_per_cd_per_src, ``{
  978.         y2milestone( "src #%1: %2", src_no, inst_src );
  979.  
  980.         if ( ListSum( inst_src ) > 0     // Ignore sources from where there is nothing is to install
  981.          || src_no+1 == current_src_no ) // or if this happens to be the current source
  982.         {
  983.         if ( size( total_sizes_per_cd_per_src ) > 1 )    // Multiple sources?
  984.         {
  985.             //
  986.             // Add heading for this installation source
  987.             //
  988.  
  989.             itemList = add( itemList, TableItem( sformat( "src(%1)", src_no ),
  990.                              inst_src_names[ src_no ]:"", "", "", "" ) );
  991.         }
  992.  
  993.         integer cd_no = 0;
  994.  
  995.         foreach ( integer remaining, inst_src, ``{
  996.             if ( remaining > 0
  997.              || ( src_no+1 == current_src_no && cd_no+1 == current_cd_no ) )    // suppress current CD
  998.             {
  999.             string caption  = sformat( "%1 %2", media_type, cd_no+1 );        // "CD 1" - column #0
  1000.             string rem_size = FormatRemainingSize( remaining );            // column #1
  1001.             string rem_count = FormatRemainingCount( remaining_pkg_count_per_cd_per_src[ src_no, cd_no ]:0 );
  1002.             string rem_time = "";
  1003.  
  1004.             if ( unit_is_seconds && bytes_per_second > 0 )
  1005.             {
  1006.                 remaining = remaining / bytes_per_second;
  1007.                 rem_time = FormatTime( remaining );                    // column #2
  1008.  
  1009.                 if ( remaining > max_time_per_cd )        // clip off at 2 hours
  1010.                 {
  1011.                 // When data throughput goes downhill (stalled network connection etc.),
  1012.                 // cut off the predicted time at a reasonable maximum.
  1013.                 // "%1" is a predefined maximum time.
  1014.                 rem_time = FormatTimeShowOverflow( -max_time_per_cd );
  1015.                 }
  1016.             }
  1017.  
  1018.             itemList = add( itemList,
  1019.                     TableItem( sformat("cd(%1,%2)", src_no, cd_no ),    // ID
  1020.                            caption, "   " + rem_size, "   " + rem_count, "   " + rem_time ) );
  1021.             }
  1022.  
  1023.             cd_no = cd_no + 1;
  1024.         });
  1025.         }
  1026.  
  1027.         src_no = src_no + 1;
  1028.     });
  1029.  
  1030.     if ( debug )
  1031.     {
  1032.         y2milestone( "Remaining: %1", remaining_sizes_per_cd_per_src );
  1033.         y2milestone( "CD table item list:\n%1", itemList );
  1034.     }
  1035.  
  1036.     return itemList;
  1037.     }
  1038.  
  1039.  
  1040.  
  1041.     /**
  1042.      * Progress display update
  1043.      * This is called via the packager's progress callbacks.
  1044.      *
  1045.      * @param pkg_percent    package percentage
  1046.      **/
  1047.     global void UpdateCurrentPackageProgress( integer pkg_percent )
  1048.     {
  1049.     if ( UI::WidgetExists(`progressCurrentPackage ) )
  1050.         UI::ChangeWidget(`progressCurrentPackage, `Value, pkg_percent);
  1051.     }
  1052.  
  1053.  
  1054.  
  1055.     /**
  1056.      * Update progress widgets for all CDs.
  1057.      * Uses global statistics variables.
  1058.      **/
  1059.     global void UpdateAllCdProgress()
  1060.     {
  1061.     if ( ! SanityCheck( true ) )    return;
  1062.     if ( ! widgets_created )    return;
  1063.  
  1064.     if ( unit_is_seconds )
  1065.         RecalcRemainingTimes( true );    // force
  1066.  
  1067.     if ( UI::WidgetExists(`cdStatisticsTable) )
  1068.         UI::ChangeWidget(`cdStatisticsTable, `Items, CdStatisticsTableItems() );
  1069.     }
  1070.  
  1071.  
  1072.     /**
  1073.      * Update progress widgets for the current CD: Label and ProgressBar.
  1074.      * Use global statistics variables for that.
  1075.      **/
  1076.     global void UpdateCurrentCdProgress()
  1077.     {
  1078.     if ( ! SanityCheck( false ) )            return;
  1079.     if ( ! UI::WidgetExists(`cdStatisticsTable) )    return;
  1080.  
  1081.  
  1082.     //
  1083.     // Update table entries for current CD
  1084.     //
  1085.  
  1086.     integer remaining = remaining_sizes_per_cd_per_src [ current_src_no-1, current_cd_no-1 ]:0;
  1087.     UI::ChangeWidget(`id(`cdStatisticsTable ),
  1088.              `Item( sformat( "cd(%1,%2)", current_src_no-1, current_cd_no-1), size_column ),
  1089.              FormatRemainingSize( remaining ) );
  1090.  
  1091.     UI::ChangeWidget(`id(`cdStatisticsTable ),
  1092.              `Item( sformat( "cd(%1,%2)", current_src_no-1, current_cd_no-1), pkg_count_column ),
  1093.              FormatRemainingCount( remaining_pkg_count_per_cd_per_src [ current_src_no-1, current_cd_no-1 ]:0 ) );
  1094.  
  1095.     if ( unit_is_seconds )
  1096.     {
  1097.         // Convert 'remaining' from size (bytes) to time (seconds)
  1098.  
  1099.         remaining = remaining / bytes_per_second;
  1100.  
  1101.         if ( remaining <= 0 )
  1102.         remaining = 0;
  1103.  
  1104.         if ( remaining > max_time_per_cd )        // clip off at 2 hours
  1105.         {
  1106.         // When data throughput goes downhill (stalled network connection etc.),
  1107.         // cut off the predicted time at a reasonable maximum.
  1108.         remaining = -max_time_per_cd;
  1109.         }
  1110.  
  1111.         UI::ChangeWidget(`id(`cdStatisticsTable ),
  1112.                  `Item( sformat( "cd(%1,%2)", current_src_no-1, current_cd_no-1), time_column ),
  1113.                  FormatTimeShowOverflow( remaining ) );
  1114.     }
  1115.  
  1116.  
  1117.     //
  1118.     // Update "total" table entries
  1119.     //
  1120.  
  1121.     UI::ChangeWidget(`id( `cdStatisticsTable ),
  1122.              `Item( "total", size_column ),
  1123.              FormatRemainingSize( TotalRemainingSize() ) );
  1124.  
  1125.     UI::ChangeWidget(`id( `cdStatisticsTable ),
  1126.              `Item( "total", pkg_count_column ),
  1127.              FormatRemainingCount( TotalRemainingPkgCount() ) );
  1128.  
  1129.     if ( unit_is_seconds )
  1130.     {
  1131.         UI::ChangeWidget(`id( `cdStatisticsTable ), `Item( "total", time_column ),
  1132.                  FormatTimeShowOverflow( TotalRemainingTime() ) );
  1133.  
  1134.     }
  1135.     }
  1136.  
  1137.  
  1138.     /**
  1139.      * Update progress widgets
  1140.      **/
  1141.     void UpdateTotalProgress()
  1142.     {
  1143.     if ( UI::WidgetExists(`multiProgressMeter ) )
  1144.         UI::ChangeWidget(`multiProgressMeter, `Values, FlattenNoZeroes( remaining_sizes_per_cd_per_src ) );
  1145.  
  1146.     if ( UI::WidgetExists(`progressTotal ) )
  1147.         UI::ChangeWidget(`progressTotal, `Value, TotalInstalledSize() );
  1148.  
  1149.     UpdateCurrentCdProgress();
  1150.  
  1151.     if ( UI::WidgetExists(`nextMedia ) )
  1152.     {
  1153.         string nextMedia = FormatNextMedia();
  1154.  
  1155.         if ( nextMedia != "" || last_cd )
  1156.         {
  1157.         UI::ChangeWidget(`nextMedia, `Value, nextMedia );
  1158.         UI::RecalcLayout();
  1159.         last_cd = false;
  1160.         }
  1161.     }
  1162.  
  1163.     if ( UI::WidgetExists(`totalRemainingLabel ) )
  1164.         UI::ChangeWidget(`totalRemainingLabel, `Value, FormatTotalRemaining() );
  1165.  
  1166.     if ( UI::WidgetExists(`multiProgressMeter ) )    // Avoid that in NCurses - too much flicker
  1167.         UI::RecalcLayout();
  1168.     }
  1169.  
  1170.  
  1171.     /**
  1172.      * Switch unit to seconds if necessary and recalc everything accordingly.
  1173.      * @return true if just switched from sizes to seconds, false otherwise
  1174.      **/
  1175.     boolean SwitchToSecondsIfNecessary()
  1176.     {
  1177.     if ( unit_is_seconds
  1178.         || time() < start_time + initial_recalc_delay )
  1179.     {
  1180.         return false;    // no need to switch
  1181.     }
  1182.  
  1183.     RecalcRemainingTimes( true );    // force recalculation
  1184.     unit_is_seconds = true;
  1185.  
  1186.     return true;    // just switched
  1187.     }
  1188.  
  1189.  
  1190.     /**
  1191.      * Load a slide image + text.
  1192.      * @param slide_no number of slide to load
  1193.      **/
  1194.     void LoadSlide( integer slide_no )
  1195.     {
  1196.     if ( slide_no > size( slides ) )
  1197.     {
  1198.         slide_no = 0;
  1199.     }
  1200.  
  1201.     current_slide_no = slide_no;
  1202.  
  1203.     string slide_name = slides[slide_no]:"";
  1204.     slide_start_time = time();
  1205.  
  1206.     if ( LoadSlideFile( sformat ("%1/%2", slide_txt_path, slide_name ) ) ) return;
  1207.     SetSlideText ("");
  1208.     }
  1209.  
  1210.  
  1211.     /**
  1212.      * Check if the current slide needs to be changed and do that if
  1213.      * necessary.
  1214.      **/
  1215.     void ChangeSlideIfNecessary()
  1216.     {
  1217.     if ( current_slide_no + 1 < size( slides )
  1218.         && time() > slide_start_time + slide_interval )
  1219.     {
  1220.         y2debug( "Loading slide #%1", current_slide_no + 2 );
  1221.         LoadSlide( current_slide_no + 1 );
  1222.     }
  1223.     }
  1224.  
  1225.  
  1226.     /**
  1227.      * package start display update
  1228.      * - this is called at the beginning of a new package
  1229.      *
  1230.      * @param pkg_name        package name
  1231.      * @param pkg_summary    package summary (short description)
  1232.      * @param deleting        Flag: deleting (true) or installing (false) package?
  1233.      **/
  1234.     global void SlideDisplayStart( string    pkg_name,
  1235.                    string    pkg_summary,
  1236.                    integer    pkg_size,
  1237.                    boolean    deleting    )
  1238.     {
  1239.     if ( ! SanityCheck( false ) )    return;
  1240.  
  1241.     // remove path
  1242.     pkg_name = StripPath(pkg_name);
  1243.  
  1244.     // remove release and .rpm suffix
  1245.     // pkg_name = StripReleaseNo( pkg_name );    // bug #154872
  1246.  
  1247.     if ( deleting )
  1248.     {
  1249.         pkg_size = -1;
  1250.  
  1251.         // This is a kind of misuse of insider knowledge: If there are packages to delete, this
  1252.         // deletion comes first, and only then packages are installed. This, however, greatly
  1253.         // distorts the estimated times based on data throughput so far: While packages are
  1254.         // deleted, throughput is zero, and estimated times rise to infinity (they are cut off
  1255.         // at max_time_per_cd to hide this). So we make sure the time spent deleting packages is
  1256.         // not counted for estimating remaining times - reset the timer.
  1257.         //
  1258.         // Note: This will begin to fail when some day packages are deleted in the middle of the
  1259.         // installaton process.
  1260.  
  1261.         ResetTimer();
  1262.     }
  1263.  
  1264.     if ( pkg_summary == nil )
  1265.         pkg_summary = "";
  1266.  
  1267.     string msg = "";
  1268.  
  1269.     if ( deleting )
  1270.     {
  1271.         // Heading for the progress bar for the current package
  1272.         // while it is deleted. "%1" is the package name.
  1273.         msg = sformat( _("Deleting %1"), pkg_name );
  1274.     }
  1275.     else
  1276.     {
  1277.         msg = sformat( "%1 (installed size %2)", pkg_name, String::FormatSize( pkg_size ) );
  1278.     }
  1279.  
  1280.  
  1281.     //
  1282.     // Update package progress bar
  1283.     //
  1284.  
  1285.     if ( UI::WidgetExists(`progressCurrentPackage) )
  1286.     {
  1287.         UI::ChangeWidget(`progressCurrentPackage, `Label, msg );
  1288.         UI::ChangeWidget(`progressCurrentPackage, `Value, 0);
  1289.     }
  1290.  
  1291.  
  1292.     //
  1293.     // Update (user visible) installation log
  1294.     //
  1295.  
  1296.     string log_line = "\n" + msg;
  1297.  
  1298.     if ( pkg_summary != "" )
  1299.         log_line = log_line + " -- " + pkg_summary;
  1300.  
  1301.     inst_log = inst_log + log_line;
  1302.  
  1303.     if ( ShowingDetails() )
  1304.     {
  1305.         if ( UI::WidgetExists( `instLog ) )
  1306.         UI::ChangeWidget(`instLog, `LastLine, log_line );
  1307.     }
  1308.     else
  1309.     {
  1310.         ChangeSlideIfNecessary();
  1311.     }
  1312.  
  1313.  
  1314.     if ( ! deleting )
  1315.     {
  1316.         // the actions are performed when package installation finishes
  1317. //        SubtractPackageSize( pkg_size );
  1318.         y2milestone( "Installing %1 -- %2", pkg_name, pkg_summary );
  1319.  
  1320. //        if (SwitchToSecondsIfNecessary()
  1321. //        || RecalcRemainingTimes( false ) )    // no forced recalculation
  1322. //        {
  1323. //        y2debug( "Updating progress for all CDs" );
  1324. //        UpdateAllCdProgress();
  1325. //        }
  1326. //        else
  1327. //        {
  1328. //        UpdateCurrentCdProgress();
  1329. //        }
  1330.  
  1331. //        UpdateTotalProgress();
  1332.  
  1333.     } // ! deleting
  1334.     }
  1335.  
  1336.     /**
  1337.      * package start display update
  1338.      * - this is called at the end of a new package
  1339.      *
  1340.      * @param pkg_name        package name
  1341.      * @param deleting        Flag: deleting (true) or installing (false) package?
  1342.      **/
  1343.     global void SlideDisplayDone ( string    pkg_name,
  1344.                    integer    pkg_size,
  1345.                    boolean    deleting    )
  1346.     {
  1347.     if ( ! deleting )
  1348.     {
  1349.         SubtractPackageSize( pkg_size );
  1350.  
  1351.         if (SwitchToSecondsIfNecessary()
  1352.         || RecalcRemainingTimes( false ) )    // no forced recalculation
  1353.         {
  1354.         y2debug( "Updating progress for all CDs" );
  1355.         UpdateAllCdProgress();
  1356.         }
  1357.         else
  1358.         {
  1359.         UpdateCurrentCdProgress();
  1360.         }
  1361.  
  1362.         UpdateTotalProgress();
  1363.  
  1364.     } // ! deleting
  1365.  
  1366.     }
  1367.  
  1368.     /**
  1369.      * Add widgets for progress bar etc. around a slide show page
  1370.      * @param page_id        ID to use for this page (for checking with UI::WidgetExists() )
  1371.      * @param page_contents    The inner widgets (the page contents)
  1372.      * @return            A term describing the widgets
  1373.      **/
  1374.     term AddProgressWidgets( symbol page_id, term page_contents )
  1375.     {
  1376.     term widgets = `Empty();
  1377.  
  1378.     if ( UI::HasSpecialWidget(`VMultiProgressMeter ) )
  1379.     {
  1380.         widgets =
  1381.         `VBox(`id( page_id ),
  1382.               `VWeight( 1, // lower layout priority so `Label(`nextMedia) gets its desired height
  1383.                 `HBox(
  1384.                       page_contents,
  1385.                       `HSpacing( 0.5 ),
  1386.                       `VBox(
  1387.                         `Label(`id(`totalRemainingLabel), FormatTotalRemaining() ),
  1388.                         `VWeight( 1, `VMultiProgressMeter(`id(`multiProgressMeter),
  1389.                                           FlattenNoZeroes( total_sizes_per_cd_per_src  ) ) )
  1390.                         )
  1391.                       )
  1392.                 ),
  1393.               `VSpacing( 0.3 ),
  1394.               `Label(`id(`nextMedia ), `opt( `hstretch), FormatNextMedia() )
  1395.               );
  1396.     }
  1397.     else // No VMultiProgressMeter - use a simpler version (mainly for NCurses UI)
  1398.     {
  1399.         widgets =
  1400.         `HBox(`id( page_id ),
  1401.               `HSpacing( 1 ),
  1402.               `VBox(
  1403.                 `VSpacing( 0.4 ),
  1404.                 `VWeight( 1, // lower layout priority
  1405.                       page_contents ),
  1406.                 // Progress bar for overall progress of software package installation
  1407.                 `ProgressBar(`id(`progressTotal ), _("Total"),
  1408.                      total_size_to_install, TotalInstalledSize() )
  1409.                 // intentionally omitting `Label(`nextMedia) -
  1410.                 // too much flicker upon update (UI::RecalcLayout() ) on NCurses
  1411.                 ),
  1412.               `HSpacing( 0.5 )
  1413.               );
  1414.     }
  1415.  
  1416.     y2debug( "widget term: \n%1", widgets );
  1417.     return widgets;
  1418.     }
  1419.  
  1420.  
  1421.     /**
  1422.      * Construct widgets describing a page with the real slide show
  1423.      * (the RichText / HTML page)
  1424.      *
  1425.      * @return    A term describing the widgets
  1426.      **/
  1427.     term SlidePageWidgets()
  1428.     {
  1429.     term widgets =
  1430.         AddProgressWidgets( `slideShowPage,
  1431.                 `RichText(`id(`slideText), "" )
  1432.                 );
  1433.     y2debug( "widget term: \n%1", widgets );
  1434.     return widgets;
  1435.     }
  1436.  
  1437.  
  1438.     /**
  1439.      * Construct widgets for the "details" page
  1440.      *
  1441.      * @return    A term describing the widgets
  1442.      **/
  1443.     term DetailsPageWidgets()
  1444.     {
  1445.     term widgets =
  1446.         AddProgressWidgets( `detailsPage,
  1447.                 `VBox(
  1448.                       `VWeight( 1,
  1449.                         `Table( `id(`cdStatisticsTable), `opt(`keepSorting),
  1450.                             `header(
  1451.                                 // Table headings for CD statistics during installation
  1452.                                 _("Media"),
  1453.                                 // Table headings for CD statistics during installation
  1454.                                 `Right( _("Size") ),
  1455.                                 // Table headings for CD statistics during installation
  1456.                                 `Right( _("Packages") ),
  1457.                                 // Table headings for CD statistics during installation
  1458.                                 `Right(  _("Time") )
  1459.                                 ),
  1460.                             CdStatisticsTableItems()
  1461.                             )
  1462.                         ),
  1463.                       `VWeight( 1,
  1464.                         `LogView(`id(`instLog ),  "", 6, 0 )
  1465.                         ),
  1466.                       `ProgressBar(`id(`progressCurrentPackage), " ", 100, 0 )
  1467.                       )
  1468.                 );
  1469.  
  1470.     y2debug( "widget term: \n%1", widgets );
  1471.     return widgets;
  1472.     }
  1473.  
  1474.  
  1475.     /**
  1476.      * Switch from the 'details' view to the 'slide show' view.
  1477.      **/
  1478.     global void SwitchToSlideView()
  1479.     {
  1480.     if ( ShowingSlide() )
  1481.         return;
  1482.  
  1483.     if ( UI::WidgetExists(`tabContents ) )
  1484.     {
  1485.         UI::ChangeWidget(`dumbTab, `CurrentItem, `showSlide );
  1486.         UI::ReplaceWidget(`tabContents, SlidePageWidgets() );
  1487.         UpdateTotalProgress();
  1488.     }
  1489.     }
  1490.  
  1491.  
  1492.     /**
  1493.      * Switch from the 'slide show' view to the 'details' view.
  1494.      **/
  1495.     global void SwitchToDetailsView()
  1496.     {
  1497.     if ( ShowingDetails() )
  1498.         return;
  1499.  
  1500.     if ( UI::WidgetExists(`tabContents ) )
  1501.     {
  1502.         UI::ChangeWidget(`dumbTab, `CurrentItem, `showDetails );
  1503.         UI::ReplaceWidget(`tabContents, DetailsPageWidgets() );
  1504.     }
  1505.  
  1506.     UpdateTotalProgress();
  1507.     UpdateAllCdProgress();
  1508.  
  1509.     if ( UI::WidgetExists( `instLog ) && inst_log != "" )
  1510.         UI::ChangeWidget(`instLog, `Value, inst_log );
  1511.     }
  1512.  
  1513.  
  1514.     string HelpText()
  1515.     {
  1516.     // Help text while software packages are being installed (displayed only in rare cases)
  1517.     string help_text = _("<p>Please wait while packages are installed.</p>");
  1518.  
  1519.     return help_text;
  1520.     }
  1521.  
  1522.  
  1523.     void RebuildDialog()
  1524.     {
  1525.     if ( ! SanityCheck( false ) )    return;
  1526.  
  1527.     term contents = `Empty();
  1528.  
  1529.     if ( UI::HasSpecialWidget(`DumbTab) && HaveSlideSupport()
  1530.         && HaveSlides() )
  1531.     {
  1532.         contents =
  1533.         `DumbTab(`id(`dumbTab ),
  1534.                  [
  1535.                 // tab
  1536.                   `item(`id(`showSlide   ), _("Slide Sho&w") ),
  1537.                 // tab
  1538.                   `item(`id(`showDetails ), _("&Details")       )
  1539.                   ],
  1540.               `VBox(
  1541.                 `VSpacing( 0.4 ),
  1542.                 `VWeight( 1,    // lower layout priority
  1543.                       `HBox(
  1544.                         `HSpacing( 1 ),
  1545.                         `ReplacePoint(`id(`tabContents), SlidePageWidgets() ),
  1546.                         `HSpacing( 0.5 )
  1547.                         )
  1548.                       ),
  1549.                 `VSpacing( 0.4 )
  1550.                 )
  1551.               );
  1552.  
  1553.     }
  1554.     else
  1555.     {
  1556.         contents = DetailsPageWidgets();
  1557.     }
  1558.  
  1559.     Wizard::SetContents(
  1560.                 // Dialog heading while software packages are being installed
  1561.                 _("Package Installation"),
  1562.                 contents,
  1563.                 HelpText(),
  1564.                 false, false );    // has_back, has_next
  1565.  
  1566.     widgets_created = true;
  1567.  
  1568.     if ( ! HaveSlides() && ShowingSlide() )
  1569.         SwitchToDetailsView();
  1570.     }
  1571.  
  1572.  
  1573.  
  1574.     /**
  1575.      * Open the slide show base dialog with empty work area (placeholder for
  1576.      * the image) and CD statistics.
  1577.      **/
  1578.     void OpenSlideShowBaseDialog()
  1579.     {
  1580.     if ( ! Wizard::IsWizardDialog() ) // If there is no Wizard dialog open already, open one
  1581.     {
  1582.         Wizard::OpenNextBackDialog();
  1583.         opened_own_wizard = true;
  1584.     }
  1585.  
  1586.     UI::WizardCommand(`ProtectNextButton( false ) );
  1587.     Wizard::RestoreBackButton();
  1588.     Wizard::RestoreAbortButton();
  1589.     Wizard::RestoreNextButton();
  1590.  
  1591.     Wizard::SetContents(
  1592.                 // Dialog heading while software packages are being installed
  1593.                 _("Package Installation"),
  1594.                 `Empty(),        // Wait until InitPkgData() is called from outside
  1595.                 HelpText(),
  1596.                 false, false );    // has_back, has_next
  1597.  
  1598.     RebuildDialog();
  1599.     Wizard::SetTitleIcon( "software" );
  1600.     }
  1601.  
  1602.  
  1603.     /**
  1604.      * Initialize internal pacakge data, such as remaining package sizes and
  1605.      * times. This may not be called before the pkginfo server is up and
  1606.      * running, so this cannot be reliably done from the constructor in all
  1607.      * cases.
  1608.      * @param force true to force reinitialization
  1609.      **/
  1610.     global void InitPkgData(boolean force)
  1611.     {
  1612.     if ( init_pkg_data_complete && ! force)
  1613.         return;
  1614.  
  1615.     // Reinititalize some globals (in case this is a second run)
  1616.     total_size_installed    = 0;
  1617.     total_time_elapsed    = 0;
  1618.     start_time        = -1;
  1619.     next_recalc_time    = -1;
  1620.     current_src_no        = -1;    // 1..n
  1621.     current_cd_no        = -1;    // 1..n
  1622.     next_src_no        = -1;
  1623.     next_cd_no        = -1;
  1624.     last_cd            = false;
  1625.     unit_is_seconds        = false;    // begin with package sizes
  1626.     bytes_per_second    = 1;
  1627.     current_slide_no    = 0;
  1628.     slide_start_time    = 0;
  1629.  
  1630.     list< list > src_list = Pkg::PkgMediaNames();
  1631.     inst_src_names = maplist( list src, src_list, ``(src[0]:"CD") );
  1632.  
  1633.     y2milestone ("Media names: %1", inst_src_names);
  1634.  
  1635.     integer index = 0;
  1636.  
  1637.     srcid_to_current_src_no = listmap( list src, src_list, {
  1638.       index = index + 1;
  1639.       return $[src[1]:-1 : index];
  1640.     });
  1641.  
  1642.     y2milestone ("Source mapping information: %1", srcid_to_current_src_no );
  1643.  
  1644.     total_sizes_per_cd_per_src        = Pkg::PkgMediaSizes();
  1645.     total_pkg_count_per_cd_per_src        = Pkg::PkgMediaCount();
  1646.  
  1647.  
  1648.     total_size_to_install            = ListSum( flatten( total_sizes_per_cd_per_src ) );
  1649.     remaining_sizes_per_cd_per_src        = (list<list <integer> >) eval (total_sizes_per_cd_per_src);
  1650.     remaining_pkg_count_per_cd_per_src    = (list<list <integer> >) eval (total_pkg_count_per_cd_per_src);
  1651.     total_cd_count                = size( flatten( total_sizes_per_cd_per_src ) );
  1652.     init_pkg_data_complete            = true;
  1653.     
  1654.     y2milestone( "SlideShow::InitPkgData() done; total_sizes_per_cd_per_src: %1", total_sizes_per_cd_per_src );
  1655.     y2milestone( "SlideShow::InitPkgData(): pkg: %1", total_pkg_count_per_cd_per_src );
  1656.     RebuildDialog();
  1657.     }
  1658.  
  1659.  
  1660.  
  1661.     /**
  1662.      * Try to figure out what media will be needed next
  1663.      * and set next_src_no and next_cd_no accordingly.
  1664.      **/
  1665.     void FindNextMedia()
  1666.     {
  1667.     // Normally we would have to use current_cd_no+1,
  1668.     // but since this uses 1..n and we need 0..n-1
  1669.     // for array subscripts anyway, use it as it is.
  1670.     next_cd_no    = current_cd_no;
  1671.     next_src_no    = current_src_no-1;
  1672.     last_cd        = false;
  1673.  
  1674.     while ( next_src_no < size( remaining_sizes_per_cd_per_src ) )
  1675.     {
  1676.         list<integer> remaining_sizes = remaining_sizes_per_cd_per_src[ next_src_no ]: [];
  1677.  
  1678.         while ( next_cd_no < size( remaining_sizes ) )
  1679.         {
  1680.         if ( remaining_sizes[ next_cd_no ]:0 > 0 )
  1681.         {
  1682.             if ( debug )
  1683.             y2milestone( "Next media: src: %1 CD: %2", next_src_no, next_cd_no );
  1684.             return;
  1685.         }
  1686.         else
  1687.         {
  1688.             next_cd_no = next_cd_no + 1;
  1689.         }
  1690.         }
  1691.  
  1692.         next_src_no = next_src_no + 1;
  1693.     }
  1694.  
  1695.     if ( debug )
  1696.         y2milestone( "No next media - all done" );
  1697.  
  1698.     next_src_no    = -1;
  1699.     next_cd_no    = -1;
  1700.     last_cd        = true;
  1701.     }
  1702.  
  1703.  
  1704.     /**
  1705.      * Set the current source and CD number. Must be called for each CD change.
  1706.      * src_no: 1...n
  1707.      * cd_no:  1...n
  1708.      **/
  1709.     global void SetCurrentCdNo( integer src_no, integer cd_no )
  1710.     {
  1711.     if (cd_no == 0)
  1712.     {
  1713.         y2milestone("medium number 0, using medium number 1");
  1714.         cd_no = 1;
  1715.     }
  1716.  
  1717.     y2milestone("SetCurrentCdNo() - src: %1 , CD: %2", src_no, cd_no);
  1718.     current_src_no = srcid_to_current_src_no[src_no]:-1;
  1719.     current_cd_no  = cd_no;
  1720.  
  1721.     CheckForSlides();
  1722.     FindNextMedia();
  1723.  
  1724.     if ( HaveSlides() && HaveSlideSupport() )
  1725.     {
  1726.         if ( ! HaveSlideWidget() )
  1727.         {
  1728.         RebuildDialog();
  1729.  
  1730.         if ( user_switched_to_details )
  1731.             SwitchToDetailsView();
  1732.         }
  1733.  
  1734.         if ( ! user_switched_to_details ) // Don't override explicit user request!
  1735.         {
  1736.         SwitchToSlideView();
  1737.         LoadSlide(0);
  1738.         }
  1739.     }
  1740.     else
  1741.     {
  1742.         if ( ! ShowingDetails() )
  1743.         RebuildDialog();
  1744.         else
  1745.         UpdateTotalProgress();
  1746.  
  1747.         current_slide_no = 0;
  1748.     }
  1749.     }
  1750.  
  1751.  
  1752.     /**
  1753.      * Process (slide show) input (button press).
  1754.      **/
  1755.     global void HandleInput( any button )
  1756.     {
  1757.     if ( button == `showDetails && ! ShowingDetails() )
  1758.     {
  1759.         user_switched_to_details = true ;
  1760.         SwitchToDetailsView();
  1761.     }
  1762.     else if ( button == `showSlide && ! ShowingSlide() )
  1763.     {
  1764.         if ( HaveSlides() )
  1765.         {
  1766.         user_switched_to_details = false;
  1767.         SwitchToSlideView();
  1768.         LoadSlide( current_slide_no );
  1769.         }
  1770.         else
  1771.         {
  1772.         UI::ChangeWidget(`dumbTab, `CurrentItem, `showDetails );
  1773.         }
  1774.     }
  1775.     else if ( button == `debugHotkey )
  1776.     {
  1777.         debug = ! debug;
  1778.         y2milestone( "Debug mode: %1", debug );
  1779.     }
  1780.     }
  1781.  
  1782.  
  1783.     /**
  1784.      * Install callbacks for slideshow. Should be in SlideShowCallbacks but
  1785.      * that doesn't work at the moment.
  1786.      */
  1787.     global void InstallSlideShowCallbacks()
  1788.     {
  1789.     y2milestone( "InstallSlideShowCallbacks");
  1790.  
  1791.     Pkg::CallbackStartPackage ("SlideShowCallbacks::StartPackage");
  1792.     Pkg::CallbackProgressPackage ("SlideShowCallbacks::ProgressPackage");
  1793.     Pkg::CallbackDonePackage ("SlideShowCallbacks::DonePackage");
  1794.  
  1795.     Pkg::CallbackStartProvide ("SlideShowCallbacks::StartProvide");
  1796.     Pkg::CallbackProgressProvide ("SlideShowCallbacks::ProgressProvide");
  1797.     Pkg::CallbackDoneProvide ("SlideShowCallbacks::DoneProvide");
  1798.  
  1799.     Pkg::CallbackSourceChange ("SlideShowCallbacks::CallbackSourceChange");
  1800.  
  1801.     Pkg::CallbackStartDeltaDownload ("SlideShowCallbacks::StartDeltaProvide");
  1802.     Pkg::CallbackProgressDeltaDownload ("SlideShowCallbacks::ProgressProvide");
  1803.     Pkg::CallbackProblemDeltaDownload ("SlideShowCallbacks::ProblemDeltaDownload");
  1804.     Pkg::CallbackFinishDeltaDownload ("SlideShowCallbacks::FinishPatchDeltaProvide");
  1805.  
  1806.     Pkg::CallbackStartDeltaApply ("SlideShowCallbacks::StartDeltaApply");
  1807.     Pkg::CallbackProgressDeltaApply ("SlideShowCallbacks::ProgressDeltaApply");
  1808.     Pkg::CallbackProblemDeltaApply ("SlideShowCallbacks::ProblemDeltaApply");
  1809.     Pkg::CallbackFinishDeltaApply ("SlideShowCallbacks::FinishPatchDeltaProvide");
  1810.  
  1811.     Pkg::CallbackStartPatchDownload ("SlideShowCallbacks::StartPatchProvide");
  1812.     Pkg::CallbackProgressPatchDownload ("SlideShowCallbacks::ProgressProvide");
  1813.     Pkg::CallbackProblemPatchDownload ("SlideShowCallbacks::ProblemPatchDownload");
  1814.     Pkg::CallbackFinishPatchDownload ("SlideShowCallbacks::FinishPatchDeltaProvide");
  1815.  
  1816.     Pkg::CallbackScriptStart("SlideShowCallbacks::ScriptStart");
  1817.     Pkg::CallbackScriptProgress("SlideShowCallbacks::ScriptProgress");
  1818.     Pkg::CallbackScriptProblem("SlideShowCallbacks::ScriptProblem");
  1819.     Pkg::CallbackScriptFinish("SlideShowCallbacks::ScriptFinish");
  1820.  
  1821.     Pkg::CallbackMessage("SlideShowCallbacks::Message");
  1822.     }
  1823.  
  1824.  
  1825.     /**
  1826.      * Remove callbacks for slideshow. Should be in SlideShowCallbacks but
  1827.      * that doesn't work at the moment.
  1828.      */
  1829.     global void RemoveSlideShowCallbacks()
  1830.     {
  1831.     y2milestone( "RemoveSlideShowCallbacks");
  1832.  
  1833.     Pkg::CallbackStartPackage ("");
  1834.     Pkg::CallbackProgressPackage ("");
  1835.     Pkg::CallbackDonePackage ("");
  1836.  
  1837.     Pkg::CallbackStartProvide ("");
  1838.     Pkg::CallbackProgressProvide ("");
  1839.     Pkg::CallbackDoneProvide ("");
  1840.  
  1841.     Pkg::CallbackSourceChange ("");
  1842.  
  1843.     Pkg::CallbackStartDeltaDownload ("");
  1844.     Pkg::CallbackProgressDeltaDownload ("");
  1845.     Pkg::CallbackProblemDeltaDownload ("");
  1846.     Pkg::CallbackFinishDeltaDownload ("");
  1847.  
  1848.     Pkg::CallbackStartDeltaApply ("");
  1849.     Pkg::CallbackProgressDeltaApply ("");
  1850.     Pkg::CallbackProblemDeltaApply ("");
  1851.     Pkg::CallbackFinishDeltaApply ("");
  1852.  
  1853.     Pkg::CallbackStartPatchDownload ("");
  1854.     Pkg::CallbackProgressPatchDownload ("");
  1855.     Pkg::CallbackProblemPatchDownload ("");
  1856.     Pkg::CallbackFinishPatchDownload ("");
  1857.  
  1858.     Pkg::CallbackScriptStart("");
  1859.     Pkg::CallbackScriptProgress("");
  1860.     Pkg::CallbackScriptProblem("");
  1861.     Pkg::CallbackScriptFinish("");
  1862.  
  1863.     Pkg::CallbackMessage("");
  1864.     }
  1865.  
  1866.  
  1867.     /**
  1868.      * Open the slide show dialog.
  1869.      **/
  1870.     global void OpenSlideShowDialog()
  1871.     {
  1872.     InstallSlideShowCallbacks();
  1873.  
  1874.     OpenSlideShowBaseDialog();
  1875.     CheckForSlides();
  1876.  
  1877.     if ( HaveSlides() )
  1878.         LoadSlide(0);
  1879.     else
  1880.         SwitchToDetailsView();
  1881.  
  1882.     UpdateAllCdProgress();
  1883.     }
  1884.  
  1885.  
  1886.     /**
  1887.      * Close the slide show dialog.
  1888.      **/
  1889.     global void CloseSlideShowDialog()
  1890.     {
  1891.     if ( opened_own_wizard )
  1892.         Wizard::CloseDialog();
  1893.  
  1894.     RemoveSlideShowCallbacks();
  1895.     }
  1896.  
  1897. }
  1898.