def OnDelete(self, evt): PDEBUG('NOT IMPLEMENT.') if self._clip is None: PDEBUG('oops') self.dropClip(self._clip) self._clip = None
def OnClipSelected(self, event): self._cur_item = event.GetItem() id = self._cur_item.GetData() txt = self._cur_item.GetText() PDEBUG('ID: %d -- %s', id, txt) self._clip = self.model.getClipById(id) PDEBUG('CLIP: %s', self._clip) pass
def OnSearch(self, evt): PDEBUG('ENTER') target = self.search.GetValue() args = target.split() if not args: PDEBUG('empty search target...') return it = self.model.searchClips(args) self.showClips('Result for "%s"' % target, it) pass
def dropClip(self, clip): """Move specified clip into blacklist. """ SQL = ''' insert into blacklist select * from clippings where ID = %d''' % clip.id PDEBUG('SQL: %s', SQL) self.__execute__(SQL) SQL = '''delete from clippings where ID = %d''' % clip.id PDEBUG('SQL: %s', SQL) self.__execute__(SQL) pass
def OnRightClickClips(self, evt): """ """ PDEBUG('enter: %s', evt) # only do this part the first time so the events are only bound once if not hasattr(self, "popupID1"): self.popupID1 = wx.NewIdRef() self.popupID2 = wx.NewIdRef() self.popupID3 = wx.NewIdRef() self.popupID5 = wx.NewIdRef() self.popupID6 = wx.NewIdRef() self.Bind(wx.EVT_MENU, self.OnNew, id=self.popupID1) self.Bind(wx.EVT_MENU, self.OnEdit, id=self.popupID2) self.Bind(wx.EVT_MENU, self.OnDelete, id=self.popupID3) # make a menu menu = wx.Menu() # add some items menu.Append(self.popupID1, "New Clip") menu.Append(self.popupID2, "Edit Selected") menu.Append(self.popupID3, "Delete Selected") # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. self.PopupMenu(menu) menu.Destroy() pass
def OnBookSelected(self, event): """ """ self._book_id = event.GetItem().GetData() PDEBUG('BOOK_ID: %d', self._book_id) self.showClipsOfBook() self._clip = None pass
def getClipsByBookId(self, id): sql = ''' select clippings.id, books.name , pos, content from clippings inner join books on clippings.book = books.id and books.id = %d ''' % id PDEBUG('SQL: %s', sql) cursor = self.__execute__(sql) return ClipIter(cursor)
def OnEdit(self, evt): """ """ PDEBUG('NOT IMPLEMENT.') self.detailPanel.Show(True) self.detailPanel.OnNew(evt) pass
def cleanUpBooks(self, callback=None): """ """ num = 0 iter = self.getBooks() while iter.next(): num += self.cleanUpBook(iter.name, callback) PDEBUG('Total %d records cleaned up.', num) return num
def refreshContents(self): """ """ self.fillBooks() total_books = self.book_list.GetItemCount() PDEBUG('BOOK_ID: %s, total: %s', self._book_id, total_books) if self._book_id is None: self.book_list.Select(0) elif self._book_id >= total_books: self.book_list.Select(total_books - 1) else: self.book_list.Select(self._book_id) pass
def UpdateContent(self, clip): self._clip = clip PDEBUG('POS: %s, DATE: %s', clip.pos, clip.date) self.editor.SetValue(clip.content) html = self.md_converter.getHtmlPage(clip.content) self.browser.SetPage(html, '') self.st_type.SetLabel(clip.typ) self.st_date.SetLabel(clip.date) self.st_location.SetLabel(clip.pos) self.Layout() self.sizer.Fit(self) pass
def dropClip(self, clip): idx = self._cur_item.GetId() PDEBUG('DELETE IDX: %d', idx) self.model.dropClip(clip) self.clip_list.DeleteItem(idx) if idx < self.clip_list.GetItemCount(): self.clip_list.Select(idx) self.Refresh() self.clip_list.SetFocus() # If there are no clips left for current book, ask if we should remove # current book... if self.clip_list.GetItemCount() == 0: self.refreshContents() pass
def __execute__(self, sql, commit=True): """ Execute sql, and commit commit if asked. """ cursor = None PDEBUG('Executing SQL: %s' % sql) try: cursor = self.conn.execute(sql) except sqlite3.IntegrityError as e: print('DUP: %s' % e) except Exception as e: print('FATAL: %s -- %s' % (e, sql)) else: if commit: self.conn.commit() return cursor
def __cleanClipsById__(self, ids): if ids is None or len(ids) == 0: return PDEBUG('ids: %s', ids) id_strs = [] for id in ids: id_strs.append("'%d'" % id) sql = '''insert into blacklist select * from clippings where id in (%s)''' % ", ".join( id_strs) self.__execute__(sql, False) sql = '''delete from clippings where id in (%s)''' % ", ".join(id_strs) self.__execute__(sql, True) pass
def showClipsOfBook(self): """Show clips of book. """ PDEBUG('BOOK_ID: %d', self._book_id) book_iter = self.model.getBookById(self._book_id) if not book_iter.next(): print('Failed to load book info, book_id: %d' % (self._book_id)) sys.exit(1) book = book_iter.name author = book_iter.author iter = self.model.getClipsByBookId(self._book_id) self.showClips(book, author, iter) pass
def getDBPath(readonly=False): """Return path of database. """ path = None if sys.platform == 'darwin': path = '%s/Library/Application Support/Klip/' % os.getenv("HOME") else: home = os.getenv('HOME') if home is None: raise Exception("Platform %s not support." % sys.platform) else: path = '%s/.config/klip/' % (os.getenv('HOME')) if not os.path.exists(path): os.mkdir(path) path += 'klip.db' if readonly: path += '?mode = ro' PDEBUG('DB_PATH: %s', path) return path
def processQuery(j): """ """ PDEBUG('Query: %s', j) r = {} cmd = j.get('cmd', None) if cmd is None: r['err'] = 'No command is specified.' elif cmd == 'get-books': r = getBooks() pass elif cmd == 'get-clips': book_id = j.get('book-id', None) if book_id is None: r['err'] = 'Book id not specified.' else: r = getClips(book_id) pass else: r['err'] = 'Unsupported command: %s' % cmd return json.dumps(r) pass
def loadFile(self, path): """Load clippings from file. Arguments: - `path`: path of file """ books_added = 0 records_added = 0 books_to_clean = set() PDEBUG('Loading from file: %s', path) with open(path) as fd: while True: content = fd.read(PAGE_SIZE) if content is None: break if len(content) == 0: break pos = 0 while True: m = R_MATCH_ENTRY.search(content, pos) if m is None: new_content = fd.read(PAGE_SIZE) if len(new_content) == 0: print('New books: %d, new records: %d' % (books_added, records_added)) print('EOF reached...') return (books_added, records_added) else: content = content[pos:] + new_content pos = 0 else: (book, author) = process_book_name(m.group(1)) book = handleStr(book) author = handleStr(author) page = handleStr(m.group(2).strip()) time = handleStr(m.group(3).strip()) mark = handleStr(m.group(4).strip()) pos = m.end(0) bts = book.encode() if bts[0:3] == codecs.BOM_UTF8: PDEBUG('oops: ') PDEBUG('%X-%X-%X', bts[0], bts[1], bts[2]) sys.exit() if len(mark) == 0: continue res = R_MATCH_POS.match(page) if res is None: res = R_MATCH_PAGE.match(page) if res is None: PDEBUG('oops: %s -- %s', book, page) sys.exit(1) pos_str = res.group(1) typ_str = res.group(2) (new_book, new_clip) = \ self.__addEntry__( book, author, pos_str, typ_str, time, mark) if new_book: books_added += 1 if new_clip: books_to_clean.add(book) records_added += 1 if books_to_clean: PDEBUG('Books to clean: %s', books_to_clean) for book in books_to_clean: self.cleanUpBook(book) print('Total books added: %d, clips added:%d' % (books_added, records_added)) return (books_added, records_added)
def cleanUpBook(self, book, callback=None): """Clean up book """ PDEBUG('Cleaning book: %s', book) clips = {} # pos - (id, content) dup_id = [] # array of cons, where cdr should be dropped... iter = self.getClipsByBookName(book) total_clips = 0 while iter.next(): total_clips += 1 id = iter.id pos = iter.pos # simply ignore it if '-' not in pos. if '-' not in pos: continue if pos.startswith('-'): continue pos_array = [] for p in pos.split('-'): pos_array.append(int(p)) # Find nearest position, for now, linear search, change to binary # search later.... action = Action.APPEND # 0: append, 1: use new, 2: use old key_new = Range(pos_array[0], pos_array[1]) for key in clips.keys(): if key.covers(key_new): action = Action.USE_OLD break elif key_new.covers(key): action = Action.USE_NEW break pass if action == Action.APPEND: clips[key_new] = id elif action == Action.USE_NEW: old_id = clips.pop(key) dup_id.append((id, old_id)) clips[key_new] = id elif action == Action.USE_OLD: old_id = clips[key] dup_id.append((old_id, id)) else: print('Should not be here: %s' % (action)) ret = 0 if dup_id: do_remove = True if callback: to_be_remove = [] for (keep, drop) in dup_id: clip_keep = self.getClipById(keep) clip_drop = self.getClipById(drop) to_be_remove.append((clip_keep.content, clip_drop.content)) do_remove = callback(book, to_be_remove) if do_remove: ids = [] for id in dup_id: ids.append(id[1]) self.__cleanClipsById__(ids) ret += len(dup_id) return ret
def __init__(self, model_): """ """ self.model = model_ self._book = None self._book_id = None self._search_target = None super(KlipFrame, self).__init__(None, size=wx.Size(1200, 760), title='Klip', name='Klip') # ensure the parent's __init__ is called icon = icons.klip.GetIcon() self.SetIcon(icon) self.tb = TaskBarIcon(wx.adv.TBI_DOCK) self.tb.SetIcon(icon) sp = wx.SplitterWindow(self, style=wx.SP_BORDER | wx.SP_3DBORDER) sp.SetSplitMode(wx.SPLIT_VERTICAL) sp.SetMinimumPaneSize(50) self.detailPanel = KlipDetailWindow(self) self.SetMinSize(wx.Size(800, 600)) # create a panel in the frame pnl_books = wx.Panel(sp) pnl_clips = wx.Panel(sp) pnl_clips.SetBackgroundColour(wx.Colour(255, 255, 255)) sp.SplitVertically(pnl_books, pnl_clips, int(round(self.GetSize().GetWidth() * 0.3))) sizer = wx.BoxSizer(wx.VERTICAL) self.search = wx.SearchCtrl(pnl_books, size=(200, -1), style=wx.TE_PROCESS_ENTER) self.search.ShowSearchButton(True) self.search.ShowCancelButton(True) self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, self.OnSearch, self.search) self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnCancelSearch, self.search) sizer.Add(self.search, 0, wx.EXPAND, 0) self.book_list = KlipListCtrl(pnl_books, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_NO_HEADER | wx.LC_HRULES | wx.LC_SINGLE_SEL) font = self.book_list.GetFont() font.PointSize += 5 # font = font.Bold() self.book_list.SetFont(font) self.book_list.SetForegroundColour(wx.Colour(0x43, 0x43, 0x43)) self.book_list.ClearAll() self.book_list.SetBackgroundColour(wx.Colour(0xf6, 0xf6, 0xf6)) self.bl_width = self.book_list.GetSize().GetWidth() * 0.5 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnBookSelected, self.book_list) self._total_books = wx.StaticText(pnl_books, label="BOOKS (%d)" % 0, pos=(25, 25)) sizer.Add(self._total_books, 0, wx.LEFT, 0) sizer.Add(self.book_list, 1, wx.EXPAND | wx.ALL, 0) pnl_books.SetSizer(sizer) # init right panel, show clippings. sizer = wx.BoxSizer(wx.VERTICAL) self.book_title = wx.StaticText(pnl_clips, label='') font = self.book_title.GetFont() font.PointSize += 10 font.Bold() self.book_title.SetFont(font) self.book_title.SetForegroundColour(wx.Colour(0x25, 0x91, 0xff)) sizer.Add(self.book_title, 0, wx.EXPAND, 10) self.book_info = wx.StaticText(pnl_clips, label='') font = self.book_info.GetFont() font.PointSize += 5 font.Bold() self.book_info.SetFont(font) # self.book_info.SetForegroundColour(wx.Colour(0x25, 0x91, 0xff)) sizer.Add(self.book_info, 0, wx.EXPAND, 10) self.clip_list = KlipListCtrl(pnl_clips, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_NO_HEADER | wx.LC_HRULES | wx.LC_SINGLE_SEL) self.clip_list.InsertColumn(0, "Clip") # books = self.fillBooks() sizer.Add(self.clip_list, 1, wx.EXPAND | wx.ALL, 0) self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.showClipDetail, self.clip_list) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnClipSelected, self.clip_list) pnl_clips.SetSizer(sizer) font = self.clip_list.GetFont() font.PointSize += 3 self.clip_list.SetFont(font) width = self.clip_list.GetSize().GetWidth() * 0.5 PDEBUG('Update Column Width: %d' % width) self.clip_list.SetColumnWidth(0, width) self.clip_list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClickClips) self.refreshContents() # create a menu bar self.makeMenuBar()
def startServer(model_): """ """ global model model = model_ HOST, PORT = "localhost", 9999 with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 0, 0)) s.bind((HOST, PORT)) s.listen(1) PDEBUG('Listening on %s:%d', HOST, PORT) while True: conn, addr = s.accept() with conn: PDEBUG('Connected by: %s', addr) data = '' while True: if len(data) < 4: # header length # Read Length, 4 bytes data = conn.recv(1024) if not data: PDEBUG('No data, break...') break PDEBUG('LEN: %d' % (len(data))) PDEBUG('DATA: %s', data) # Now parse header... total = len(data) l = struct.unpack_from('!I', data, 0)[0] total -= 4 PDEBUG('L: %d, total: %d' % (l, total)) body = data[4:] PDEBUG('BODY: %s', body) if total < l: expected = l - total while expected > 0: PDEBUG('1: Needs to read %d bytes...', expected) data = conn.recv(1024) PDEBUG('Got data: %s, len: %d', data, len(data)) to_copy = min(len(data), expected) expected -= to_copy body += data[:to_copy] PDEBUG('2: Needs to read %d bytes...', expected) if expected == 0: data = data[to_copy:] else: data = data[4 + l:] # Now we should have a complete json string... PDEBUG('BODY: %s', body) j = json.loads(body) PDEBUG('GOT: %s' % (j)) ret = processQuery(j) PDEBUG('RET: %s' % (ret)) ret_txt = ret.encode() rsp = struct.pack('!I', len(ret_txt)) rsp += ret_txt conn.sendall(rsp)
def OnCancelSearch(self, evt): PDEBUG('ENTER') self.showClipsOfBook()