github.com/vlifesystems/rulehunter@v0.0.0-20180501090014-673078aa4a83/html/builder.go (about)

     1  // Copyright (C) 2016-2018 vLife Systems Ltd <http://vlifesystems.com>
     2  // Licensed under an MIT licence.  Please see LICENSE.md for details.
     3  
     4  package html
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/vlifesystems/rulehunter/config"
    12  	"github.com/vlifesystems/rulehunter/logger"
    13  	"github.com/vlifesystems/rulehunter/progress"
    14  	"github.com/vlifesystems/rulehunter/quitter"
    15  )
    16  
    17  // Builder represents an html website builder
    18  type Builder struct {
    19  	cfg       *config.Config
    20  	pm        *progress.Monitor
    21  	log       logger.Logger
    22  	isRunning bool
    23  	sync.Mutex
    24  }
    25  
    26  func New(
    27  	cfg *config.Config,
    28  	pm *progress.Monitor,
    29  	l logger.Logger,
    30  ) *Builder {
    31  	return &Builder{
    32  		cfg:       cfg,
    33  		pm:        pm,
    34  		log:       l,
    35  		isRunning: false,
    36  	}
    37  }
    38  
    39  // This should be run as a goroutine and it will periodically generate html
    40  func (b *Builder) Run(q *quitter.Quitter) {
    41  	q.Add()
    42  	defer q.Done()
    43  	b.isRunning = true
    44  	defer func() {
    45  		b.Lock()
    46  		defer b.Unlock()
    47  		b.isRunning = false
    48  	}()
    49  	if err := b.generateAll(); err != nil {
    50  		b.log.Error(fmt.Errorf("Couldn't generate html: %s", err))
    51  	}
    52  
    53  	oldExperiments := b.pm.GetExperiments()
    54  
    55  	tickChan := time.NewTicker(time.Second * 5).C
    56  	for {
    57  		select {
    58  		case <-tickChan:
    59  			currentExperiments := b.pm.GetExperiments()
    60  			if !areReportsUptodate(oldExperiments, currentExperiments) {
    61  				if err := b.generateReports(); err != nil {
    62  					b.log.Error(fmt.Errorf("Couldn't generate html: %s", err))
    63  				}
    64  			}
    65  			if !isProgressUptodate(oldExperiments, currentExperiments) {
    66  				if err := b.generateProgress(); err != nil {
    67  					b.log.Error(fmt.Errorf("Couldn't generate html: %s", err))
    68  				}
    69  			}
    70  			oldExperiments = currentExperiments
    71  		case <-q.C:
    72  			if err := b.generateAll(); err != nil {
    73  				b.log.Error(fmt.Errorf("Couldn't generate html: %s", err))
    74  			}
    75  			return
    76  		}
    77  	}
    78  }
    79  
    80  // Running returns whether the html builder is running
    81  func (b *Builder) Running() bool {
    82  	b.Lock()
    83  	defer b.Unlock()
    84  	return b.isRunning
    85  }
    86  
    87  type generator func(*config.Config, *progress.Monitor) error
    88  
    89  func (b *Builder) generateAll() error {
    90  	generators := []generator{
    91  		generateActivityPage,
    92  		generateFront,
    93  		generateReports,
    94  		generateTagPages,
    95  		generateCategoryPages,
    96  	}
    97  	return b.generate(generators)
    98  }
    99  
   100  func (b *Builder) generateProgress() error {
   101  	generators := []generator{
   102  		generateActivityPage,
   103  		generateFront,
   104  	}
   105  	return b.generate(generators)
   106  }
   107  
   108  func (b *Builder) generateReports() error {
   109  	generators := []generator{
   110  		generateActivityPage,
   111  		generateFront,
   112  		generateReports,
   113  		generateTagPages,
   114  		generateCategoryPages,
   115  	}
   116  	return b.generate(generators)
   117  }
   118  
   119  func (b *Builder) generate(generators []generator) error {
   120  	for _, g := range generators {
   121  		if err := g(b.cfg, b.pm); err != nil {
   122  			return err
   123  		}
   124  	}
   125  	return nil
   126  }
   127  
   128  func areReportsUptodate(
   129  	oldExperiments, currentExperiments []*progress.Experiment,
   130  ) bool {
   131  	for _, ce := range currentExperiments {
   132  		if ce.Status.State == progress.Success &&
   133  			time.Since(ce.Status.Stamp).Seconds() < 10 {
   134  			return false
   135  		}
   136  	}
   137  	return true
   138  }
   139  
   140  func isProgressUptodate(
   141  	oldExperiments, currentExperiments []*progress.Experiment,
   142  ) bool {
   143  	if len(currentExperiments) != len(oldExperiments) {
   144  		return false
   145  	}
   146  	for _, ce := range currentExperiments {
   147  		for _, oe := range oldExperiments {
   148  			if !ce.IsEqual(oe) {
   149  				return false
   150  			}
   151  		}
   152  	}
   153  	return true
   154  }