import itertools
import os
import sys
+from StringIO import StringIO
import misc
-from misc import FileWrap, ThreadInvoker
+from misc import FileWrap
import datfile
import barehtmlparser
import idxfile
import session
import board_window
import uri_opener
-from http_sub import HTTPRedirectHandler302, HTTPDebugHandler
from BbsType import bbs_type_judge_uri
from BbsType import bbs_type_exception
import config
import thread_view
import thread_popup
import submit_window
+import network_manager
GLADE_FILENAME = "thread_window.glade"
self.layout = self.threadview.create_res_layout(
self.left_margin, self.resnum)
- gtk.gdk.threads_enter()
self.layout.add_text(data, bold, href)
- gtk.gdk.threads_leave()
def to_thread_view(self):
if self.layout is not None:
- # gobject.idle_add(self.threadview.add_layout, self.layout)
- gtk.gdk.threads_enter()
self.threadview.add_layout(self.layout)
- gtk.gdk.threads_leave()
self.initialize()
+class LoadDat:
+
+ def __init__(self, bbs_type, threadwindow, threadview, statusbar):
+ self.bbs_type = bbs_type
+ self.threadwindow = threadwindow
+ self.threadview = threadview
+ self.statusbar = statusbar
+ self.lock_obj = False
+ self.jump_request_num = 0
+ self.size = 0
+ self.num = 0
+ self.request_headers = None
+ self.response_headers = None
+
+ self.statusbar_context_id = self.statusbar.get_context_id(
+ "Thread Window Status")
+ self.statusbar.push(self.statusbar_context_id, "OK.")
+
+ def initialize(self):
+ self.num = 0
+ self.size = 0
+ self.threadview.initialize_buffer()
+
+ def jump_request(self, res_num):
+ if not self.threadview.jump_to_res(res_num):
+ self.jump_request_num = res_num
+
+ def _do_jump_if_need(self):
+ if self.jump_request_num:
+ num = self.jump_request_num
+ self.jump_request_num = 0
+ return self.threadview.jump_to_res(num)
+
+ def _do_jump(self):
+ if not self._do_jump_if_need():
+ return self.threadview.jump_to_the_end()
+
+ def _load(self):
+ dat_path = misc.get_thread_dat_path(self.bbs_type)
+ try:
+ fd = file(dat_path)
+ except IOError:
+ raise misc.StopChainException()
+ else:
+ i = 0
+ for line in fd:
+ self.append_rawres_to_buffer(line)
+ yield
+ fd.close()
+ self._do_jump()
+
+ def _on_end(self):
+ self.request_headers = None
+ self.response_headers = None
+ self._un_lock()
+
+ def _lock(self):
+ if self.lock_obj:
+ print "Busy."
+ return False
+ self.lock_obj = True
+ return True
+
+ def _un_lock(self):
+ self.lock_obj = False
+
+ def load(self):
+ self.jump_request_num = 0
+ if self._lock():
+ misc.chain(lambda *args: None, self._on_end, self._load())
+
+ def _load_and_save(self, fd):
+ dat_path = misc.get_thread_dat_path(self.bbs_type)
+ try:
+ save_fd = FileWrap(dat_path)
+ save_fd.seek(self.size)
+ except IOError:
+ raise misc.StopChainException()
+
+ for line in fd:
+ if not line.endswith("\n"):
+ # the last line is not terminated with '\n'
+ print "does not end with \\n. maybe incomplete"
+ self.response_headers["lastmodified"] = None
+ self.response_headers["etag"] = None
+ raise misc.StopChainException()
+ save_fd.write(line)
+ self.append_rawres_to_buffer(line)
+ yield
+ self.threadview.queue_draw()
+ self._do_jump_if_need()
+
+ def on_received(self, res):
+ headers = res.headers
+ status = res.status
+
+ if "Last-modified".capitalize() in headers:
+ self.statusbar.pop(self.statusbar_context_id)
+ self.statusbar.push(self.statusbar_context_id,
+ "%s [%s]" % (status, headers["last-modified".capitalize()]))
+ else:
+ self.statusbar.pop(self.statusbar_context_id)
+ self.statusbar.push(self.statusbar_context_id, "%s" % status)
+
+ version, code, msg = status.split(None, 2)
+ code = int(code)
+ if code != 200 and code != 206:
+ self._on_end()
+ return
+ res.code = code
+
+ if "Range".capitalize() in self.request_headers and code == 200:
+ self.initialize()
+
+ self.response_headers = headers
+ fd = StringIO(res.message)
+
+ def save_and_end():
+ try:
+ self._saveidx()
+ finally:
+ self._on_end()
+
+ misc.chain(lambda *args: None, save_and_end, self._load_and_save(fd))
+
+ def _saveidx(self):
+ lastmod = ""
+ etag = ""
+ if "last-modified".capitalize() in self.response_headers:
+ lastmod = self.response_headers["last-modified".capitalize()]
+ if "etag".capitalize() in self.response_headers:
+ etag = self.response_headers["etag".capitalize()]
+
+ if self.num > 0:
+ # save idx
+ idx_dic = {"title": self.title, "lineCount": self.num,
+ "lastModified": lastmod, "etag": etag}
+ idxfile.save_idx(self.bbs_type, idx_dic)
+
+ session.thread_idx_updated(self.bbs_type.get_thread_uri(), idx_dic)
+
+ def _http_get_dat(self):
+
+ datfile_url = self.bbs_type.get_dat_uri()
+
+ headers = {}
+
+ idx_dic = idxfile.load_idx(self.bbs_type)
+ lastmod = idx_dic["lastModified"]
+ etag = idx_dic["etag"]
+
+ req = urllib2.Request(datfile_url)
+ req.add_header("User-agent", config.User_Agent)
+ if self.size > 0:
+ req.add_header("Range", "bytes=" + str(self.size) + "-")
+ if lastmod:
+ req.add_header("If-Modified-Since", lastmod)
+ if etag:
+ req.add_header("If-None-Match", etag)
+
+ req = self.bbs_type.set_extra_dat_request(req, self)
+ try:
+ network_manager.request_get(req.get_full_url(),
+ req.headers, self.on_received)
+ except network_manager.BusyException:
+ self._on_end()
+ self.statusbar.pop(self.statusbar_context_id)
+ self.statusbar.push(self.statusbar_context_id,
+ "The network is busy. Try later.")
+ except:
+ self._on_end()
+ self.statusbar.pop(self.statusbar_context_id)
+ self.statusbar.push(self.statusbar_context_id,
+ str(sys.exc_info()))
+ else:
+ self.statusbar.pop(self.statusbar_context_id)
+ self.statusbar.push(self.statusbar_context_id, "GET...")
+ self.request_headers = req.headers
+
+ def update(self):
+
+ self.jump_request_num = 0
+
+ if not self._lock():
+ return
+
+ line_count = datfile.get_dat_line_count(self.bbs_type)
+ if line_count != self.num:
+ # load dat file once more
+ self.num = 0
+ self.size = 0
+ self.threadview.initialize_buffer()
+ def end_and_get():
+ self._on_end()
+ self._http_get_dat()
+ misc.chain(lambda *args: None, end_and_get, self._load())
+ else:
+ self._http_get_dat()
+
+ def append_rawres_to_buffer(self, line):
+ self.size += len(line)
+ self.num += 1
+
+ if self.num == 1:
+ title = self.bbs_type.get_title_from_dat(line)
+ if title:
+ self.title = title
+ self.threadwindow.set_title(title)
+
+ line = line.decode(self.bbs_type.encoding, "replace")
+ m = self.bbs_type.dat_reg.match(line)
+ if m:
+ name = m.group("name")
+ mail = m.group("mail")
+ date = m.group("date")
+ msg = m.group("msg")
+ try:
+ num = int(m.group("num"))
+ except IndexError:
+ # use simple counter num
+ num = self.num
+ else:
+ # use num in dat
+ self.num = num
+ try:
+ id = m.group("id")
+ except IndexError:
+ pass
+ else:
+ if id:
+ date += " ID:" + id
+ self.reselems_to_buffer(num, name, mail, date, msg)
+ else:
+ self.reselems_to_buffer(
+ str(self.num), "Invalid Name", "Invalid Mail",
+ "Invalid Date", line)
+ print "maybe syntax error.", self.num, line
+
+ def reselems_to_buffer(self, num, name, mail, date, msg):
+ pipe = HTMLParserToThreadView(self.threadview, num, 0)
+ p = barehtmlparser.BareHTMLParser(
+ pipe.from_html_parser, pipe.on_new_line)
+
+ # First, create a pango layout for num,name,mail,date
+ # 'margin left' is 0
+ # number
+ p.feed(str(num) + " ")
+
+ # name
+ p.feed("<b>" + name + "</b>")
+
+ # mail
+ p.feed("[" + mail + "]")
+
+ # date
+ p.feed(date)
+ p.flush()
+
+ pipe.to_thread_view()
+
+
+ # Second, create a pango layout for message
+ # 'margin left' is 20
+ # msg
+ pipe.set_left_margin(20)
+ p.feed(msg.lstrip(" "))
+
+ p.feed("<br>")
+ p.close()
+
+ pipe.to_thread_view()
+
+
class WinWrap(winwrapbase.WinWrapBase):
- hovering_over_link = False
- hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
- regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
def __init__(self, uri):
- from BbsType import bbs_type_judge_uri
- from BbsType import bbs_type_exception
self.bbs_type = bbs_type_judge_uri.get_type(uri)
if not self.bbs_type.is_thread():
raise bbs_type_exception.BbsTypeError, \
"the uri does not represent thread: " + uri
- self.size = 0
- self.num = 0
- self.title = ""
- self.lock_obj = False
- self.jump_request_num = 0
- self.progress = False
glade_path = os.path.join(config.glade_dir, GLADE_FILENAME)
self.widget_tree = gtk.glade.XML(glade_path)
self.threadpopup.connect(
"uri-clicked-event", self.on_thread_popup_uri_clicked)
- self.statusbar_context_id = self.statusbar.get_context_id(
- "Thread Window Status")
- self.statusbar.push(self.statusbar_context_id, "OK.")
-
self.initialize_buffer()
+ self.dat_load = LoadDat(self.bbs_type, self.window,
+ self.threadview, self.statusbar)
+
self.restore()
self.window.show_all()
session.main_quit()
def _regist_as_bookmark(self):
+ title = self.window.get_title()
bookmark_list.bookmark_list.add_bookmark_with_edit(
- name=self.title, uri=self.bbs_type.uri)
+ name=title, uri=self.bbs_type.uri)
def _manage_bookmarks(self):
bookmark_window.open()
uri = "http://" + uri
return uri
- def http_get_dat(self, on_get_res):
- datfile_url = self.bbs_type.get_dat_uri()
-
- idx_dic = idxfile.load_idx(self.bbs_type)
- lastmod = idx_dic["lastModified"]
- etag = idx_dic["etag"]
-
- req = urllib2.Request(datfile_url)
- req.add_header("User-agent", config.User_Agent)
- if self.size > 0:
- req.add_header("Range", "bytes=" + str(self.size) + "-")
- if lastmod:
- req.add_header("If-Modified-Since", lastmod)
- if etag:
- req.add_header("If-None-Match", etag)
-
- def push():
- self.statusbar.pop(self.statusbar_context_id)
- self.statusbar.push(self.statusbar_context_id, "GET...")
- gobject.idle_add(push)
-
- req = self.bbs_type.set_extra_dat_request(req, self)
-
- opener = urllib2.build_opener(HTTPRedirectHandler302, HTTPDebugHandler)
- try:
- res = opener.open(req)
- except urllib2.HTTPError, e:
- def push(code, msg):
- message = "%d %s" % (code, msg)
- self.statusbar.pop(self.statusbar_context_id)
- self.statusbar.push(self.statusbar_context_id, message)
- gobject.idle_add(push, e.code, e.msg)
- else:
- headers = res.info()
-
- if "Last-Modified" in headers:
- la = headers["Last-Modified"]
- def push(code, msg, lastm):
- message = "%d %s [%s]" % (code, msg, lastm)
- self.statusbar.pop(self.statusbar_context_id)
- self.statusbar.push(self.statusbar_context_id, message)
- gobject.idle_add(push, res.code, res.msg, la)
- else:
- def push(code, msg):
- message = "%d %s" % (code, msg)
- self.statusbar.pop(self.statusbar_context_id)
- self.statusbar.push(self.statusbar_context_id, message)
- gobject.idle_add(push, res.code, res.msg)
-
- maybe_incomplete = False
- for line in res:
- if not line.endswith("\n"):
- maybe_incomplete = True
- print "does not end with \\n. maybe incomplete"
- break
- on_get_res(line)
-
- res.close()
-
- if maybe_incomplete:
- lastmod = None
- etag = None
- else:
- if "Last-Modified" in headers:
- lastmod = headers["Last-Modified"]
- if "ETag" in headers:
- etag = headers["Etag"]
-
- if self.num > 0:
- # save idx
- idx_dic = {"title": self.title, "lineCount": self.num,
- "lastModified": lastmod, "etag": etag}
- idxfile.save_idx(self.bbs_type, idx_dic)
-
- gobject.idle_add(session.thread_idx_updated,
- self.bbs_type.get_thread_uri(), idx_dic)
-
def update(self):
-
- self.jump_request_num = 0
-
- def load():
- line_count = datfile.get_dat_line_count(self.bbs_type)
- if line_count < self.num:
- self.num = 0
- self.size = 0
-
- gobject.idle_add(self.initialize_buffer)
-
- if line_count > self.num:
- datfile.load_dat_partly(
- self.bbs_type, self.append_rawres_to_buffer, self.num+1)
-
- def do_jump():
- if self.jump_request_num:
- if self.jump_request_num <= num:
- num = self.jump_request_num
- self.jump_request_num = 0
- self.jump_to_res(num)
- else:
- self.jump_to_the_end()
-
- gobject.idle_add(do_jump)
-
- def get():
- dat_path = misc.get_thread_dat_path(self.bbs_type)
- dat_file = FileWrap(dat_path)
-
- def save_line_and_append_to_buffer(line):
- dat_file.seek(self.size)
- dat_file.write(line)
- self.append_rawres_to_buffer(line)
-
- self.http_get_dat(save_line_and_append_to_buffer)
- gtk.gdk.threads_enter()
- self.threadview.queue_draw()
- gtk.gdk.threads_leave()
- dat_file.close()
-
- def do_jump():
- if self.jump_request_num:
- num = self.jump_request_num
- self.jump_request_num = 0
- self.jump_to_res(num)
-
- gobject.idle_add(do_jump)
-
- if self.lock():
-
- def on_end():
- self.un_lock()
- self.progress = False
-
- self.progress = True
- t = ThreadInvoker(lambda : gobject.idle_add(on_end), load, get)
- t.start()
+ self.dat_load.update()
def load_dat(self):
-
- self.size = 0
- self.num = 0
- self.jump_request_num = 0
-
- def load():
- datfile.load_dat(self.bbs_type, self.append_rawres_to_buffer)
-
- def jump():
-
- def do_jump():
- if self.jump_request_num:
- num = self.jump_request_num
- self.jump_request_num = 0
- self.jump_to_res(num)
- else:
- self.jump_to_the_end()
-
- gobject.idle_add(do_jump)
-
- if self.lock():
-
- def on_end():
- self.un_lock()
- self.progress = False
-
- self.progress = True
- t = ThreadInvoker(lambda : gobject.idle_add(on_end), load, jump)
- t.start()
-
- def append_rawres_to_buffer(self, line):
- self.size += len(line)
- self.num += 1
-
- if not self.title and self.num == 1:
- title = self.bbs_type.get_title_from_dat(line)
- if title:
- self.title = title
- gobject.idle_add(self.window.set_title, title)
-
- line = line.decode(self.bbs_type.encoding, "replace")
- m = self.bbs_type.dat_reg.match(line)
- if m:
- name = m.group("name")
- mail = m.group("mail")
- date = m.group("date")
- msg = m.group("msg")
- try:
- num = int(m.group("num"))
- except IndexError:
- # use simple counter num
- num = self.num
- else:
- # use num in dat
- self.num = num
- try:
- id = m.group("id")
- except IndexError:
- pass
- else:
- if id:
- date += " ID:" + id
- self.reselems_to_buffer(num, name, mail, date, msg)
- else:
- self.reselems_to_buffer(
- str(self.num), "Invalid Name", "Invalid Mail",
- "Invalid Date", line)
- print "maybe syntax error.", self.num, line
-
- def reselems_to_buffer(self, num, name, mail, date, msg):
- pipe = HTMLParserToThreadView(self.threadview, num, 0)
- p = barehtmlparser.BareHTMLParser(
- pipe.from_html_parser, pipe.on_new_line)
-
- # First, create a pango layout for num,name,mail,date
- # 'margin left' is 0
- # number
- p.feed(str(num) + " ")
-
- # name
- p.feed("<b>" + name + "</b>")
-
- # mail
- p.feed("[" + mail + "]")
-
- # date
- p.feed(date)
- p.flush()
-
- pipe.to_thread_view()
-
-
- # Second, create a pango layout for message
- # 'margin left' is 20
- # msg
- pipe.set_left_margin(20)
- p.feed(msg.lstrip(" "))
-
- p.feed("<br>")
- p.close()
-
- pipe.to_thread_view()
+ self.dat_load.load()
def jump(self, value):
gobject.idle_add(self.threadview.jump, value)
def jump_to_the_end(self):
gobject.idle_add(self.threadview.jump_to_the_end)
- def lock(self):
- if self.lock_obj:
- print "locked, try later."
- return False
- else:
- print "get lock"
- self.lock_obj = True
- return True
-
- def un_lock(self):
- self.lock_obj = False
- print "unlock"
-
def jump_to_res(self, resnum):
if self.threadview.jump_to_res(resnum):
return
- self.jump_request_num = resnum
+ self.dat_load.jump_request(resnum)
def load(self, update=False):
dat_path = misc.get_thread_dat_path(self.bbs_type)