A long time ago, I posted an ASCII-art download meter in Perl to
kragen-hacks.  It drew a little progress bar in equals signs and
estimated what time the file would finish downloading.  Here's a
version with Python and Tkinter.

It's useful even when iconified, at least if you're using a window
manager that displays the iconname and keeps it up to date.

First, progbar.py, which is imported by the download-meter program:
"""A crude progress bar for Tkinter.
"""
import Tkinter

class Progress:
    def __init__(self, parentwidget, width=300, height=22):
        self.width = width
        self.frame = Tkinter.Frame(parentwidget, relief='sunken',
                                   borderwidth=2, height=height)
        self.foreground = Tkinter.Frame(self.frame, relief='raised',
                                        borderwidth=2, height=height)
        self.background = Tkinter.Frame(self.frame, background='#555588',
                                        height=height)
        self.foreground.pack(side='left', fill='both', expand=1)
        self.fullness = 0
        self.redraw()
    def redraw(self):
        # When I didn't pack_forget the background, it would never shrink
        # below three pixels (unless the progress bar was constrained
        # in size).  So every time the thing got full, it would grow by
        # three pixels.  Likewise, when I didn't pack_forget the foreground,
        # it would never shrink below one pixel, so every time the thing got
        # empty, it would grow by one pixel.
        if self.winfo_ismapped():
            if self.background.winfo_ismapped():
                oldbgwidth = self.background.winfo_width()
            else: oldbgwidth = 0
            if self.foreground.winfo_ismapped():
                oldfgwidth = self.foreground.winfo_width()
            else: oldfgwidth = 0
            width = (oldfgwidth + oldbgwidth)
        else: width = self.width
        fgwidth = int(self.fullness * width + 0.5)
        bgwidth = width - fgwidth
        if bgwidth:
            self.background.pack(side='left', fill='both', expand=1)
            self.background.configure(width=bgwidth)
        else:
            self.background.pack_forget()
        if fgwidth:
            if bgwidth: before={'before': self.background}
            else: before = {}
            self.foreground.pack(before, side='left', fill='both', expand=1)
            self.foreground.configure(width=fgwidth)
        else:
            self.foreground.pack_forget()
        self.frame.update()
    def setfullness(self, fullness):
        self.fullness = fullness
        self.redraw()
    def __getattr__(self, name):
        return getattr(self.frame, name)

Here's the download meter program itself.
#!/usr/bin/env python
"""Draws a progress bar.

Some programs that download stuff have progress bars that tell you how much
of the file is downloaded, how much longer you can expect it to take, etc.
But some don't, and none of them tell me what time they expect it to finish.
(I have to calculate that myself.)  So here's a program to solve that problem.
Point it at a file, tell it how big you expect the file to be when it is fully
downloaded, and it will watch the file and linearly extrapolate when it thinks
the file will finish downloading, as well as drawing you a cute progress bar.
"""
# Make emacs happy: '
import progbar, Tkinter, sys, os.path, time

def usage(argv0):
    sys.stderr.write("%s: Usage: %s filename expectedsize\n" % (argv0, argv0))
    return -1

class downloadmeter:
    def __init__(self, window, filename, ultimatesize, donecb):
        self.window = window
        self.filename = filename
        self.ultimatesize = ultimatesize
        self.origsize = None
        self.starttime = None
        self.pb = progbar.Progress(window)
        self.donecb = donecb
        frame = Tkinter.Frame(window)
        self.fnlabel, self.sizelabel, self.percentlabel, self.etalabel = [
            Tkinter.Label(frame, text=x) for x in (filename, '', '',
                                                   'ETA unknown')]
        for x in self.pb, frame: x.pack(side='top', fill='both')
        frame.pack(expand=1)
        for x in frame.winfo_children(): x.pack(side='left', expand=1,
                                                fill='both')
        self.update()
    def update(self):
        # reschedule ourself
        self.window.after(200, self.update)

        try: size = os.path.getsize(self.filename)
        except OSError, v:  # file doesn't exist
            self.sizelabel.configure(text=v)
            return

        now = time.time()
        if self.origsize is None:
            self.origsize = size
            self.starttime = now

        if size >= self.ultimatesize: self.donecb()

        self.sizelabel.configure(text=str(size))
        fullness = size/float(self.ultimatesize)
        self.pb.setfullness(fullness)
        percent = "%d%%" % int(fullness * 100)
        self.percentlabel.configure(text=percent)
        # if we're in a toplevel window, set its icon name
        try: self.window.wm_iconname("%s %s" % (percent, self.filename))
        except AttributeError: pass # but if not, that's OK
        if now > self.starttime:
            # rate in bytes per second
            slope = (size - self.origsize) / (now - self.starttime)
            if slope > 0:
                timetowait = (self.ultimatesize - size) / slope
                eta = now + timetowait
                _, _, _, h, m, s, _, _, _ = time.localtime(eta)
                self.etalabel.configure(text="ETA %02d:%02d:%02d" % (h, m, s))

def main(argv):
    if len(argv) != 3:
        sys.exit(usage(argv[0]))
    w = Tkinter.Tk()
    w.wm_title("%s download meter" % argv[1])
    dm = downloadmeter(w, argv[1], int(argv[2]), lambda: sys.exit(0))
    w.mainloop()

if __name__ == "__main__": main(sys.argv)

Reply via email to