OSDN Git Service

Use strict thread uri for windowlist.
[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
28 import misc
29 import datfile
30 import barehtmlparser
31 import idxfile
32 import session
33 import board_window
34 from http_sub import HTTPRedirectHandler302
35 from BbsType import bbs_type_judge_uri
36 from BbsType import bbs_type_exception
37
38 GLADE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
39                          "..", "data")
40 GLADE_FILENAME = "thread_window.glade"
41
42 def open_thread(uri):
43     if not uri:
44         raise ValueError, "parameter must not be empty"
45
46     bbs_type = bbs_type_judge_uri.get_type(uri)
47     if not bbs_type.is_thread():
48         raise bbs_type_exception.BbsTypeError, \
49               "the uri does not represent thread: " + uri
50     uri = bbs_type.get_thread_uri()  # use strict thread uri
51
52     winwrap = session.get_window(uri)
53     if winwrap:
54         # already opened
55         winwrap.window.present()
56         pass
57     else:
58         win_wrap = WinWrap(bbs_type.uri)  # pass original uri
59         session.window_created(uri, win_wrap)
60
61
62 class WinWrap:
63     hovering_over_link = False
64     hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
65     regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
66
67
68     def __init__(self, uri):
69         from BbsType import bbs_type_judge_uri
70         from BbsType import bbs_type_exception
71         self.bbs_type = bbs_type_judge_uri.get_type(uri)
72         if not self.bbs_type.is_thread():
73             raise bbs_type_exception.BbsTypeError, \
74                   "the uri does not represent thread: " + uri
75         self.bbs = self.bbs_type.bbs_type
76         self.board = self.bbs_type.board
77         self.thread = self.bbs_type.thread
78         self.host = self.bbs_type.host
79         self.uri = self.bbs_type.uri
80         self.size = 0
81         self.num = 0
82         self.title = None
83
84         glade_path = os.path.join(GLADE_DIR, GLADE_FILENAME)
85         self.widget_tree = gtk.glade.XML(glade_path)
86         self.window = self.widget_tree.get_widget("thread_window")
87         self.textview = self.widget_tree.get_widget("textview")
88         self.textbuffer = self.textview.get_buffer()
89         self.enditer = self.textbuffer.get_end_iter()
90         self.boldtag = self.textbuffer.create_tag(weight=pango.WEIGHT_BOLD)
91         self.leftmargintag = self.textbuffer.create_tag()
92         self.leftmargintag.set_property("left-margin", 20)
93
94         sigdic = {"on_toolbutton_refresh_clicked": self.update,
95                   "on_toolbutton_compose_clicked": self.on_compose_clicked,
96                   "on_refresh_activate": self.update,
97                   "on_close_activate": self.on_close_activate,
98                   "on_quit_activate": self.on_quit_activate,
99                   "on_show_board_activate": self.on_show_board_activate,
100                   "on_thread_window_destroy": self.on_thread_window_destroy}
101         self.widget_tree.signal_autoconnect(sigdic)
102
103         self.textview.connect("event-after", self.on_event_after)
104         self.textview.connect("motion-notify-event",
105                               self.on_motion_notify_event)
106         self.textview.connect("visibility-notify-event",
107                               self.on_visibility_notify_event)
108
109         self.load_dat()
110
111     def on_compose_clicked(self, widget):
112         import submit_window
113         submit_window.open(self.bbs_type.get_thread_uri())
114
115     def on_event_after(self, widget, event):
116         if event.type != gtk.gdk.BUTTON_RELEASE:
117             return False
118         if event.button != 1:
119             return False
120         buffer = widget.get_buffer()
121
122         try:
123             start, end = buffer.get_selection_bounds()
124         except ValueError:
125             pass
126         else:
127             if start.get_offset() != end.get_offset():
128                 return False
129
130         x, y = widget.window_to_buffer_coords(
131             gtk.TEXT_WINDOW_WIDGET, int (event.x), int(event.y))
132         iter = widget.get_iter_at_location(x, y)
133         if not iter.has_tag(self.leftmargintag) or x > 20:
134             tags = iter.get_tags()
135             for tag in tags:
136                 href = tag.get_data("href")
137                 if href:
138                     self.on_link_clicked(widget, href)
139         return False
140
141     def on_link_clicked(self, widget, href):
142         if href.startswith("http://"):
143             from BbsType import bbs_type_exception
144             import uri_opener
145             try:
146                 uri_opener.open_uri(href)
147             except bbs_type_exception.BbsTypeError:
148                 import gnome
149                 gnome.url_show(href)
150
151     def on_motion_notify_event(self, widget, event):
152         x, y = widget.window_to_buffer_coords(
153             gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y))
154         self.set_cursor_if_appropriate(widget, x, y)
155         widget.window.get_pointer()
156         return False
157
158     def on_visibility_notify_event(self, widget, event):
159         wx, wy, mod = widget.window.get_pointer()
160         bx, by = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy)
161
162         self.set_cursor_if_appropriate(widget, bx, by)
163         return False
164
165     def set_cursor_if_appropriate(self, widget, x, y):
166         hovering = False
167
168         buffer = widget.get_buffer()
169         iter = widget.get_iter_at_location(x, y)
170         if not iter.has_tag(self.leftmargintag) or x > 20:
171             tags = iter.get_tags()
172             for tag in tags:
173                 href = tag.get_data("href")
174                 if href:
175                     hovering = True
176
177         if hovering != self.hovering_over_link:
178             self.hovering_over_link = hovering
179
180         if self.hovering_over_link:
181             widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
182                 self.hand_cursor)
183         else:
184             widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
185                 self.regular_cursor)
186
187     def on_close_activate(self, widget):
188         self.window.destroy()
189
190     def on_thread_window_destroy(self, widget):
191         -1
192
193     def on_quit_activate(self, widget):
194         session.main_quit()
195
196     def on_show_board_activate(self, widget):
197         board_window.open_board(self.bbs_type.get_uri_base())
198
199     def http_get_dat(self, on_get_res):
200         datfile_url = self.bbs_type.get_dat_uri()
201
202         idx_dic = idxfile.load_idx(self.bbs, self.board, self.thread)
203         lastmod = idx_dic["lastModified"]
204         etag = idx_dic["etag"]
205
206         req = urllib2.Request(datfile_url)
207         if self.size > 0:
208             req.add_header("Range", "bytes=" + str(self.size) + "-")
209         if lastmod:
210             req.add_header("If-Modified-Since", lastmod)
211         if etag:
212             req.add_header("If-None-Match", etag)
213         print req.headers
214
215         opener = urllib2.build_opener(HTTPRedirectHandler302)
216         res = opener.open(req)
217         headers = res.info()
218         print headers
219
220         line = res.readline()
221         maybe_incomplete = False
222         while line:
223             if not line.endswith("\n"):
224                 maybe_incomplete = True
225                 print "does not end with \\n. maybe incomplete"
226                 break
227             on_get_res(line)
228             line = res.readline()
229
230         res.close()
231
232         if maybe_incomplete:
233             lastmod = None
234             etag = None
235         else:
236             if "Last-Modified" in headers:
237                 lastmod = headers["Last-Modified"]
238             if "ETag" in headers:
239                 etag = headers["Etag"]
240
241         if self.num > 0:
242             if not self.title:
243                 self.title = datfile.get_title_from_dat(
244                     self.bbs, self.board, self.thread)
245                 if self.title:
246                     self.window.set_title(self.title)
247             # save idx
248             idx_dic = {"title": self.title, "lineCount": self.num,
249                    "lastModified": lastmod, "etag": etag}
250             idxfile.save_idx(self.bbs, self.board, self.thread, idx_dic)
251
252             session.thread_idx_updated(
253                 self.bbs_type.get_thread_uri(), idx_dic)
254
255     def update(self, widget=None):
256         line_count = datfile.get_dat_line_count(
257             self.bbs, self.board, self.thread)
258
259         if line_count > self.num:
260             datfile.load_dat_partly(
261                 self.bbs, self.board, self.thread,
262                 self.append_rawres_to_buffer, self.num+1)
263
264
265         class FileWrap:
266             def __init__(self, path):
267                 self._file = None
268                 self._path = path
269             def __del__(self):
270                 self.close()
271             def seek(self, size):
272                 self.file().seek(size)
273             def write(self, data):
274                 self.file().write(data)
275             def close(self):
276                 if self._file:
277                     self._file.close()
278                     self._file = None
279             def file(self):
280                 if not self._file:
281                     basedir = os.path.dirname(self._path)
282                     if not os.path.isdir(basedir):
283                         os.makedirs(basedir)
284                     self._file = file(self._path, "a+")
285                 return self._file
286
287         dat_path = misc.get_thread_dat_path(self.bbs, self.board, self.thread)
288         dat_file = FileWrap(dat_path)
289
290         def save_line_and_append_to_buffer(line):
291             dat_file.seek(self.size)
292             dat_file.write(line)
293             self.append_rawres_to_buffer(line)
294
295         self.http_get_dat(save_line_and_append_to_buffer)
296         dat_file.close()
297
298     def load_dat(self):
299         self.size = 0
300         self.num = 0
301
302         self.title = datfile.get_title_from_dat(
303             self.bbs, self.board, self.thread)
304         if self.title:
305             self.window.set_title(self.title)
306
307         datfile.load_dat(self.bbs, self.board, self.thread,
308                          self.append_rawres_to_buffer)
309         self.textview.scroll_to_mark(self.textbuffer.get_insert(), 0)
310
311
312     def append_rawres_to_buffer(self, line):
313         self.size += len(line)
314         self.num += 1
315
316         h = lambda name,mail,date,msg: self.reselems_to_buffer(
317             self.num, name, mail, date, msg)
318
319         datfile.split_line_to_elems(line.decode("cp932", "replace"), h)
320         
321     def reselems_to_buffer(self, num, name, mail, date, msg):
322         p = barehtmlparser.BareHTMLParser(self.untiedata_to_buffer)
323         # number
324         p.feed(str(num) + " ")
325
326         # name
327         p.feed("<b>" + name + "</b>")
328
329         # mail
330         p.feed("[" + mail + "]")
331
332         # date
333         p.feed(date)
334         p.feed("<br>")
335
336         # msg
337         p.reset_func(self.untiedata_to_buffer_with_leftmargin)
338         p.feed(msg.lstrip(" "))
339
340         p.feed("<br><br>")
341         p.close()
342
343     def href_tag(self, href):
344         tag = self.textbuffer.create_tag(underline=pango.UNDERLINE_SINGLE)
345         tag.set_data("href", href)
346         return tag
347
348     def untiedata_to_buffer(self, data, bold, href):
349         if bold:
350             if href:
351                 self.textbuffer.insert_with_tags(
352                     self.enditer, data, self.boldtag, self.href_tag(href))
353             else:                
354                 self.textbuffer.insert_with_tags(
355                     self.enditer, data, self.boldtag)
356         else:
357             if href:
358                 self.textbuffer.insert(self.enditer, data, self.href(href))
359             else:
360                 self.textbuffer.insert(self.enditer, data)
361
362     def untiedata_to_buffer_with_leftmargin(self, data, bold, href):
363         if bold:
364             if href:
365                 self.textbuffer.insert_with_tags(
366                     self.enditer, data, self.boldtag,
367                     self.leftmargintag,self.href_tag(href))
368             else:
369                 self.textbuffer.insert_with_tags(
370                     self.enditer, data, self.boldtag, self.leftmargintag)
371         else:
372             if href:
373                 self.textbuffer.insert_with_tags(
374                     self.enditer, data,
375                     self.leftmargintag, self.href_tag(href))
376             else:
377                 self.textbuffer.insert_with_tags(
378                     self.enditer, data, self.leftmargintag)