OSDN Git Service

Multithreads are abandoned. Alternatly, The asyncore substitutes.(#16776)
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / thread_window.py
index aaa09f2..974a57e 100644 (file)
@@ -33,16 +33,16 @@ import traceback
 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
@@ -52,6 +52,7 @@ import bookmark_window
 import thread_view
 import thread_popup
 import submit_window
+import network_manager
 
 GLADE_FILENAME = "thread_window.glade"
 
@@ -109,37 +110,294 @@ class HTMLParserToThreadView:
             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)
@@ -163,12 +421,11 @@ class WinWrap(winwrapbase.WinWrapBase):
         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()
 
@@ -218,8 +475,9 @@ class WinWrap(winwrapbase.WinWrapBase):
         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()
@@ -266,246 +524,11 @@ class WinWrap(winwrapbase.WinWrapBase):
             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)
@@ -516,23 +539,10 @@ class WinWrap(winwrapbase.WinWrapBase):
     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)