'Upload a list of books to the device. If a file already
exists on the device, it should be replaced.
This method should raise a L{FreeSpaceError} if there is not enough
free space on the device. The text of the FreeSpaceError must contain the
word "card" if C{on_card} is not None otherwise it must contain the word "memory".
:files: A list of paths and/or file-like objects.
:names: A list of file names that the books should have
once uploaded to the device. len(names) == len(files)
:return: A list of 3-element tuples. The list is meant to be passed
to L{add_books_to_metadata}.
:metadata: If not None, it is a list of :class:`Metadata` objects.
The idea is to use the metadata to determine where on the device to
put the book. len(metadata) == len(files). Apart from the regular
cover (path to cover), there may also be a thumbnail attribute, which should
be used in preference. The thumbnail attribute is of the form
(width, height, cover_data as jpeg).'

def upload_books(self, files, names, on_card=None, end_session=True, metadata=None):

    new_booklist = []
    self.update_list = []
    file_count = float(len(files))
    self.problem_titles = []
    self.problem_msg = _("Some    cover    art    could    not    be    converted.\nClick    'Show    Details'    for    a    list.")
    if self.verbose:
        logger().info(('%s.upload_books()' % self.__class__.__name__))
    if isosx:
        for (i, fpath) in enumerate(files):
            format = fpath.rpartition('.')[2].lower()
            path = (self.path_template % (metadata[i].title, authors_to_string(metadata[i].authors), format))
            self._remove_existing_copy(path, metadata[i])
            (db_added, lb_added) = self._add_new_copy(fpath, metadata[i])
            thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format)
            this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format)
            new_booklist.append(this_book)
            self._update_iTunes_metadata(metadata[i], db_added, lb_added, this_book)
            if self.verbose:
                logger().info(('%s.upload_books()' % self.__class__.__name__))
                logger().info(("    adding    '%s'    by    '%s'    uuid:%s    to    self.cached_books" % (metadata[i].title, authors_to_string(metadata[i].authors), metadata[i].uuid)))
            self.cached_books[this_book.path] = {'author': authors_to_string(metadata[i].authors), 'authors': metadata[i].authors, 'dev_book': db_added, 'format': format, 'lib_book': lb_added, 'title': metadata[i].title, 'uuid': metadata[i].uuid}
            if (self.report_progress is not None):
                self.report_progress(((i + 1) / file_count), (_('%(num)d    of    %(tot)d') % dict(num=(i + 1), tot=file_count)))
    elif iswindows:
        import pythoncom, win32com.client
        try:
            pythoncom.CoInitialize()
            self.iTunes = win32com.client.Dispatch('iTunes.Application')
            for (i, fpath) in enumerate(files):
                format = fpath.rpartition('.')[2].lower()
                path = (self.path_template % (metadata[i].title, authors_to_string(metadata[i].authors), format))
                self._remove_existing_copy(path, metadata[i])
                (db_added, lb_added) = self._add_new_copy(fpath, metadata[i])
                if (self.manual_sync_mode and (not db_added)):
                    self.problem_msg = "Title    and/or    author    metadata    mismatch    with    uploaded    books.\nClick    'Show    Details...'    for    affected    books."
                    self.problem_titles.append(("'%s'    by    %s" % (metadata[i].title, metadata[i].author[0])))
                thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format)
                this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format)
                new_booklist.append(this_book)
                self._update_iTunes_metadata(metadata[i], db_added, lb_added, this_book)
                if self.verbose:
                    logger().info(('%s.upload_books()' % self.__class__.__name__))
                    logger().info(("    adding    '%s'    by    '%s'    uuid:%s    to    self.cached_books" % (metadata[i].title, authors_to_string(metadata[i].authors), metadata[i].uuid)))
                self.cached_books[this_book.path] = {'author': authors_to_string(metadata[i].authors), 'authors': metadata[i].authors, 'dev_book': db_added, 'format': format, 'lib_book': lb_added, 'title': metadata[i].title, 'uuid': metadata[i].uuid}
                if (self.report_progress is not None):
                    self.report_progress(((i + 1) / file_count), (_('%(num)d    of    %(tot)d') % dict(num=(i + 1), tot=file_count)))
        finally:
            pythoncom.CoUninitialize()
    if (self.report_progress is not None):
        self.report_progress(1.0, _('finished'))
    if (not self.manual_sync_mode):
        self.update_needed = True
        self.update_msg = 'Added    books    to    device'
    if False:
        self._dump_booklist(new_booklist, header='after    upload_books()', indent=2)
        self._dump_cached_books(header='after    upload_books()', indent=2)
    return (new_booklist, [], [])
