OSDN Git Service

Evernoteサーバからのサムネイル取得を別スレッドで順次実行するようにした
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / gui / RensoNoteList.java
1 /*
2  * This file is part of NeighborNote
3  * Copyright 2013 Yuki Takahashi
4  * 
5  * This file may be licensed under the terms of of the
6  * GNU General Public License Version 2 (the ``GPL'').
7  *
8  * Software distributed under the License is distributed
9  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
10  * express or implied. See the GPL for the specific language
11  * governing rights and limitations.
12  *
13  * You should have received a copy of the GPL along with this
14  * program. If not, go to http://www.gnu.org/licenses/gpl.html
15  * or write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18 */
19
20 package cx.fbn.nevernote.gui;
21
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 import com.evernote.edam.type.Note;
30 import com.trolltech.qt.QThread;
31 import com.trolltech.qt.core.QFile;
32 import com.trolltech.qt.core.QSize;
33 import com.trolltech.qt.core.Qt.MouseButton;
34 import com.trolltech.qt.gui.QAction;
35 import com.trolltech.qt.gui.QApplication;
36 import com.trolltech.qt.gui.QContextMenuEvent;
37 import com.trolltech.qt.gui.QListWidget;
38 import com.trolltech.qt.gui.QListWidgetItem;
39 import com.trolltech.qt.gui.QMenu;
40
41 import cx.fbn.nevernote.Global;
42 import cx.fbn.nevernote.NeverNote;
43 import cx.fbn.nevernote.sql.DatabaseConnection;
44 import cx.fbn.nevernote.threads.ENRelatedNotesRunner;
45 import cx.fbn.nevernote.threads.ENThumbnailRunner;
46 import cx.fbn.nevernote.threads.SyncRunner;
47 import cx.fbn.nevernote.utilities.ApplicationLogger;
48 import cx.fbn.nevernote.utilities.Pair;
49
50 public class RensoNoteList extends QListWidget {
51         private final DatabaseConnection conn;
52         private final ApplicationLogger logger;
53         private final HashMap<QListWidgetItem, String> rensoNoteListItems;
54         private final HashMap<String, RensoNoteListItem> rensoNoteListTrueItems;
55         private String rensoNotePressedItemGuid;
56         private final QAction openNewTabAction;
57         private final QAction starAction;
58         private final QAction unstarAction;
59         private final QAction excludeNoteAction;
60         private final NeverNote parent;
61         private final QMenu menu;
62         private HashMap<String, Integer> mergedHistory;                         // マージされた操作履歴
63         private final SyncRunner syncRunner;
64         private final ENRelatedNotesRunner enRelatedNotesRunner;
65         private final QThread enRelatedNotesThread;
66         private final HashMap<String, List<String>> enRelatedNotesCache;        // Evernote関連ノートのキャッシュ<guid, 関連ノートリスト>
67         private final ENThumbnailRunner enThumbnailRunner;
68         private final QThread enThumbnailThread;
69         private String guid;
70         private int allPointSum;
71
72         public RensoNoteList(DatabaseConnection c, NeverNote p, SyncRunner syncRunner, ApplicationLogger logger) {
73                 this.logger = logger;
74                 this.logger.log(this.logger.HIGH, "Setting up rensoNoteList");
75                 allPointSum = 0;
76
77                 this.conn = c;
78                 this.parent = p;
79                 this.syncRunner = syncRunner;
80                 
81                 this.guid = new String();
82                 mergedHistory = new HashMap<String, Integer>();
83                 enRelatedNotesCache = new HashMap<String, List<String>>();
84                 this.enRelatedNotesRunner = new ENRelatedNotesRunner(this.syncRunner, this.logger);
85                 this.enRelatedNotesRunner.enRelatedNotesSignal.getENRelatedNotesFinished.connect(this, "enRelatedNotesComplete()");
86                 this.enRelatedNotesRunner.limitSignal.rateLimitReached.connect(parent, "informRateLimit(Integer)");
87                 this.enRelatedNotesThread = new QThread(enRelatedNotesRunner, "ENRelatedNotes Thread");
88                 this.getEnRelatedNotesThread().start();
89                 
90                 this.enThumbnailRunner = new ENThumbnailRunner(this.logger);
91                 this.enThumbnailRunner.enThumbnailSignal.getENThumbnailFinished.connect(this, "enThumbnailComplete(String)");
92                 this.enThumbnailRunner.limitSignal.rateLimitReached.connect(parent, "informRateLimit(Integer)");
93                 this.enThumbnailThread = new QThread(enThumbnailRunner, "ENThumbnail Thread");
94                 this.enThumbnailThread.start();
95                 
96                 rensoNoteListItems = new HashMap<QListWidgetItem, String>();
97                 rensoNoteListTrueItems = new HashMap<String, RensoNoteListItem>();
98                 
99                 this.itemPressed.connect(this, "rensoNoteItemPressed(QListWidgetItem)");
100                 
101                 // コンテキストメニュー作成
102                 menu = new QMenu(this);
103                 // 新しいタブで開くアクション生成
104                 openNewTabAction = new QAction(tr("Open in New Tab"), this);
105                 openNewTabAction.setToolTip(tr("Open this note in new tab"));
106                 openNewTabAction.triggered.connect(parent, "openNewTabFromRNL()");
107                 // スターをつけるアクション生成
108                 starAction = new QAction(tr("Add Star"), this);
109                 starAction.setToolTip(tr("Add Star to this item"));
110                 starAction.triggered.connect(parent, "starNote()");
111                 // スターを外すアクション生成
112                 unstarAction = new QAction(tr("Remove Star"), this);
113                 unstarAction.setToolTip(tr("Remove Star from this item"));
114                 unstarAction.triggered.connect(parent, "unstarNote()");
115                 // このノートを除外するアクション生成
116                 excludeNoteAction = new QAction(tr("Exclude"), this);
117                 excludeNoteAction.setToolTip(tr("Exclude this note from RensoNoteList"));
118                 excludeNoteAction.triggered.connect(parent, "excludeNote()");
119                 // コンテキストメニューに登録
120                 menu.addAction(openNewTabAction);
121                 menu.addAction(excludeNoteAction);
122                 menu.aboutToHide.connect(this, "contextMenuHidden()");
123                 
124                 this.logger.log(this.logger.HIGH, "rensoNoteList setup complete");
125         }
126         
127         // オーバーロード
128         // 現在開いているノートの連想ノートリストをリフレッシュ
129         public void refreshRensoNoteList() {
130                 refreshRensoNoteList(guid);
131         }
132
133         // 連想ノートリストをリフレッシュ
134         public void refreshRensoNoteList(String guid) {
135                 logger.log(logger.HIGH, "Entering RensoNoteList.refreshRensoNoteList guid = " + guid);
136
137                 this.clear();
138                 rensoNoteListItems.clear();
139                 rensoNoteListTrueItems.clear();
140                 mergedHistory = new HashMap<String, Integer>();
141
142                 if (!this.isEnabled()) {
143                         return;
144                 }
145                 if (guid == null || guid.equals("")) {
146                         return;
147                 }
148                 
149                 this.guid = guid;
150                 // すでにEvernote関連ノートがキャッシュされているか確認
151                 boolean isCached;
152                 isCached = enRelatedNotesCache.containsKey(guid);
153                 if (!isCached) {        // キャッシュ無し
154                         // Evernoteの関連ノートを別スレッドで取得させる
155                         enRelatedNotesRunner.addGuid(guid);
156                 } else {                        // キャッシュ有り
157                         List<String> relatedNoteGuids = enRelatedNotesCache.get(guid);
158                         addENRelatedNotes(relatedNoteGuids);
159                 }
160                 
161                 calculateHistory(guid);
162                 repaintRensoNoteList(false);
163
164                 logger.log(logger.HIGH, "Leaving RensoNoteList.refreshRensoNoteList");
165         }
166         
167         // 操作履歴をデータベースから取得してノートごとの関連度を算出、その後mergedHistoryに追加
168         private void calculateHistory(String guid) {
169                 logger.log(logger.EXTREME, "Entering RensoNoteList.calculateHistory guid = " + guid);
170                 
171                 // browseHistory<guid, 回数(ポイント)>
172                 HashMap<String, Integer> browseHistory = conn.getHistoryTable().getBehaviorHistory("browse", guid);
173                 addWeight(browseHistory, Global.getBrowseWeight());
174                 mergedHistory = mergeHistory(filterHistory(browseHistory), mergedHistory);
175                 
176                 // copy&pasteHistory<guid, 回数(ポイント)>
177                 HashMap<String, Integer> copyAndPasteHistory = conn.getHistoryTable().getBehaviorHistory("copy & paste", guid);
178                 addWeight(copyAndPasteHistory, Global.getCopyPasteWeight());
179                 mergedHistory = mergeHistory(filterHistory(copyAndPasteHistory), mergedHistory);
180                 
181                 // addNewNoteHistory<guid, 回数(ポイント)>
182                 HashMap<String, Integer> addNewNoteHistory = conn.getHistoryTable().getBehaviorHistory("addNewNote", guid);
183                 addWeight(addNewNoteHistory, Global.getAddNewNoteWeight());
184                 mergedHistory = mergeHistory(filterHistory(addNewNoteHistory), mergedHistory);
185                 
186                 // rensoItemClickHistory<guid, 回数(ポイント)>
187                 HashMap<String, Integer> rensoItemClickHistory = conn.getHistoryTable().getBehaviorHistory("rensoItemClick", guid);
188                 addWeight(rensoItemClickHistory, Global.getRensoItemClickWeight());
189                 mergedHistory = mergeHistory(filterHistory(rensoItemClickHistory), mergedHistory);
190                 
191                 // sameTagHistory<guid, 回数(ポイント)>
192                 HashMap<String, Integer> sameTagHistory = conn.getHistoryTable().getBehaviorHistory("sameTag", guid);
193                 addWeight(sameTagHistory, Global.getSameTagWeight());
194                 mergedHistory = mergeHistory(filterHistory(sameTagHistory), mergedHistory);
195                 
196                 // sameNotebookNoteHistory<guid, 回数(ポイント)>
197                 HashMap<String, Integer> sameNotebookHistory = conn.getHistoryTable().getBehaviorHistory("sameNotebook", guid);
198                 addWeight(sameNotebookHistory, Global.getSameNotebookWeight());
199                 mergedHistory = mergeHistory(filterHistory(sameNotebookHistory), mergedHistory);
200                 logger.log(logger.EXTREME, "Leaving RensoNoteList.calculateHistory");
201         }
202         
203         // 操作回数に重み付けする
204         private void addWeight(HashMap<String, Integer> history, int weight){
205                 logger.log(logger.EXTREME, "Entering RensoNoteList.addWeight");
206                 
207                 Set<String> keySet = history.keySet();
208                 Iterator<String> hist_iterator = keySet.iterator();
209                 while(hist_iterator.hasNext()){
210                         String key = hist_iterator.next();
211                         history.put(key, history.get(key) * weight);
212                 }
213                 
214                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addWeight");
215         }
216         
217         // 連想ノートリストを再描画
218         private void repaintRensoNoteList(boolean needClear) {
219                 logger.log(logger.EXTREME, "Entering RensoNoteList.repaintRensoNoteList");
220                 
221                 if (needClear) {
222                         this.clear();
223                         rensoNoteListItems.clear();
224                         rensoNoteListTrueItems.clear();
225                 }
226                 
227                 if (!this.isEnabled()) {
228                         return;
229                 }
230                 
231                 // すべての関連ポイントの合計を取得(関連度のパーセント算出に利用)
232                 allPointSum = 0;
233                 for (int p : mergedHistory.values()) {
234                         allPointSum += p;
235                 }
236                 
237                 addRensoNoteList(mergedHistory);
238                 
239                 logger.log(logger.EXTREME, "Leaving RensoNoteList.repaintRensoNoteList");
240         }
241         
242         // 引数1と引数2をマージしたハッシュマップを返す
243         private HashMap<String, Integer> mergeHistory(HashMap<String, Integer> History1, HashMap<String, Integer> History2){
244                 logger.log(logger.EXTREME, "Entering RensoNoteList.mergeHistory");
245                 
246                 HashMap<String, Integer> mergedHistory = new HashMap<String, Integer>();
247                 
248                 mergedHistory.putAll(History1);
249                 
250                 Set<String> keySet = History2.keySet();
251                 Iterator<String> hist2_iterator = keySet.iterator();
252                 while(hist2_iterator.hasNext()){
253                         String key = hist2_iterator.next();
254                         if(mergedHistory.containsKey(key)){
255                                 mergedHistory.put(key, mergedHistory.get(key) + History2.get(key));
256                         }else {
257                                 mergedHistory.put(key, History2.get(key));
258                         }
259                 }
260                 
261                 logger.log(logger.EXTREME, "Leaving RensoNoteList.mergeHistory");
262                 return mergedHistory;
263         }
264         
265         // 連想ノートリストにハッシュマップのデータを追加
266         private void addRensoNoteList(HashMap<String, Integer> History){
267                 logger.log(logger.EXTREME, "Entering RensoNoteList.addRensoNoteList");
268                 
269                 enThumbnailRunner.setUser(Global.getUserInformation());
270                 enThumbnailRunner.setServerUrl(Global.getServer());
271                 
272                 String currentNoteGuid = new String(parent.getCurrentNoteGuid());
273                 
274                 // スター付きノートとスター無しノートを分ける
275                 HashMap<String, Integer> staredNotes = new HashMap<String, Integer>();  // スター付きノートのマップ
276                 HashMap<String, Integer> normalNotes = new HashMap<String, Integer>();  // スター無しノートのマップ
277                 for (String nextGuid: History.keySet()) {
278                         int relationPoint = History.get(nextGuid);
279                         boolean isStared = conn.getStaredTable().existNote(currentNoteGuid, nextGuid);
280                         if (isStared) {
281                                 staredNotes.put(nextGuid, relationPoint);
282                         } else {
283                                 normalNotes.put(nextGuid, relationPoint);
284                         }
285                 }
286                 
287                 // 連想ノートリストアイテムの最大表示数まで繰り返す
288                 for (int i = 0; i < Global.getRensoListItemMaximum(); i++) {
289                         // スター付きノートがあれば先に処理する
290                         HashMap<String, Integer> tmpMap = new HashMap<String, Integer>();
291                         if (!staredNotes.isEmpty()) {
292                                 tmpMap = staredNotes;
293                         }else if (!normalNotes.isEmpty()) {
294                                 tmpMap = normalNotes;
295                         }
296                         
297                         // 操作回数が多い順に取り出して連想ノートリストに追加する
298                         if (!tmpMap.isEmpty()) {
299                                 int maxNum = -1;
300                                 String maxGuid = new String();
301                                 
302                                 for (String nextGuid: tmpMap.keySet()) {
303                                         int relationPoint = tmpMap.get(nextGuid);
304                                         
305                                         // 最大ノート探索する
306                                         if (relationPoint > maxNum) {
307                                                 maxNum = relationPoint;
308                                                 maxGuid = nextGuid;
309                                         }
310                                 }
311                                 
312                                 // 次の最大値探索で邪魔なので最大値をHashMapから削除
313                                 tmpMap.remove(maxGuid);
314         
315                                 // 関連度最大のノートがアクティブか確認
316                                 Note maxNote = conn.getNoteTable().getNote(maxGuid, true, false, false, false, true);
317                                 boolean isNoteActive = false;
318                                 if(maxNote != null) {
319                                         isNoteActive = maxNote.isActive();
320                                 }
321                                 
322                                 // 存在していて、かつ関連度0でなければノート情報を取得して連想ノートリストに追加
323                                 if (isNoteActive && maxNum > 0) {
324                                         // Evernoteサムネイルが取得済みか確認。未取得ならサムネイル取得スレッドにキュー
325                                         // TODO 今はとりあえずresディレクトリを確認。あとでデータベースにカラムを追加する
326                                         if (Global.isConnected) {
327                                                 String thumbnailName = Global.getFileManager().getResDirPath("enThumbnail-" + maxGuid + ".png");
328                                                 QFile thumbnail = new QFile(thumbnailName);
329                                                 if (!thumbnail.exists()) {
330                                                         enThumbnailRunner.addGuid(maxGuid);
331                                                 }
332                                         }
333                                         
334                                         // スター付きか確認
335                                         boolean isStared;
336                                         isStared = conn.getStaredTable().existNote(currentNoteGuid, maxGuid);
337                                         
338                                         QListWidgetItem item = new QListWidgetItem();
339                                         RensoNoteListItem myItem = new RensoNoteListItem(maxNote, maxNum, isStared, allPointSum, conn, this);
340                                         item.setSizeHint(new QSize(0, 90));
341                                         this.addItem(item);
342                                         this.setItemWidget(item, myItem);
343                                         rensoNoteListItems.put(item, maxGuid);
344                                         rensoNoteListTrueItems.put(maxGuid, myItem);
345                                 } else {
346                                         break;
347                                 }
348                         }
349                 }
350                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addRensoNoteList");
351         }
352
353         // リストのアイテムから対象ノートのguidを取得
354         public String getNoteGuid(QListWidgetItem item) {
355                 return rensoNoteListItems.get(item);
356         }
357         
358         // 関連ノートリストの右クリックメニュー
359         @Override
360         public void contextMenuEvent(QContextMenuEvent event){
361                 logger.log(logger.EXTREME, "Entering RensoNoteList.contextMenuEvent");
362                 
363                 if (rensoNotePressedItemGuid == null || rensoNotePressedItemGuid.equals("")) {
364                         return;
365                 }
366                 
367                 // STAR, UNSTARがあれば、一度消す
368                 List<QAction> menuActions = new ArrayList<QAction>(menu.actions());
369                 if (menuActions.contains(starAction)) {
370                         menu.removeAction(starAction);
371                 }
372                 if (menuActions.contains(unstarAction)) {
373                         menu.removeAction(unstarAction);
374                 }
375                 
376                 // 対象アイテムがスター付きなら「UNSTAR」、スター無しなら「STAR」を追加
377                 String currentNoteGuid = parent.getCurrentNoteGuid();
378                 boolean isExist = conn.getStaredTable().existNote(currentNoteGuid, rensoNotePressedItemGuid);
379                 if (isExist) {
380                         menu.insertAction(excludeNoteAction, unstarAction);
381                 } else {
382                         menu.insertAction(excludeNoteAction, starAction);
383                 }
384                 
385                 // コンテキストメニューを表示
386                 menu.exec(event.globalPos());
387                 
388                 rensoNotePressedItemGuid = null;
389                 
390                 logger.log(logger.EXTREME, "Leaving RensoNoteList.contextMenuEvent");
391         }
392         
393         // コンテキストメニューが表示されているかどうか
394         public boolean isContextMenuVisible() {
395                 return menu.isVisible();
396         }
397         
398         // コンテキストメニューが閉じられた時
399         @SuppressWarnings("unused")
400         private void contextMenuHidden() {
401                 for (RensoNoteListItem item : rensoNoteListTrueItems.values()) {
402                         item.setDefaultBackground();
403                 }
404         }
405         
406         // ユーザが連想ノートリストのアイテムを選択した時の処理
407         @SuppressWarnings("unused")
408         private void rensoNoteItemPressed(QListWidgetItem current) {
409                 logger.log(logger.HIGH, "Entering RensoNoteList.rensoNoteItemPressed");
410                 
411                 rensoNotePressedItemGuid = null;
412                 // 右クリックだったときの処理
413                 if (QApplication.mouseButtons().isSet(MouseButton.RightButton)) {
414                         rensoNotePressedItemGuid = getNoteGuid(current);
415                 }
416                 
417                 logger.log(logger.HIGH, "Leaving RensoNoteList.rensoNoteItemPressed");
418         }
419         
420         // Evernoteの関連ノートの取得が完了
421         @SuppressWarnings("unused")
422         private void enRelatedNotesComplete() {
423                 logger.log(logger.HIGH, "Entering RensoNoteList.enRelatedNotesComplete");
424                 
425                 Pair<String, List<String>> enRelatedNoteGuidPair = enRelatedNotesRunner.getENRelatedNoteGuids();        // <元ノートguid, 関連ノートguidリスト>
426                 
427                 if (enRelatedNoteGuidPair == null) {
428                         return;
429                 }
430                 
431                 String sourceGuid = enRelatedNoteGuidPair.getFirst();
432                 List<String> enRelatedNoteGuids = enRelatedNoteGuidPair.getSecond();
433                 
434                 
435                 if (sourceGuid != null && !sourceGuid.equals("") && enRelatedNoteGuids != null) {       // Evernote関連ノートがnullでなければ
436                         // まずキャッシュに追加
437                         enRelatedNotesCache.put(sourceGuid, enRelatedNoteGuids);
438                         
439                         if (!enRelatedNoteGuids.isEmpty()) {    // Evernote関連ノートが存在していて
440                                 if (sourceGuid.equals(this.guid)) {     // 取得したデータが今開いているノートの関連ノートなら
441                                         // mergedHistoryにEvernote関連ノートを追加してから再描画
442                                         addENRelatedNotes(enRelatedNoteGuids);
443                                         repaintRensoNoteList(true);
444                                 }
445                         }
446                 }
447                 
448                 logger.log(logger.HIGH, "Leaving RensoNoteList.enRelatedNotesComplete");
449         }
450         
451         // Evernote関連ノートの関連度情報をmergedHistoryに追加
452         private void addENRelatedNotes(List<String> relatedNoteGuids) {
453                 logger.log(logger.EXTREME, "Entering RensoNoteList.addENRelatedNotes");
454                 
455                 // Evernote関連ノート<guid, 関連ポイント>
456                 HashMap<String, Integer> enRelatedNotes = new HashMap<String, Integer>();
457                 
458                 for (String relatedGuid : relatedNoteGuids) {
459                         enRelatedNotes.put(relatedGuid, Global.getENRelatedNotesWeight());
460                 }
461                 
462                 mergedHistory = mergeHistory(filterHistory(enRelatedNotes), mergedHistory);
463                 
464                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addENRelatedNotes");
465         }
466         
467         // Evernoteの関連ノート取得スレッドを終了させる
468         public boolean stopThread() {
469                 logger.log(logger.HIGH, "Entering RensoNoteList.stopThread");
470                 
471                 if (!enRelatedNotesRunner.addStop()) {
472                         logger.log(logger.HIGH, "RensoNoteList.stopThread failed(enRelatedNotesRunner)");
473                         return false;
474                 }
475                 if (!enThumbnailRunner.addStop()) {
476                         logger.log(logger.HIGH, "RensoNoteList.stopThread failed(enThumbnailRunner)");
477                         return false;
478                 }
479                 
480                 logger.log(logger.HIGH, "RensoNoteList.stopThread succeeded");
481                 return true;
482         }
483
484         public QThread getEnRelatedNotesThread() {
485                 return enRelatedNotesThread;
486         }
487         
488         public String getGuid() {
489                 return guid;
490         }
491         
492         // ローカルに存在していて、かつアクティブなノートだけを返す
493         private HashMap<String, Integer> filterHistory(HashMap<String, Integer> sourceHistory) {
494                 HashMap<String, Integer> dstHistory = new HashMap<String, Integer>();
495                 
496                 for (String guid : sourceHistory.keySet()) {
497                         if (conn.getNoteTable().exists(guid)) {
498                                 if (conn.getNoteTable().getNote(guid, false, false, false, false, false).isActive()) {
499                                         dstHistory.put(guid, sourceHistory.get(guid));
500                                 }
501                         }
502                 }
503                 
504                 return dstHistory;
505         }
506         
507         /**
508          * Evernoteサムネイルの取得が完了
509          * 
510          * @param guid 現在開いているノートのguid
511          */
512         @SuppressWarnings("unused")
513         private void enThumbnailComplete(String guid) {
514                 logger.log(logger.HIGH, "Entering RensoNoteList.enThumbnailComplete");
515                 
516                 for (Map.Entry<String, RensoNoteListItem> e : rensoNoteListTrueItems.entrySet()) {
517                         // サムネイル取得が完了したノートが現在の連想ノートリストに表示されていたら再描画
518                         if (guid.equals(e.getKey())) {
519                                 e.getValue().repaint();
520                         }
521                 }
522                 
523                 logger.log(logger.HIGH, "Leaving RensoNoteList.enThumbnailComplete");
524         }
525 }