kindle manager

This commit is contained in:
gavin
2020-06-06 08:43:34 +08:00
parent c0bfa52fc3
commit 722757dadd
8 changed files with 498 additions and 358 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -75,6 +75,7 @@ b['1']['2'] = {'3':1} # OK
# feature plan # feature plan
## 20200528 ## 20200528
- first abstract from kindle hard / local directory for different OS - first abstract from kindle hard / local directory for different OS **done**
- add GUI use QT **done** - add GUI use QT **done**
- new thread to check kindle connection status

2
cui
View File

@@ -1,2 +1,2 @@
pyuic mainwindow.ui -o mainwindow.py pyuic mainwindow.ui -o mainwindow.py --no-protection
pyside2-rcc -binary kmanapp.qrc -o kmanapp_rc.py pyside2-rcc -binary kmanapp.qrc -o kmanapp_rc.py

128
kman.py
View File

@@ -8,7 +8,9 @@
######################################################### #########################################################
import re import re
import os
import json import json
import time
import logging import logging
import platform import platform
from collections import defaultdict from collections import defaultdict
@@ -107,7 +109,28 @@ r'''
(\d{1,2}:\d{1,2}:\d{1,2}) #group6 - time (\d{1,2}:\d{1,2}:\d{1,2}) #group6 - time
''', flags=re.X ) ''', flags=re.X )
def parse_section(s,i): class kMan:
def __init__(self, parent=None):
self.hlnum = 0
self.ntnum = 0
self.refleshtime = '2020/10/10 10:00:00'
self.status = self.status_info()
def status_info(self):
s1 = u'Hightlight: {} Note: {} RefleshTime: {}'. \
format(self.hlnum,self.ntnum,self.refleshtime)
kp = self.get_kindle_path()
if not kp:
s2 = u'Disconnected'
else:
with open(kp+'/system/version.txt' , 'r', encoding='utf8', errors='ignore') as f:
s2 = u'Connected ({}) version {}'.format(kp,f.read().strip())
return [s1,s2]
def parse_section(self,s,i):
"""parse section """parse section
Args: Args:
@@ -182,7 +205,7 @@ def parse_section(s,i):
return section return section
def format_time(ds): def format_time(self,ds):
""" format date """ format date
Args: Args:
ds: 2020年1月13日 星期一 上午 8:11:05 ds: 2020年1月13日 星期一 上午 8:11:05
@@ -197,7 +220,7 @@ def format_time(ds):
return ymd+tm return ymd+tm
def format_data(bks, ft='MD'): def format_data(self,bks, ft='MD'):
""" format data for MD & CSV """ format data for MD & CSV
Args: Args:
@@ -221,11 +244,12 @@ def format_data(bks, ft='MD'):
for ks, vs in vb.items(): for ks, vs in vb.items():
if ks in ['author', 'lines']: continue if ks in ['author', 'lines']: continue
secs.append(DELIMITER.join([vs['type'],kb,author, \ secs.append(DELIMITER.join([vs['type'],kb,author, \
format_time(' '.join([vs['day'],vs['week'],vs['meridiem'],vs['time']])),vs['content']])) self.format_time(' '.join([vs['day'],vs['week'],\
vs['meridiem'],vs['time']])),vs['content']]))
return hd+secs return hd+secs
def format_out(bks, fnpref, ft='MD'): def format_out(self,bks, fnpref, ft='MD'):
"""format output and write to file """format output and write to file
markdown format: markdown format:
TYPE | bookname | author | marktime | content TYPE | bookname | author | marktime | content
@@ -252,13 +276,13 @@ def format_out(bks, fnpref, ft='MD'):
if ft=='JSON': if ft=='JSON':
fw.write(json.dumps(bks, indent=4, sort_keys=True, ensure_ascii=False)) fw.write(json.dumps(bks, indent=4, sort_keys=True, ensure_ascii=False))
elif ft in ['MD','CSV']: elif ft in ['MD','CSV']:
for s in format_data(bks, ft): for s in self.format_data(bks, ft):
fw.write(s) fw.write(s)
fw.write('\n') fw.write('\n')
else: else:
fw.write(json.dumps(bks)) # only for load back fw.write(json.dumps(bks)) # only for load back
def drop_duplicate(bks): def drop_duplicate(self,bks):
""" drop duplicated section """ drop duplicated section
If I mark second time in same place, kindle will create two note, If I mark second time in same place, kindle will create two note,
@@ -290,7 +314,7 @@ def drop_duplicate(bks):
return bks return bks
def add_note_to_highlight(bks): def add_note_to_highlight(self,bks):
""" append note content to corresponding highlight """ append note content to corresponding highlight
and remove NT sections and remove NT sections
@@ -312,7 +336,7 @@ def add_note_to_highlight(bks):
return bks return bks
def search_clip(bks, s, t='ALL', p='ALL'): def search_clip(self,bks, s, t='ALL', p='ALL'):
"""search clip, searching scope may be title/author/content """search clip, searching scope may be title/author/content
Args: Args:
input: bks: books dict input: bks: books dict
@@ -348,11 +372,11 @@ def search_clip(bks, s, t='ALL', p='ALL'):
return [nu,nbks] return [nu,nbks]
# to be implement # to be implement
def statistic(bks): def statistic(self,bks):
pass pass
def dict2json(d): def dict2json(self,d):
"""convert dict to json """convert dict to json
Args: d is the dict Args: d is the dict
Return: json string Return: json string
@@ -360,7 +384,7 @@ def dict2json(d):
jstr = json.dumps(d) jstr = json.dumps(d)
return jstr return jstr
def json2dict(jf): def json2dict(self,jf):
"""convert dict to json """convert dict to json
Args: jf is the file saved json string Args: jf is the file saved json string
Return: dict Return: dict
@@ -370,12 +394,48 @@ def json2dict(jf):
d=json.load(f) d=json.load(f)
return d return d
def import_clips(): def get_kindle_path(self):
# 4 lines for each section seperated with '=======' """check and return kindle device path
# so read 4 lines before '=======' Args:
Return:
if kindle connected, return path string of kindle device
else return false
"""
cmd = "wmic logicaldisk get name,volumename" if os.name=='nt'\
else ("ls /Volumes/Kindle" if os.name=='posix' else '')
# not test for windows & linux
with os.popen(cmd) as s:
r = s.read()
if os.name == 'nt': # windows
for d in r.split('\n'):
if 'Kindle' in d: return d.split('\s+')[0]
elif os.name == 'posix': # mac os
if r: return('/Volumes/Kindle')
else:
pass
return False
def import_clips(self, tp='local'):
"""import clips from local file or kindle
4 lines for each section seperated with '======='
so read 4 lines before '======='
Args: tp: 'local' local clipping file
'kindle' kindle clipping file
Return: 0 - want to import kindle but kindle is not connected
books dict
"""
if tp=='kindle':
kp = get_kindle_path()
if not kp: return 0
else: path = kp
else:
path = CLIPPATH
# loop to fill books dict # loop to fill books dict
with open(CLIPPATH, 'r', encoding='utf8', errors='ignore') as f: with open(path, 'r', encoding='utf8', errors='ignore') as f:
bks = defaultdict(dict) bks = defaultdict(dict)
secd = defaultdict(dict) secd = defaultdict(dict)
sidx = 0 sidx = 0
@@ -399,7 +459,7 @@ def import_clips():
sidx += 1 sidx += 1
# parsing section & fill data structure # parsing section & fill data structure
secd = parse_section(sec,sidx) secd = self.parse_section(sec,sidx)
if secd: if secd:
bn = secd['bookname'] bn = secd['bookname']
@@ -414,40 +474,44 @@ def import_clips():
if tpy=='NT' and bks[bn][str(sidx-1)]['type']=='HL': if tpy=='NT' and bks[bn][str(sidx-1)]['type']=='HL':
bks[bn][str(sidx-1)]['content'] += str(NTPREF+sec[2]) bks[bn][str(sidx-1)]['content'] += str(NTPREF+sec[2])
""" """
if tpy=='HL': self.hlnum += 1
elif tpy=='NT': self.ntnum += 1
else: # BM or not correct format section else: # BM or not correct format section
sidx -= 1 sidx -= 1
# initial section for next section loop # initial section for next section loop
sec = [] sec = []
return bks
self.refleshtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
return bks
if __name__=='__main__': if __name__=='__main__':
#books = defaultdict(dict) #books = defaultdict(dict)
books = import_clips() km = kMan()
books = km.import_clips('local')
# remove duplication # remove duplication
drop_duplicate(books) km.drop_duplicate(books)
# test search note function # test search note function
searchnote = search_clip(books, '三大都市圈', 'ALL', 'CONTENT') searchnote = km.search_clip(books, '三大都市圈', 'ALL', 'CONTENT')
if searchnote[0] > 0: format_out(searchnote[1], 'searchcontent', ft='MD') if searchnote[0] > 0: km.format_out(searchnote[1], 'searchcontent', ft='MD')
searchnote = search_clip(books, '经济', 'ALL', 'TITLE') searchnote = km.search_clip(books, '经济', 'ALL', 'TITLE')
if searchnote[0] > 0: format_out(searchnote[1], 'searchtitle', ft='MD') if searchnote[0] > 0: km.format_out(searchnote[1], 'searchtitle', ft='MD')
searchnote = search_clip(books, '巴曙松', 'ALL', 'AUTHOR') searchnote = km.search_clip(books, '巴曙松', 'ALL', 'AUTHOR')
if searchnote[0] > 0: format_out(searchnote[1], 'searchauthor', ft='MD') if searchnote[0] > 0: km.format_out(searchnote[1], 'searchauthor', ft='MD')
# add note content to hightlight, then delete note # add note content to hightlight, then delete note
add_note_to_highlight(books) km.add_note_to_highlight(books)
# test dict json convert # test dict json convert
with open('./xx', 'w', encoding='utf8', errors='ignore') as fw: with open('./xx', 'w', encoding='utf8', errors='ignore') as fw:
fw.write(dict2json(books)) fw.write(km.dict2json(books))
if json2dict('./xx')==books: print( 'test OK') if km.json2dict('./xx')==books: print( 'test OK')
format_out(books, OUTPREF, ft='MD') km.format_out(books, OUTPREF, ft='MD')
# print data with json format # print data with json format
logger.debug(json.dumps(books, indent=4, sort_keys=True, ensure_ascii=False)) logger.debug(json.dumps(books, indent=4, sort_keys=True, ensure_ascii=False))

View File

@@ -1,5 +1,10 @@
import sys import sys
from time import sleep
from threading import Thread
import _thread
import threading
from PySide2.QtWidgets import QApplication from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QMainWindow from PySide2.QtWidgets import QMainWindow
@@ -16,6 +21,8 @@ from kman import *
# import binary resource file(kmanapp_rc.py) # import binary resource file(kmanapp_rc.py)
import kmanapp_rc import kmanapp_rc
ONLY_TEST = 0
class kmanWindow(QMainWindow): class kmanWindow(QMainWindow):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -26,13 +33,26 @@ class kmanWindow(QMainWindow):
self.stat_str = 'status information' self.stat_str = 'status information'
self.search_str = '' self.search_str = ''
self.local_fn = CLIPPATH
# create ui and initial it # create ui and initial it
ui = Ui_MainWindow() ui = Ui_MainWindow()
ui.setupUi(self) ui.setupUi(self)
self.ui = ui self.ui = ui
self.books = import_clips() self.km = kMan()
self.books = self.km.import_clips('local')
# loop check kindle is connected or not
# to be implement
"""
try:
#_thread.start_new_thread(self.check_kindle_status)
t1 = threading.Thread(target=check_kindle_status)
t1.start()
except:
print ("Error: can not start thread")
"""
# connect action/toolbutton to slot functions # connect action/toolbutton to slot functions
ui.actionimportkindle.triggered.connect(lambda: self.import_kindle(self.books)) ui.actionimportkindle.triggered.connect(lambda: self.import_kindle(self.books))
@@ -54,7 +74,7 @@ class kmanWindow(QMainWindow):
def add_ui_component(self): def add_ui_component(self):
self.ui.searchComboBox.addItems(['ALL','bookname','content','author']) self.ui.searchComboBox.addItems(['ALL','bookname','content','author'])
#inert test data xxxxxxxx if not ONLY_TEST: # XXXXXXXXXXXXX
model = QStandardItemModel() model = QStandardItemModel()
rootItem = model.invisibleRootItem() rootItem = model.invisibleRootItem()
idx = 0 idx = 0
@@ -96,7 +116,6 @@ class kmanWindow(QMainWindow):
self.ui.treeView.setModel(model) self.ui.treeView.setModel(model)
def clicked_items(self): def clicked_items(self):
print( 'call clicked_items()' ) print( 'call clicked_items()' )
@@ -110,15 +129,46 @@ class kmanWindow(QMainWindow):
#print(search_clip(self.books,s,'ALL',p[t])) #print(search_clip(self.books,s,'ALL',p[t]))
print('call search_scope_change()') print('call search_scope_change()')
def check_kindle_status(self):
while True:
self.show_status_info()
sleep(1)
def show_status_info(self):
""" show status information on statusbar
Args:
conn: 1 if kindle is connected else 0
Return:
conn
"""
status = self.km.status_info()
self.ui.statusbar.showMessage(status[0],0)
clabel = QLabel(status[1])
if not self.km.status:
pe = QPalette()
pe.setColor(QPalette.WindowText,Qt.red)
#clabel.setAutoFillBackground(True)
clabel.setPalette(pe)
self.ui.statusbar.addPermanentWidget(clabel, stretch=0)
# define slot functions # define slot functions
def import_kindle(self,bks): def import_kindle(self,bks):
print("call slot importkindle()") status = self.km.status_info()
self.show_status_info()
print(bks) print(bks)
pass pass
def import_local(self): def import_local(self):
print("call slot importlocal()") fn, ft = QFileDialog.getOpenFileName(self,
pass "choose file to import",
'./', # 起始路径
"All Files (*);;Text Files (*.txt)") # 设置文件扩展名过滤,用双分号间隔
self.fn = fn
#print('filename ', fn, 'filetype ', ft)
if fn == "": return False
def config(self): def config(self):
print("call slot config()") print("call slot config()")
@@ -137,6 +187,12 @@ class kmanWindow(QMainWindow):
pass pass
def about(self): def about(self):
self.messagebox('\n'+ \
' kindle management tool \n\n' + \
' v1.0.4\n\n' + \
' Author: chengan\n\n' + \
' douboer@gmail.com')
print("call slot about()") print("call slot about()")
pass pass
@@ -144,8 +200,15 @@ class kmanWindow(QMainWindow):
print("call slot flush()") print("call slot flush()")
pass pass
def messageBox(self, showInfo): # unify messagebox
box = QMessageBox.about(self, 'Kindle Management', showInfo) def messagebox(self, showinfo):
msgBox = QMessageBox()
msgBox.setText(showinfo)
msgBox.setInformativeText("")
msgBox.setIcon(QMessageBox.Information)
msgBox.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok)
msgBox.setBaseSize(QSize(600, 300))
r = msgBox.exec()
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
@@ -157,3 +220,4 @@ if __name__ == "__main__":
kmw.show() kmw.show()
app.exec_() app.exec_()

6
searchtitle.md Normal file
View File

@@ -0,0 +1,6 @@
TYPE|BOOKNAME|AUTHOR|MARKTIME|CONTENT
--|--|--|--|--
HL|薛兆丰经济学讲义 |薛兆丰|2020/1/13 8:11:05|么到底什么叫边际?边际就是“新增”带来的“新增”。 例如,边际成本就是每新增一个单位产品所需要付出的新增成本;边际收入是每多卖一个产品能够带来的新增收入;边际产量是每新增一份投入所带来的新增产量;边际效用是每消耗一个单位的商品所能带来的新增享受。
HL|薛兆丰经济学讲义 |薛兆丰|2020/1/30 10:23:58|一个国家很大,贫富有差距,并非每个学校和家长都能负担得起这样标准的校车。标准太高,就会逼着很多学校,尤其是农村的学校放弃提供校车,家长们就只能使用安全性能更低的交通工具,比如自己骑自行车或雇用黑车等,结果是孩子们享受到的安全保障反而降低了。
NT|薛兆丰经济学讲义 |薛兆丰|2020/1/30 10:26:31|山寨 假货 问题
HL|薛兆丰经济学讲义 |薛兆丰|2020/1/30 10:29:41|为了克服信息不对称,建立互信,人类社会构想出了各种各样有趣的解决方案,从重复交易到第三方背书,从质保、延保,再到收益共享。此外,还有三种非常接近的建立信任的办法:付出沉没成本、给出人质或者给出抵押。

View File

@@ -116,7 +116,6 @@ class TestKman(unittest.TestCase):
self.assertEqual(t_ds, '2020/1/13 20:11:05') self.assertEqual(t_ds, '2020/1/13 20:11:05')
# test function format_data # test function format_data
def test_format_data(self):
def test_format_data(self): def test_format_data(self):
t_books = self.cre_tbooks() t_books = self.cre_tbooks()
t_out = format_data(t_books, ft='MD') t_out = format_data(t_books, ft='MD')
@@ -125,6 +124,11 @@ class TestKman(unittest.TestCase):
self.assertEqual(t_out[2], 'HL|薛兆丰经济学讲义 |薛兆丰|2020/1/13 8:11:05|边际就是“新增”带来的“新增”。\n') self.assertEqual(t_out[2], 'HL|薛兆丰经济学讲义 |薛兆丰|2020/1/13 8:11:05|边际就是“新增”带来的“新增”。\n')
t_out.clear() t_out.clear()
def test_add_note_to_highlight(self):
t_books = self.cre_tbooks()
t_books_remove_nt = add_note_to_highlight(t_books)
for k in t_books_remove_nt.keys():
bn = k
self.assertEqual((t_books_remove_nt[bn]['1']['content']).replace('\n',''),\ self.assertEqual((t_books_remove_nt[bn]['1']['content']).replace('\n',''),\
'边际就是“新增”带来的“新增”。'+NTPREF+ '山寨 假货 问题') '边际就是“新增”带来的“新增”。'+NTPREF+ '山寨 假货 问题')

1
xx Normal file
View File

@@ -0,0 +1 @@
{"\u859b\u5146\u4e30\u7ecf\u6d4e\u5b66\u8bb2\u4e49 ": {"author": "\u859b\u5146\u4e30", "1": {"type": "HL", "position": "1408-1410", "day": "2020\u5e741\u670813\u65e5", "week": "\u661f\u671f\u4e00", "meridiem": "\u4e0a\u5348", "time": "8:11:05", "content": "\u4e48\u5230\u5e95\u4ec0\u4e48\u53eb\u8fb9\u9645\uff1f\u8fb9\u9645\u5c31\u662f\u201c\u65b0\u589e\u201d\u5e26\u6765\u7684\u201c\u65b0\u589e\u201d\u3002 \u4f8b\u5982\uff0c\u8fb9\u9645\u6210\u672c\u5c31\u662f\u6bcf\u65b0\u589e\u4e00\u4e2a\u5355\u4f4d\u4ea7\u54c1\u6240\u9700\u8981\u4ed8\u51fa\u7684\u65b0\u589e\u6210\u672c\uff1b\u8fb9\u9645\u6536\u5165\u662f\u6bcf\u591a\u5356\u4e00\u4e2a\u4ea7\u54c1\u80fd\u591f\u5e26\u6765\u7684\u65b0\u589e\u6536\u5165\uff1b\u8fb9\u9645\u4ea7\u91cf\u662f\u6bcf\u65b0\u589e\u4e00\u4efd\u6295\u5165\u6240\u5e26\u6765\u7684\u65b0\u589e\u4ea7\u91cf\uff1b\u8fb9\u9645\u6548\u7528\u662f\u6bcf\u6d88\u8017\u4e00\u4e2a\u5355\u4f4d\u7684\u5546\u54c1\u6240\u80fd\u5e26\u6765\u7684\u65b0\u589e\u4eab\u53d7\u3002"}, "2": {"type": "HL", "position": "4284-4286", "day": "2020\u5e741\u670830\u65e5", "week": "\u661f\u671f\u56db", "meridiem": "\u4e0a\u5348", "time": "10:23:58", "content": "\u4e00\u4e2a\u56fd\u5bb6\u5f88\u5927\uff0c\u8d2b\u5bcc\u6709\u5dee\u8ddd\uff0c\u5e76\u975e\u6bcf\u4e2a\u5b66\u6821\u548c\u5bb6\u957f\u90fd\u80fd\u8d1f\u62c5\u5f97\u8d77\u8fd9\u6837\u6807\u51c6\u7684\u6821\u8f66\u3002\u6807\u51c6\u592a\u9ad8\uff0c\u5c31\u4f1a\u903c\u7740\u5f88\u591a\u5b66\u6821\uff0c\u5c24\u5176\u662f\u519c\u6751\u7684\u5b66\u6821\u653e\u5f03\u63d0\u4f9b\u6821\u8f66\uff0c\u5bb6\u957f\u4eec\u5c31\u53ea\u80fd\u4f7f\u7528\u5b89\u5168\u6027\u80fd\u66f4\u4f4e\u7684\u4ea4\u901a\u5de5\u5177\uff0c\u6bd4\u5982\u81ea\u5df1\u9a91\u81ea\u884c\u8f66\u6216\u96c7\u7528\u9ed1\u8f66\u7b49\uff0c\u7ed3\u679c\u662f\u5b69\u5b50\u4eec\u4eab\u53d7\u5230\u7684\u5b89\u5168\u4fdd\u969c\u53cd\u800c\u964d\u4f4e\u4e86\u3002--CG\u6ce8:\u5c71\u5be8 \u5047\u8d27 \u95ee\u9898"}, "4": {"type": "HL", "position": "4382-4384", "day": "2020\u5e741\u670830\u65e5", "week": "\u661f\u671f\u56db", "meridiem": "\u4e0a\u5348", "time": "10:29:41", "content": "\u4e3a\u4e86\u514b\u670d\u4fe1\u606f\u4e0d\u5bf9\u79f0\uff0c\u5efa\u7acb\u4e92\u4fe1\uff0c\u4eba\u7c7b\u793e\u4f1a\u6784\u60f3\u51fa\u4e86\u5404\u79cd\u5404\u6837\u6709\u8da3\u7684\u89e3\u51b3\u65b9\u6848\uff0c\u4ece\u91cd\u590d\u4ea4\u6613\u5230\u7b2c\u4e09\u65b9\u80cc\u4e66\uff0c\u4ece\u8d28\u4fdd\u3001\u5ef6\u4fdd\uff0c\u518d\u5230\u6536\u76ca\u5171\u4eab\u3002\u6b64\u5916\uff0c\u8fd8\u6709\u4e09\u79cd\u975e\u5e38\u63a5\u8fd1\u7684\u5efa\u7acb\u4fe1\u4efb\u7684\u529e\u6cd5\uff1a\u4ed8\u51fa\u6c89\u6ca1\u6210\u672c\u3001\u7ed9\u51fa\u4eba\u8d28\u6216\u8005\u7ed9\u51fa\u62b5\u62bc\u3002"}, "lines": 4}}