Show More
@@ -13,11 +13,13 b'' | |||||
13 | from __future__ import print_function |
|
13 | from __future__ import print_function | |
14 |
|
14 | |||
15 | # Stdlib imports |
|
15 | # Stdlib imports | |
|
16 | import atexit | |||
16 | import datetime |
|
17 | import datetime | |
17 | import json |
|
18 | import json | |
18 | import os |
|
19 | import os | |
19 | import re |
|
20 | import re | |
20 | import sqlite3 |
|
21 | import sqlite3 | |
|
22 | import threading | |||
21 |
|
23 | |||
22 | from collections import defaultdict |
|
24 | from collections import defaultdict | |
23 |
|
25 | |||
@@ -76,6 +78,10 b' class HistoryManager(Configurable):' | |||||
76 | db_input_cache = List() |
|
78 | db_input_cache = List() | |
77 | db_output_cache = List() |
|
79 | db_output_cache = List() | |
78 |
|
80 | |||
|
81 | # History saving in separate thread | |||
|
82 | save_thread = Instance('IPython.core.history.HistorySavingThread') | |||
|
83 | save_flag = Instance(threading._Event) | |||
|
84 | ||||
79 | # Private interface |
|
85 | # Private interface | |
80 | # Variables used to store the three last inputs from the user. On each new |
|
86 | # Variables used to store the three last inputs from the user. On each new | |
81 | # history update, we populate the user's namespace with these, shifted as |
|
87 | # history update, we populate the user's namespace with these, shifted as | |
@@ -120,6 +126,10 b' class HistoryManager(Configurable):' | |||||
120 | # The hist_file is probably :memory: or something else. |
|
126 | # The hist_file is probably :memory: or something else. | |
121 | raise |
|
127 | raise | |
122 |
|
128 | |||
|
129 | self.save_flag = threading.Event() | |||
|
130 | self.save_thread = HistorySavingThread(self) | |||
|
131 | self.save_thread.start() | |||
|
132 | ||||
123 | self.new_session() |
|
133 | self.new_session() | |
124 |
|
134 | |||
125 |
|
135 | |||
@@ -376,7 +386,7 b' class HistoryManager(Configurable):' | |||||
376 | self.db_input_cache.append((line_num, source, source_raw)) |
|
386 | self.db_input_cache.append((line_num, source, source_raw)) | |
377 | # Trigger to flush cache and write to DB. |
|
387 | # Trigger to flush cache and write to DB. | |
378 | if len(self.db_input_cache) >= self.db_cache_size: |
|
388 | if len(self.db_input_cache) >= self.db_cache_size: | |
379 |
self. |
|
389 | self.save_flag.set() | |
380 |
|
390 | |||
381 | # update the auto _i variables |
|
391 | # update the auto _i variables | |
382 | self._iii = self._ii |
|
392 | self._iii = self._ii | |
@@ -408,38 +418,40 b' class HistoryManager(Configurable):' | |||||
408 |
|
418 | |||
409 | self.db_output_cache.append((line_num, output)) |
|
419 | self.db_output_cache.append((line_num, output)) | |
410 | if self.db_cache_size <= 1: |
|
420 | if self.db_cache_size <= 1: | |
411 |
self. |
|
421 | self.save_flag.set() | |
412 |
|
|
422 | ||
413 | def _writeout_input_cache(self): |
|
423 | def _writeout_input_cache(self, conn): | |
414 |
with |
|
424 | with conn: | |
415 | for line in self.db_input_cache: |
|
425 | for line in self.db_input_cache: | |
416 |
|
|
426 | conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)", | |
417 | (self.session_number,)+line) |
|
427 | (self.session_number,)+line) | |
418 |
|
428 | |||
419 | def _writeout_output_cache(self): |
|
429 | def _writeout_output_cache(self, conn): | |
420 |
with |
|
430 | with conn: | |
421 | for line in self.db_output_cache: |
|
431 | for line in self.db_output_cache: | |
422 |
|
|
432 | conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", | |
423 | (self.session_number,)+line) |
|
433 | (self.session_number,)+line) | |
424 |
|
434 | |||
425 | def writeout_cache(self): |
|
435 | def writeout_cache(self, conn=None): | |
426 | """Write any entries in the cache to the database.""" |
|
436 | """Write any entries in the cache to the database.""" | |
|
437 | if conn is None: | |||
|
438 | conn = self.db | |||
427 | try: |
|
439 | try: | |
428 | self._writeout_input_cache() |
|
440 | self._writeout_input_cache(conn) | |
429 | except sqlite3.IntegrityError: |
|
441 | except sqlite3.IntegrityError: | |
430 | self.new_session() |
|
442 | self.new_session() | |
431 | print("ERROR! Session/line number was not unique in", |
|
443 | print("ERROR! Session/line number was not unique in", | |
432 | "database. History logging moved to new session", |
|
444 | "database. History logging moved to new session", | |
433 | self.session_number) |
|
445 | self.session_number) | |
434 | try: # Try writing to the new session. If this fails, don't recurse |
|
446 | try: # Try writing to the new session. If this fails, don't recurse | |
435 | self._writeout_input_cache() |
|
447 | self._writeout_input_cache(conn) | |
436 | except sqlite3.IntegrityError: |
|
448 | except sqlite3.IntegrityError: | |
437 | pass |
|
449 | pass | |
438 | finally: |
|
450 | finally: | |
439 | self.db_input_cache = [] |
|
451 | self.db_input_cache = [] | |
440 |
|
452 | |||
441 | try: |
|
453 | try: | |
442 | self._writeout_output_cache() |
|
454 | self._writeout_output_cache(conn) | |
443 | except sqlite3.IntegrityError: |
|
455 | except sqlite3.IntegrityError: | |
444 | print("!! Session/line number for output was not unique", |
|
456 | print("!! Session/line number for output was not unique", | |
445 | "in database. Output will not be stored.") |
|
457 | "in database. Output will not be stored.") | |
@@ -447,6 +459,29 b' class HistoryManager(Configurable):' | |||||
447 | self.db_output_cache = [] |
|
459 | self.db_output_cache = [] | |
448 |
|
460 | |||
449 |
|
461 | |||
|
462 | class HistorySavingThread(threading.Thread): | |||
|
463 | daemon = True | |||
|
464 | stop_now = False | |||
|
465 | def __init__(self, history_manager): | |||
|
466 | super(HistorySavingThread, self).__init__() | |||
|
467 | self.history_manager = history_manager | |||
|
468 | atexit.register(self.stop) | |||
|
469 | ||||
|
470 | def run(self): | |||
|
471 | # We need a separate db connection per thread: | |||
|
472 | self.db = sqlite3.connect(self.history_manager.hist_file) | |||
|
473 | while True: | |||
|
474 | self.history_manager.save_flag.wait() | |||
|
475 | self.history_manager.save_flag.clear() | |||
|
476 | self.history_manager.writeout_cache(self.db) | |||
|
477 | if self.stop_now: | |||
|
478 | return | |||
|
479 | ||||
|
480 | def stop(self): | |||
|
481 | self.stop_now = True | |||
|
482 | self.history_manager.save_flag.set() | |||
|
483 | ||||
|
484 | ||||
450 | # To match, e.g. ~5/8-~2/3 |
|
485 | # To match, e.g. ~5/8-~2/3 | |
451 | range_re = re.compile(r""" |
|
486 | range_re = re.compile(r""" | |
452 | ((?P<startsess>~?\d+)/)? |
|
487 | ((?P<startsess>~?\d+)/)? |
General Comments 0
You need to be logged in to leave comments.
Login now