OSDN Git Service

Add an update option to uri_opener.
[fukui-no-namari/fukui-no-namari.git] / src / Hage1 / thread_window.py
1 # Copyright (C) 2006 by Aiwota Programmer
2 # aiwotaprog@tetteke.tk
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 import pygtk
19 pygtk.require('2.0')
20 import gtk
21 import gtk.glade
22 import os.path
23 import codecs
24 import re
25 import pango
26 import urllib2
27 import urlparse
28 import gnome
29
30 import misc
31 import datfile
32 import barehtmlparser
33 import idxfile
34 import session
35 import board_window
36 import uri_opener
37 from http_sub import HTTPRedirectHandler302
38 from BbsType import bbs_type_judge_uri
39 from BbsType import bbs_type_exception
40
41 GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
42                          "..", "data")
43 GLADE_FILENAME = "thread_window.glade"
44
45 def open_thread(uri, update=False):
46     if not uri:
47         raise ValueError, "parameter must not be empty"
48
49     bbs_type = bbs_type_judge_uri.get_type(uri)
50     if not bbs_type.is_thread():
51         raise bbs_type_exception.BbsTypeError, \
52               "the uri does not represent thread: " + uri
53     uri = bbs_type.get_thread_uri()  # use strict thread uri
54
55     winwrap = session.get_window(uri)
56     if winwrap:
57         # already opened
58         winwrap.window.present()
59         if update:
60             winwrap.load(update)
61     else:
62         winwrap = WinWrap(bbs_type.uri)  # pass original uri
63         session.window_created(uri, winwrap)
64         winwrap.load(update)
65
66     # jump to the res if necessary.
67     winwrap.jump_to_res(bbs_type.uri)
68
69
70 class WinWrap:
71     hovering_over_link = False
72     hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
73     regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
74
75
76     def __init__(self, uri):
77         from BbsType import bbs_type_judge_uri
78         from BbsType import bbs_type_exception
79         self.bbs_type = bbs_type_judge_uri.get_type(uri)
80         if not self.bbs_type.is_thread():
81             raise bbs_type_exception.BbsTypeError, \
82                   "the uri does not represent thread: " + uri
83         self.bbs = self.bbs_type.bbs_type
84         self.board = self.bbs_type.board
85         self.thread = self.bbs_type.thread
86         self.host = self.bbs_type.host
87         self.uri = self.bbs_type.uri
88         self.size = 0
89         self.num = 0
90         self.title = None
91
92         glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
93         self.widget_tree = gtk.glade.XML(glade_path)
94         self.window = self.widget_tree.get_widget("thread_window")
95         self.textview = self.widget_tree.get_widget("textview")
96         self.textbuffer = self.textview.get_buffer()
97         self.enditer = self.textbuffer.get_end_iter()
98         self.boldtag = self.textbuffer.create_tag(weight=pango.WEIGHT_BOLD)
99         self.leftmargintag = self.textbuffer.create_tag()
100         self.leftmargintag.set_property("left-margin", 20)
101
102         sigdic = {"on_toolbutton_refresh_clicked": self.update,
103                   "on_toolbutton_compose_clicked": self.on_compose_clicked,
104                   "on_refresh_activate": self.update,
105                   "on_close_activate": self.on_close_activate,
106                   "on_quit_activate": self.on_quit_activate,
107                   "on_show_board_activate": self.on_show_board_activate,
108                   "on_thread_window_destroy": self.on_thread_window_destroy}
109         self.widget_tree.signal_autoconnect(sigdic)
110
111         self.textview.connect("event-after", self.on_event_after)
112         self.textview.connect("motion-notify-event",
113                               self.on_motion_notify_event)
114         self.textview.connect("visibility-notify-event",
115                               self.on_visibility_notify_event)
116
117     def on_compose_clicked(self, widget):
118         import submit_window
119         submit_window.open(self.bbs_type.get_thread_uri())
120
121     def on_event_after(self, widget, event):
122         if event.type != gtk.gdk.BUTTON_RELEASE:
123             return False
124         if event.button != 1:
125             return False
126         buffer = widget.get_buffer()
127
128         try:
129             start, end = buffer.get_selection_bounds()
130         except ValueError:
131             pass
132         else:
133             if start.get_offset() != end.get_offset():
134                 return False
135
136         x, y = widget.window_to_buffer_coords(
137             gtk.TEXT_WINDOW_WIDGET, int (event.x), int(event.y))
138         iter = widget.get_iter_at_location(x, y)
139         if not iter.has_tag(self.leftmargintag) or x > 20:
140             tags = iter.get_tags()
141             for tag in tags:
142                 href = tag.get_data("href")
143                 if href:
144                     self.on_link_clicked(widget, href)
145         return False
146
147     def on_link_clicked(self, widget, href):
148
149         if not href.startswith("http://"):
150             # maybe a relative uri.
151             href = urlparse.urljoin(self.bbs_type.get_uri_base(), href)
152
153         try:
154             uri_opener.open_uri(href)
155         except bbs_type_exception.BbsTypeError:
156             # not supported, show with the web browser.
157             gnome.url_show(href)
158
159     def on_motion_notify_event(self, widget, event):
160         x, y = widget.window_to_buffer_coords(
161             gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y))
162         self.set_cursor_if_appropriate(widget, x, y)
163         widget.window.get_pointer()
164         return False
165
166     def on_visibility_notify_event(self, widget, event):
167         wx, wy, mod = widget.window.get_pointer()
168         bx, by = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy)
169
170         self.set_cursor_if_appropriate(widget, bx, by)
171         return False
172
173     def set_cursor_if_appropriate(self, widget, x, y):
174         hovering = False
175
176         buffer = widget.get_buffer()
177         iter = widget.get_iter_at_location(x, y)
178         if not iter.has_tag(self.leftmargintag) or x > 20:
179             tags = iter.get_tags()
180             for tag in tags:
181                 href = tag.get_data("href")
182                 if href:
183                     hovering = True
184
185         if hovering != self.hovering_over_link:
186             self.hovering_over_link = hovering
187
188         if self.hovering_over_link:
189             widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
190                 self.hand_cursor)
191         else:
192             widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
193                 self.regular_cursor)
194
195     def on_close_activate(self, widget):
196         self.window.destroy()
197
198     def on_thread_window_destroy(self, widget):
199         -1
200
201     def on_quit_activate(self, widget):
202         session.main_quit()
203
204     def on_show_board_activate(self, widget):
205         board_window.open_board(self.bbs_type.get_uri_base())
206
207     def http_get_dat(self, on_get_res):
208         datfile_url = self.bbs_type.get_dat_uri()
209
210         idx_dic = idxfile.load_idx(self.bbs, self.board, self.thread)
211         lastmod = idx_dic["lastModified"]
212         etag = idx_dic["etag"]
213
214         req = urllib2.Request(datfile_url)
215         if self.size > 0:
216             req.add_header("Range", "bytes=" + str(self.size) + "-")
217         if lastmod:
218             req.add_header("If-Modified-Since", lastmod)
219         if etag:
220             req.add_header("If-None-Match", etag)
221         print req.headers
222
223         opener = urllib2.build_opener(HTTPRedirectHandler302)
224         res = opener.open(req)
225         headers = res.info()
226         print headers
227
228         line = res.readline()
229         maybe_incomplete = False
230         while line:
231             if not line.endswith("\n"):
232                 maybe_incomplete = True
233                 print "does not end with \\n. maybe incomplete"
234                 break
235             on_get_res(line)
236             line = res.readline()
237
238         res.close()
239
240         if maybe_incomplete:
241             lastmod = None
242             etag = None
243         else:
244             if "Last-Modified" in headers:
245                 lastmod = headers["Last-Modified"]
246             if "ETag" in headers:
247                 etag = headers["Etag"]
248
249         if self.num > 0:
250             if not self.title:
251                 self.title = datfile.get_title_from_dat(
252                     self.bbs, self.board, self.thread)
253                 if self.title:
254                     self.window.set_title(self.title)
255             # save idx
256             idx_dic = {"title": self.title, "lineCount": self.num,
257                    "lastModified": lastmod, "etag": etag}
258             idxfile.save_idx(self.bbs, self.board, self.thread, idx_dic)
259
260             session.thread_idx_updated(
261                 self.bbs_type.get_thread_uri(), idx_dic)
262
263     def update(self, widget=None):
264         line_count = datfile.get_dat_line_count(
265             self.bbs, self.board, self.thread)
266
267         if line_count > self.num:
268             datfile.load_dat_partly(
269                 self.bbs, self.board, self.thread,
270                 self.append_rawres_to_buffer, self.num+1)
271
272
273         class FileWrap:
274             def __init__(self, path):
275                 self._file = None
276                 self._path = path
277             def __del__(self):
278                 self.close()
279             def seek(self, size):
280                 self.file().seek(size)
281             def write(self, data):
282                 self.file().write(data)
283             def close(self):
284                 if self._file:
285                     self._file.close()
286                     self._file = None
287             def file(self):
288                 if not self._file:
289                     basedir = os.path.dirname(self._path)
290                     if not os.path.isdir(basedir):
291                         os.makedirs(basedir)
292                     self._file = file(self._path, "a+")
293                 return self._file
294
295         dat_path = misc.get_thread_dat_path(self.bbs, self.board, self.thread)
296         dat_file = FileWrap(dat_path)
297
298         def save_line_and_append_to_buffer(line):
299             dat_file.seek(self.size)
300             dat_file.write(line)
301             self.append_rawres_to_buffer(line)
302
303         self.http_get_dat(save_line_and_append_to_buffer)
304         dat_file.close()
305
306     def load_dat(self):
307         self.size = 0
308         self.num = 0
309
310         self.title = datfile.get_title_from_dat(
311             self.bbs, self.board, self.thread)
312         if self.title:
313             self.window.set_title(self.title)
314
315         self.textbuffer.create_mark("1", self.enditer, True)
316
317         datfile.load_dat(self.bbs, self.board, self.thread,
318                          self.append_rawres_to_buffer)
319         if self.num > 0:
320             # jump to the last res.
321             mark = self.textbuffer.get_mark(str(self.num+1))
322             if mark:
323                 self.textview.scroll_to_mark(mark, 0)
324
325
326     def append_rawres_to_buffer(self, line):
327         self.size += len(line)
328         self.num += 1
329
330         h = lambda name,mail,date,msg: self.reselems_to_buffer(
331             self.num, name, mail, date, msg)
332
333         datfile.split_line_to_elems(line.decode("cp932", "replace"), h)
334
335         # for next res
336         self.textbuffer.create_mark(str(self.num+1), self.enditer, True)
337
338     def reselems_to_buffer(self, num, name, mail, date, msg):
339         p = barehtmlparser.BareHTMLParser(self.untiedata_to_buffer)
340         # number
341         p.feed(str(num) + " ")
342
343         # name
344         p.feed("<b>" + name + "</b>")
345
346         # mail
347         p.feed("[" + mail + "]")
348
349         # date
350         p.feed(date)
351         p.feed("<br>")
352
353         # msg
354         p.reset_func(self.untiedata_to_buffer_with_leftmargin)
355         p.feed(msg.lstrip(" "))
356
357         p.feed("<br><br>")
358         p.close()
359
360     def href_tag(self, href):
361         tag = self.textbuffer.create_tag(underline=pango.UNDERLINE_SINGLE)
362         tag.set_data("href", href)
363         return tag
364
365     def untiedata_to_buffer(self, data, bold, href):
366         if bold:
367             if href:
368                 self.textbuffer.insert_with_tags(
369                     self.enditer, data, self.boldtag, self.href_tag(href))
370             else:                
371                 self.textbuffer.insert_with_tags(
372                     self.enditer, data, self.boldtag)
373         else:
374             if href:
375                 self.textbuffer.insert_with_tags(
376                     self.enditer, data, self.href_tag(href))
377             else:
378                 self.textbuffer.insert(self.enditer, data)
379
380     def untiedata_to_buffer_with_leftmargin(self, data, bold, href):
381         if bold:
382             if href:
383                 self.textbuffer.insert_with_tags(
384                     self.enditer, data, self.boldtag,
385                     self.leftmargintag,self.href_tag(href))
386             else:
387                 self.textbuffer.insert_with_tags(
388                     self.enditer, data, self.boldtag, self.leftmargintag)
389         else:
390             if href:
391                 self.textbuffer.insert_with_tags(
392                     self.enditer, data,
393                     self.leftmargintag, self.href_tag(href))
394             else:
395                 self.textbuffer.insert_with_tags(
396                     self.enditer, data, self.leftmargintag)
397
398     def jump_to_res(self, uri):
399         strict_uri = self.bbs_type.get_thread_uri()
400         if uri != strict_uri and uri.startswith(strict_uri):
401             resnum = uri[len(strict_uri):]
402             match = re.match("\d+", resnum)
403             if match:
404                 resnum = match.group()
405                 mark = self.textbuffer.get_mark(resnum)
406                 if mark:
407                     self.textview.scroll_to_mark(mark, 0, True, 0, 0)
408
409     def load(self, update=False):
410         dat_path = misc.get_thread_dat_path(
411             self.bbs_type.bbs_type, self.bbs_type.board, self.bbs_type.thread)
412         dat_exists = os.path.exists(dat_path)
413         if update or not dat_exists:
414             self.update()
415         else:
416             self.load_dat()