geary-progress-monitor.vala 7.93 KB
Newer Older
1
/* Copyright 2013-2014 Yorba Foundation
2 3 4 5 6 7 8 9 10 11 12 13
 *
 * This software is licensed under the GNU Lesser General Public License
 * (version 2.1 or later).  See the COPYING file in this distribution.
 */

/**
 * Type of progress monitor.
 */
public enum Geary.ProgressType {
    AGGREGATED,
    ACTIVITY,
    DB_UPGRADE,
14 15
    SEARCH_INDEX,
    DB_VACUUM
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
}

/**
 * Base class for progress monitoring.
 */
public abstract class Geary.ProgressMonitor : BaseObject {
    public const double MIN = 0.0;
    public const double MAX = 1.0;
    
    public double progress { get; protected set; default = MIN; }
    public bool is_in_progress { get; protected set; default = false; }
    public Geary.ProgressType progress_type { get; protected set; }
    
    /**
     * The start signal is fired just before progress begins.  It will not fire again until after
     * {@link finish} has fired.
     */
    public signal void start();
    
    /**
     * Notifies the user of existing progress.  Note that monitor refers to the monitor that
     * invoked this update, which may not be the same as this object.
     */
    public signal void update(double total_progress, double change, Geary.ProgressMonitor monitor);
    
    /**
     * Finish is fired when progress has completed.
     */
    public signal void finish();
    
    /**
Jim Nelson's avatar
Jim Nelson committed
47 48 49
     * Users must call this before calling update.
     *
     * Must not be called again until {@link ProgressMonitor.notify_finish} has been called.
50 51 52 53 54 55 56 57 58 59
     */ 
    public virtual void notify_start() {
        assert(!is_in_progress);
        progress = MIN;
        is_in_progress = true;
        
        start();
    }
    
    /**
Jim Nelson's avatar
Jim Nelson committed
60 61 62
     * Users must call this when progress has completed.
     *
     * Must only be called after {@link ProgressMonitor.notify_start}.
63 64 65 66 67 68 69 70 71
     */
    public virtual void notify_finish() {
        assert(is_in_progress);
        is_in_progress = false;
        
        finish();
    }
}

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
/**
 * A reentrant {@link ProgressMonitor}.
 *
 * This is not thread-safe; it's designed for single-threaded asynchronous (non-blocking) use.
 */

public class Geary.ReentrantProgressMonitor : Geary.ProgressMonitor {
    private int start_count = 0;
    
    public ReentrantProgressMonitor(ProgressType type) {
        this.progress_type = type;
    }
    
    /**
     * @inheritDoc
     *
     * Unlike the base class implementation, this may be called multiple times successively without
     * a problem, but each must be matched by a {@link notify_finish} to completely stop the
     * monitor.
     *
     * This is not thread-safe; it's designed for single-threaded asynchronous (non-blocking) use.
     */
    public override void notify_start() {
        if (start_count++ == 0)
            base.notify_start();
    }
    
    /**
     * @inheritDoc
     *
     * Unlike the base class implementation, this may be called multiple times successively as
     * long as they were matched by a prior {@link notify_start}.
     *
     * This is not thread-safe; it's designed for single-threaded asynchronous (non-blocking) use.
     */
    public override void notify_finish() {
        bool finished = (--start_count == 0);
        
        // prevent underflow before signalling
        start_count = start_count.clamp(0, int.MAX);
        
        if (finished)
            base.notify_finish();
    }
}

118 119 120 121 122 123 124 125 126 127 128 129
/**
 * Captures the progress of a single action.
 */
public class Geary.SimpleProgressMonitor : Geary.ProgressMonitor {
    /**
     * Creates a new progress monitor of the given type.
     */
    public SimpleProgressMonitor(ProgressType type) {
        this.progress_type = type;
    }
    
    /**
Jim Nelson's avatar
Jim Nelson committed
130 131 132 133 134
     * Updates the progress by the given value.  Must be between {@link ProgressMonitor.MIN} and
     * {@link ProgressMonitor.MAX}.
     *
     * Must only be called after {@link ProgressMonitor.notify_start} and before
     * {@link ProgressMonitor.notify_finish}.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
     */
    public void increment(double value) {
        assert(value > 0);
        assert(is_in_progress);
        
        if (progress + value > MAX)
            value = MAX - progress;
        
        progress += value;
        update(progress, value, this);
    }
}

/**
 * Monitors the progress of a countable interval.  Note that min and max are inclusive.
 */
public class Geary.IntervalProgressMonitor : Geary.ProgressMonitor {
    private int min_interval;
    private int max_interval;
    private int current = 0;
    
    /**
     * Creates a new progress monitor with the given interval range.
     */
    public IntervalProgressMonitor(ProgressType type, int min, int max) {
        this.progress_type = type;
        this.min_interval = min;
        this.max_interval = max;
    }
    
    /**
     * Sets a new interval.  Must not be done while in progress.
     */
    public void set_interval(int min, int max) {
        assert(!is_in_progress);
        this.min_interval = min;
        this.max_interval = max;
    }
    
    public override void notify_start() {
        current = 0;
        base.notify_start();
    }
    
    /**
     * Incrememts the progress 
     */
    public void increment(int count = 1) {
        assert(is_in_progress);
        assert(count + progress >= min_interval);
        assert(count + progress <= max_interval);
        
        current += count;
        
        double new_progress = (1.0 * current - min_interval) / (1.0 * max_interval - min_interval);
        double change = new_progress - progress;
        progress = new_progress;
        
        update(progress, change, this);
    }
}

/**
 * Captures progress of multiple actions by composing
 * many progress monitors into one.
 */
public class Geary.AggregateProgressMonitor : Geary.ProgressMonitor {
    private Gee.HashSet<Geary.ProgressMonitor> monitors = new Gee.HashSet<Geary.ProgressMonitor>();
    
    /**
     * Creates an aggregate progress monitor.
     */
    public AggregateProgressMonitor() {
        this.progress_type = Geary.ProgressType.AGGREGATED;
    }
    
    /**
     * Adds a new progress monitor to this aggregate.
     */
    public void add(Geary.ProgressMonitor pm) {
        // TODO: Handle the case where we add a new monitor during progress.
        monitors.add(pm);
        pm.start.connect(on_start);
        pm.update.connect(on_update);
        pm.finish.connect(on_finish);
    }
    
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
    public void remove(Geary.ProgressMonitor pm) {
        // TODO: Handle the case where we remove a new monitor during progress.
        monitors.remove(pm);
        pm.start.disconnect(on_start);
        pm.update.disconnect(on_update);
        pm.finish.disconnect(on_finish);
        
        if (pm.is_in_progress) {
            // If no other PMs are in progress, we must issue a finish signal.
            bool issue_signal = true;
            foreach(ProgressMonitor p in monitors) {
                if (p.is_in_progress) {
                    issue_signal = false;
                    break;
                }
            }
            
            if (issue_signal)
                notify_finish();
        }
    }
    
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
    private void on_start() {
        if (!is_in_progress)
            notify_start();
    }
    
    private void on_update(double total_progress, double change, ProgressMonitor monitor) {
        assert(is_in_progress);
        
        double updated_progress = MIN;
        foreach(Geary.ProgressMonitor pm in monitors)
            updated_progress += pm.progress;
        
        updated_progress /= monitors.size;
        
        double aggregated_change = updated_progress - progress;
        if (aggregated_change < 0)
            aggregated_change = 0;
        
        progress += updated_progress;
        
        if (progress > MAX)
            progress = MAX;
        
        update(progress, aggregated_change, monitor);
    }
    
    private void on_finish() {
        // Only signal completion once all progress monitors are complete.
        foreach(Geary.ProgressMonitor pm in monitors) {
            if (pm.is_in_progress)
                return;
        }
        
        notify_finish();
    }
}