#!/usr/bin/env python
#
# Pyne - Python Newsreader and Emailer
#
# Copyright (c) 2000-2002 Tom Morton
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# Tom Morton <tom@moretom.net>
#
import utils
import gtk
import gobject
import cPickle
import time
import os
import os.path
import sys
import string
import glob
import webbrowser
from copy import copy
# remember location of pyne modules
pyne_path = os.path.split( os.path.abspath(sys.argv[0]) )[0]
from addressbook import *
import pynei18n
import boxtypes
import boxtypes.superbox
import boxtypes.loader
import mainwin
from pyneheaders import *
import pynemsg
import personality
import userconfig
import ptk.misc_widgets
class pyne_user:
"""
Each user of pyne will have one of these objects.
They may contain mailboxes, outboxes, NNTPboxes, etc.
"""
def save(self, save_all=0):
"""
save_all=1 if you want it to recursively save all folders.
"""
tosave = {}
# Folder tree structure
ftree = []
def _add_node(folder, kid_list):
this = (folder.uid, [])
kid_list.append(this)
if save_all:
folder.save(self)
folder.shutdown(self)
if not folder.__dict__.has_key("contents"):
return
for fol in folder.contents:
_add_node(fol, this[1])
for f in self.contents:
_add_node(f, ftree)
tosave["foldertree"] = ftree
def _saveme(key, tosave=tosave, self=self):
tosave[key] = self.__dict__[key]
# Stuff we want to save
_saveme("personalities")
_saveme("bodyfont")
_saveme("linewrap")
_saveme("replyhead")
_saveme("addressbook")
_saveme("col_text")
_saveme("col_quote")
_saveme("col_header")
_saveme("col_mnormal")
_saveme("col_mnobody")
_saveme("col_mreplied")
_saveme("col_mmarked")
_saveme("sort_type")
_saveme("opts")
_saveme("printer")
_saveme("last_expiry")
_saveme("tab_pos")
_saveme("ui_style")
_saveme("default_dir")
_saveme("html_parser")
_saveme("edit_cmd")
_saveme("mime_types")
_saveme("window_setup")
f = open("newuser.dat", "w")
cPickle.dump(ver_stamp, f, 1)
cPickle.dump("USER", f, 1)
cPickle.dump(tosave, f, 1)
f.close()
try:
# If old user.dat and .bak exist then remove them
os.remove("user.dat")
os.remove("user.bak")
except OSError:
pass
def load(self):
self.__dict__ = {}
version = ver_stamp
try:
f = open("newuser.dat", "r")
except IOError:
# User does not exist
pass
else:
version = cPickle.load(f)
type = cPickle.load(f)
self.__dict__ = cPickle.load(f)
f.close()
def _ifmissing(key, value, self=self):
if not self.__dict__.has_key(key):
self.__dict__[key] = value
# User personalities
_ifmissing("personalities", {})
# Font used for message body text
_ifmissing("bodyfont", "")
_ifmissing("linewrap", 72)
_ifmissing("replyhead", "On $DATE, $FROM wrote:")
# Address book
_ifmissing("addressbook", [])
# Texty colours
_ifmissing("col_text", '#000000')
_ifmissing("col_quote", '#0000cc')
_ifmissing("col_header", '#cc0000')
# Message view colours
_ifmissing("col_mnormal", '#000000')
_ifmissing("col_mnobody", '#7f7f7f')
_ifmissing("col_mmarked", '#cc0000')
_ifmissing("col_mreplied", '#0000cc')
# default: sort messages by date, newest to top (2==date)
_ifmissing("sort_type", (2, 0))
# user interface style
_ifmissing("ui_style", UI_DEFAULT)
# various boolean options. see OPT_xx at top
_ifmissing("opts", 0)
# printer command
_ifmissing("printer", 'lpr')
# When we last expired stuff
_ifmissing("last_expiry", time.localtime(time.time())[:3])
# position of tabs in quickview and composer
_ifmissing("tab_pos", int (gtk.POS_TOP))
# Default (attachment load/save, etc) directory
_ifmissing("default_dir", "~")
# parse html bodies with this:
_ifmissing("html_parser", "lynx -dump")
# alternative editor command
_ifmissing("edit_cmd", "xterm -e vim")
# Attachment handlers
_ifmissing("mime_types", [ ("image/*", "", 1),
("text/plain", "", 1),
("text/html", "mozilla", 0) ] )
# Window size info (width, height, hpane position, vpane position)
_ifmissing("window_setup", [])
###### Temporary stuff
self.contents = []
# List of open windows
self.windows = {}
# List of to-be-deleted temporary files
self.tempfiles = []
if self.__dict__.has_key("foldertree"):
ftree = self.foldertree
def _recurse_load_folders(conts, folder_node):
folder = boxtypes.loader.loader(folder_node[0], self)
conts.append(folder)
if len(folder_node[1]) > 0:
# It has children. Load them too
for fnode in folder_node[1]:
_recurse_load_folders(folder.contents, fnode)
for fnode in ftree:
_recurse_load_folders(self.contents, fnode)
del self.foldertree
else:
# New user. Give him some cute starting folders to play with :o)
# 'special' uids for non-deletable special folders (outbox,
a = boxtypes.outbox.outbox(self, "outbox")
self.contents.append(a)
a = boxtypes.storebox.storebox(self, "drafts")
a.name = _("Drafts")
self.contents.append(a)
a = boxtypes.storebox.storebox(self, "sent")
a.name = _("Sent")
self.contents.append(a)
a = boxtypes.storebox.storebox(self, "saved")
a.name = _("Saved")
self.contents.append(a)
a = boxtypes.storebox.storebox(self, "deleted")
a.name = _("Deleted")
self.contents.append(a)
# For safety...
self.save(save_all=1)
def recover(self, path):
"""
Not appropriate to pyne mailboxes...
"""
pass
box.save ()
def parent_of(self, folder):
"""
Return the parent object of 'folder'.
"""
uid = folder.uid
def find_parent(folder, uid=uid):
if folder.__dict__.has_key("contents"):
for x in folder.contents:
if x.uid == uid:
return folder
return utils.recurse_apply( [self], find_parent)[0]
def get_folder_by_uid(self, uid):
"""
Return folder in user.contents with uid==uid :-)
"""
def get_uid_matches(folder, uid=uid):
if folder.uid == uid:
return folder
folders = utils.recurse_apply(self.contents, get_uid_matches)
if len(folders) == 0:
return None
else:
return folders[0]
def set_preferences(self, parent_win):
"""
Allow the user to set his preferences like fonts, etc.
"""
userconfig.UserConfigWin(self, parent_win)
def update(self, update_type=0):
"""
Update all windows' folder lists.
By default update changed stuff only.
"""
for x in self.windows.keys():
self.windows[x].update(self, update_type)
# removed changed markers
def remove_changed(folder):
if folder.__dict__.has_key("changed"):
del folder.changed
# from 'utils.py'
utils.recurse_apply(self.contents, remove_changed)
def expire(self):
"""
Expire messages collected longer ago than self.expire_after
days.
"""
def expire_msgs(expire_msgs, folder, this_day, expire_after):
"""
Recursively remove links to msg_id in object.
"""
# if the object contains messages, remove msg_ids from
# them
if folder.__dict__.has_key("messages"):
messages = copy(folder.messages)
for x in messages:
msg = folder.load_header(x)
# fucked up headers
try:
len(msg)
except TypeError, e:
print "*",
try:
folder.delete_article(x)
except ValueError, e:
pass
continue
# test age
date_received = msg[HEAD_DATE_RECEIVED]
day = int(date_received/86400.0)
old = this_day - day
if old >= expire_after:
# unread messages: fix num_unread
if folder.__dict__.has_key ("num_unread") and \
not (msg[HEAD_OPTS] & MSG_ISREAD):
folder.num_unread -= 1
# delete it
try:
folder.delete_article(x)
except ValueError, e:
pass
############################################################
this_day = int(time.time()/86400.0)
# only test for expiry in folders with expire_after
def _pre_expire(folder, expire_msgs=expire_msgs, this_day=this_day):
if not folder.__dict__.has_key("expire_after"):
return
if folder.expire_after == None:
return
print "Pyne: Expiring folder ", folder.name
# recurse into folder looking for messages that have
# expired.
expire_msgs(expire_msgs, folder, this_day, folder.expire_after)
utils.recurse_apply(self.contents, _pre_expire)
def kill_window(self, num):
# First save its pane positions
if self.windows[num].__dict__.has_key("vpaned"):
size = list(self.window_setup[num])
if not self.windows[num].__dict__.has_key("notebook"):
# No panes if in tabbed mode
size[VPANE_POS] = self.windows[num].vpaned.get_position()
size[HPANE_POS] = self.windows[num].hpaned.get_position()
self.window_setup[num] = tuple(size)
# Destroy it
self.windows[num].destroy()
del self.windows[num]
# All windows closed: quit
if len(self.windows) == 0:
gtk.main_quit()
return
def new_window(self, display_msg = None):
"""
Open new window, with maximised quickview pane and showing
message (folder, msg-id) 'display_msg' if it is != None.
"""
# Find unused windows number
x = 0
while self.windows.has_key(x):
x = x + 1
if len(self.window_setup) <= x:
self.window_setup.append( (600, 400, 200, 200) )
win = mainwin.pyne_window(self, ver_string, self.window_setup[x], x, display_msg = display_msg)
win.update(self, mainwin.UPDATE_ALL)
self.windows[x] = win
def get_personality_uid(self):
i = 0
ids = self.personalities.keys()
while 1:
if not str(i) in ids:
return str(i)
i = i + 1
def get_personality(self, uid):
return self.personalities[uid]
def get_uid(self, prefix):
"""
Return unique id for new object.
"""
# build list of current ids
def get_uids(folder):
return folder.uid
uids = utils.recurse_apply(self.contents, get_uids)
# return an unused id
uid = 0
while 1:
if not prefix+str(uid) in uids:
return prefix+str(uid)
uid = uid + 1
def queue_action(self, act_flag):
self.act_flags = self.act_flags | act_flag
def register_child (self, pid):
"""
Stick child pids in here after forking and the timeout
function will poll them for exits so we are zombieless
"""
self.child_pids.append (pid)
def timeout_func(self):
# Update message/folder views.
if self.act_flags & ACT_UPDATE:
self.act_flags = self.act_flags & ~ACT_UPDATE
self.update()
for f in self.timeout_funcs:
f (self)
for i in xrange (len(self.child_pids)-1, -1, -1):
# waitpid returns (pid, status)
if self.child_pids[i] == os.waitpid (self.child_pids[i], os.WNOHANG)[0]:
del self.child_pids[i]
i = i+1
return True
def timeout_add (self, some_function):
"""
Functions should take 1 argument, user.
"""
if not (some_function in self.timeout_funcs):
self.timeout_funcs.append (some_function)
def timeout_remove (self, some_function):
if some_function in self.timeout_funcs:
self.timeout_funcs.remove (some_function)
def open_url (self, url):
"""
I love bacon :o)
"""
cmd = None
for mimehandler in self.mime_types:
if mimehandler[0] == "text/html":
# pyne internal (which means webbrowser interface)
if mimehandler[2] == 1:
break
cmd = mimehandler[1]
break
if cmd:
pid = os.fork ()
if pid == 0:
try:
os.execvp(cmd, (cmd, url))
except OSError:
os._exit(0)
else:
self.register_child (pid)
else:
pid = os.fork ()
if pid == 0:
webbrowser.open(url, 1)
os._exit(0)
else:
self.register_child (pid)
def start(self):
"""
Just get on with it...
"""
# Key format "%d%d%d" % (cached, isread, isreplied, ismarked)
self.msg_icons = {}
self.msg_cols = {}
self.act_flags = 0
self.child_pids = []
# just to shut it up with all the damn warnings we make this...
# shame on me :-(
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_read.xpm"))
self.msg_icons["1100"] = i
self.msg_cols["1100"] = "col_mnormal"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_unread.xpm"))
self.msg_icons["1000"] = i
self.msg_cols["1000"] = "col_mnormal"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_read_replied.xpm"))
self.msg_icons["1110"] = i
self.msg_cols["1110"] = "col_mreplied"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_uncached.xpm"))
self.msg_icons["0000"] = i
self.msg_cols["0000"] = "col_mnobody"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_unread_replied.xpm"))
self.msg_icons["1010"] = i
self.msg_cols["1010"] = "col_mreplied"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_read_marked.xpm"))
self.msg_icons["1101"] = i
self.msg_cols["1101"] = "col_mmarked"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_unread_marked.xpm"))
self.msg_icons["1001"] = i
self.msg_cols["1001"] = "col_mmarked"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_read_replied_marked.xpm"))
self.msg_icons["1111"] = i
self.msg_cols["1111"] = "col_mmarked"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_uncached_marked.xpm"))
self.msg_icons["0001"] = i
self.msg_cols["0001"] = "col_mmarked"
i = gtk.Image()
i.set_from_file(os.path.join (pyne_path,"icons","msg_unread_replied_marked.xpm"))
self.msg_icons["1011"] = i
self.msg_cols["1011"] = "col_mmarked"
# Start up main window
self.new_window()
# There are many depraved things we wish to perform
self.timeout_funcs = []
gobject.timeout_add(50, self.timeout_func)
# Input loop
gtk.main()
# Trash temporary files
for x in self.tempfiles:
# delete temporary files
try:
os.remove(x)
except OSError:
pass
self.tempfiles = []
# expire if not done today
if self.last_expiry != time.localtime(time.time())[:3]:
self.last_expiry = time.localtime(time.time())[:3]
self.expire()
# remove stuff we don't want to save
del self.msg_icons
del self.act_flags
# Save datafile
self.save(save_all=1)
# End
print "Pyne exited."
if __name__ == '__main__':
gtk.threads_init()
gtk.threads_enter()
# Help
if sys.argv[-1] == "--help":
print "Usage: pyne [option] [user location]"
print "Pyne is a GTK+ Newsreader/Emailer written in Python."
print
print "User location is optional, and defaults to ~/.pyne"
print
print " -f, --force Force startup after a crash by removing the lockfile"
print
print "Report bugs to <tom@moretom.net>"
sys.exit(0)
# get alternative location of .pyne:
for arg in sys.argv[1:]:
# skip other args
if arg[0] == "-":
continue
user_home = sys.argv[1]
break
else:
user_home = os.path.join(os.environ["HOME"], ".pyne-1.0")
# Remember this as location of pyne modules
sys.path.append(pyne_path)
# Change to the working directory
try:
os.chdir(user_home)
except OSError, e:
# It's either non-existant, or not a directory (eek!)
try:
os.mkdir(user_home)
os.chdir(user_home)
except OSError, e:
print "Error. Cannot create ~/%s/, or file exists with that name. HELP!!! :~{" % user_home
sys.exit(0)
# other arguments
for arg in sys.argv[1:]:
if arg == "-f" or arg == "--force":
try:
os.remove("pyne.lock")
except OSError, e: pass
# Check for a lock file
try:
f = open("pyne.lock", "r")
except IOError, e:
# None. make one.
f = open("pyne.lock", "w")
f.close()
else:
if ptk.misc_widgets.ReturnDialog ("Warning!", "There is already an instance of Pyne running or a Pyne\nsession terminated abnormally (crashed :-)\nIf so delete the %s/pyne.lock file." % user_home, ((gtk.STOCK_QUIT,0), ("Ignore",1))) == 0:
sys.exit()
# Get user.
user = pyne_user()
user.load()
# start main thread: user.start()
user.start()
# remove the lock file
os.remove("pyne.lock")
gtk.threads_leave()
syntax highlighted by Code2HTML, v. 0.9.1