Remove race condition if user tries to mount folder as device while a normal device is connecting. Minor cleanups (see merge log).

This commit is contained in:
Kovid Goyal 2010-05-17 11:12:44 -06:00
commit 7aee85def8
6 changed files with 67 additions and 35 deletions

View File

@ -54,10 +54,17 @@ class FOLDER_DEVICE(USBMS):
def __init__(self, path):
if not os.path.isdir(path):
raise IOError, 'Path is not a folder'
self._main_prefix = path
if path.endswith(os.sep):
self._main_prefix = path
else:
self._main_prefix = path + os.sep
self.booklist_class = BookList
self.is_connected = True
def reset(self, key='-1', log_packets=False, report_progress=None,
detected_device=None):
pass
def disconnect_from_folder(self):
self._main_prefix = ''
self.is_connected = False

View File

@ -117,7 +117,7 @@ class BookList(_BookList):
def add_book(self, book, replace_metadata):
if book not in self:
self.append(book)
return True
return False # subclasses return True if device metadata has changed
def remove_book(self, book):
self.remove(book)

View File

@ -113,8 +113,7 @@ class Device(DeviceConfig, DevicePlugin):
def _windows_space(cls, prefix):
if not prefix:
return 0, 0
if prefix.endswith(os.sep):
prefix = prefix[:-1]
prefix = prefix[:-1]
win32file = __import__('win32file', globals(), locals(), [], -1)
try:
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \

View File

@ -90,6 +90,7 @@ class USBMS(CLI, Device):
self.count_found_in_bl += 1
else:
item = self.book_from_path(prefix, lpath)
changed = True
if metadata.add_book(item, replace_metadata=False):
changed = True
except: # Probably a filename encoding error
@ -106,12 +107,17 @@ class USBMS(CLI, Device):
if not os.path.exists(ebook_dir): continue
# Get all books in the ebook_dir directory
if self.SUPPORTS_SUB_DIRS:
# build a list of files to check, so we can accurately report progress
flist = []
for path, dirs, files in os.walk(ebook_dir):
for filename in files:
self.report_progress(0.5, _('Getting list of books on device...'))
changed = update_booklist(filename, path, prefix)
if changed:
need_sync = True
if filename != self.METADATA_CACHE:
flist.append({'filename':filename, 'path': path})
for i, f in enumerate(flist):
self.report_progress(i/float(len(flist)), _('Getting list of books on device...'))
changed = update_booklist(f['filename'], f['path'], prefix)
if changed:
need_sync = True
else:
paths = os.listdir(ebook_dir)
for i, filename in enumerate(paths):
@ -123,7 +129,10 @@ class USBMS(CLI, Device):
# if count != len(bl) then there were items in it that we did not
# find on the device. If need_sync is True then there were either items
# on the device that were not in bl or some of the items were changed.
#print "count found in cache: %d, count of files in cache: %d, must_sync_cache: %s" % (self.count_found_in_bl, len(bl), need_sync)
#print "count found in cache: %d, count of files in cache: %d, need_sync: %s, must_sync_cache: %s" % \
# (self.count_found_in_bl, len(bl), need_sync,
# need_sync or self.count_found_in_bl != len(bl))
if self.count_found_in_bl != len(bl) or need_sync:
if oncard == 'cardb':
self.sync_booklists((None, None, metadata))

View File

@ -19,7 +19,7 @@ from calibre.devices.scanner import DeviceScanner
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
pixmap_to_data, warning_dialog, \
question_dialog
from calibre.ebooks.metadata import authors_to_string
from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string
from calibre import preferred_encoding
from calibre.utils.filenames import ascii_filename
from calibre.devices.errors import FreeSpaceError
@ -86,7 +86,9 @@ class DeviceManager(Thread):
self.current_job = None
self.scanner = DeviceScanner()
self.connected_device = None
self.ejected_devices = set([])
self.ejected_devices = set([])
self.connected_device_is_folder = False
self.folder_connection_path = None
def report_progress(self, *args):
pass
@ -99,7 +101,7 @@ class DeviceManager(Thread):
def device(self):
return self.connected_device
def do_connect(self, connected_devices):
def do_connect(self, connected_devices, is_folder_device):
for dev, detected_device in connected_devices:
dev.reset(detected_device=detected_device,
report_progress=self.report_progress)
@ -110,7 +112,8 @@ class DeviceManager(Thread):
traceback.print_exc()
continue
self.connected_device = dev
self.connected_slot(True)
self.connected_device_is_folder = is_folder_device
self.connected_slot(True, is_folder_device)
return True
return False
@ -128,7 +131,7 @@ class DeviceManager(Thread):
if self.connected_device in self.ejected_devices:
self.ejected_devices.remove(self.connected_device)
else:
self.connected_slot(False)
self.connected_slot(False, self.connected_device_is_folder)
self.connected_device = None
def detect_device(self):
@ -149,17 +152,19 @@ class DeviceManager(Thread):
if possibly_connected:
possibly_connected_devices.append((device, detected_device))
if possibly_connected_devices:
if not self.do_connect(possibly_connected_devices):
if not self.do_connect(possibly_connected_devices,
is_folder_device=False):
print 'Connect to device failed, retrying in 5 seconds...'
time.sleep(5)
if not self.do_connect(possibly_connected_devices):
if not self.do_connect(possibly_connected_devices,
is_folder_device=False):
print 'Device connect failed again, giving up'
def umount_device(self, *args):
if self.is_device_connected:
self.connected_device.eject()
self.ejected_devices.add(self.connected_device)
self.connected_slot(False)
self.connected_slot(False, self.connected_device_is_folder)
def next(self):
if not self.jobs.empty():
@ -170,7 +175,18 @@ class DeviceManager(Thread):
def run(self):
while self.keep_going:
self.detect_device()
if not self.is_device_connected and \
self.folder_connection_path is not None:
f = self.folder_connection_path
self.folder_connection_path = None # Make sure we try this folder only once
try:
dev = FOLDER_DEVICE(f)
self.do_connect([[dev, None],], is_folder_device=True)
except:
print 'Unable to open folder as device', f
traceback.print_exc()
else:
self.detect_device()
while True:
job = self.next()
if job is not None:
@ -182,7 +198,6 @@ class DeviceManager(Thread):
break
time.sleep(self.sleep_time)
def create_job(self, func, done, description, args=[], kwargs={}):
job = DeviceJob(func, done, self.job_manager,
args=args, kwargs=kwargs, description=description)
@ -208,21 +223,19 @@ class DeviceManager(Thread):
return self.create_job(self._get_device_information, done,
description=_('Get device information'))
# This will be called on the GUI thread. Because of this, we must store
# information that the scanner thread will use to do the real work.
def connect_to_folder(self, path):
dev = FOLDER_DEVICE(path)
try:
dev.open()
except:
print 'Unable to open device', dev
traceback.print_exc()
return False
self.connected_device = dev
self.connected_slot(True)
return True
self.folder_connection_path = path
# This is called on the GUI thread. No problem here, because it calls the
# device driver, telling it to tell the scanner when it passes by that the
# folder has disconnected.
def disconnect_folder(self):
if self.connected_device is not None:
if hasattr(self.connected_device, 'disconnect_from_folder'):
# As we are on the wrong thread, this call must *not* do
# anything besides set a flag that the right thread will see.
self.connected_device.disconnect_from_folder()
def _books(self):
@ -1133,9 +1146,11 @@ class DeviceGUI(object):
resend_metadata = True
# Set author_sort if it isn't already
asort = getattr(book, 'author_sort', None)
if not asort:
pass
if not asort and book.authors:
book.author_sort = authors_to_sort_string(book.authors)
resend_metadata = True
if resend_metadata:
# Correcting metadata cache on device.
# Correct the metadata cache on device.
if self.device_manager.is_device_connected:
self.device_manager.sync_booklists(None, booklists)

View File

@ -670,10 +670,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search.setMaximumWidth(self.width()-150)
def connect_to_folder(self):
dir = choose_dir(self, 'Select Device Folder', 'Select folder to open')
dir = choose_dir(self, 'Select Device Folder',
_('Select folder to open as device'))
if dir is not None:
self.device_manager.connect_to_folder(dir)
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
def disconnect_from_folder(self):
self.device_manager.disconnect_folder()
@ -945,12 +945,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
elif model.location_for_row(x) == 'cardb':
self.card_b_view.write_settings()
def device_detected(self, connected):
def device_detected(self, connected, is_folder_device):
'''
Called when a device is connected to the computer.
'''
if connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
if is_folder_device:
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
self.device_manager.get_device_information(\
Dispatcher(self.info_read))
self.set_default_thumbnail(\