Merge branch 'Develop' into master
This commit is contained in:
		
						commit
						c62a417cbf
					
				| 
						 | 
				
			
			@ -54,6 +54,12 @@ try:
 | 
			
		|||
except ImportError:
 | 
			
		||||
    greenlet_Version = None
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from scholarly import scholarly
 | 
			
		||||
    scholarly_version = _(u'installed')
 | 
			
		||||
except ImportError:
 | 
			
		||||
    scholarly_version = _(u'not installed')
 | 
			
		||||
 | 
			
		||||
from . import services
 | 
			
		||||
 | 
			
		||||
about = flask.Blueprint('about', __name__)
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +85,7 @@ _VERSIONS = OrderedDict(
 | 
			
		|||
    iso639=isoLanguages.__version__,
 | 
			
		||||
    pytz=pytz.__version__,
 | 
			
		||||
    Unidecode = unidecode_version,
 | 
			
		||||
    Scholarly = scholarly_version,
 | 
			
		||||
    Flask_SimpleLDAP =  u'installed' if bool(services.ldap) else None,
 | 
			
		||||
    python_LDAP = services.ldapVersion if bool(services.ldapVersion) else None,
 | 
			
		||||
    Goodreads = u'installed' if bool(services.goodreads_support) else None,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										32
									
								
								cps/admin.py
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								cps/admin.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -236,7 +236,7 @@ def edit_user_table():
 | 
			
		|||
        custom_values = []
 | 
			
		||||
    if not config.config_anonbrowse:
 | 
			
		||||
        allUser = allUser.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS)
 | 
			
		||||
 | 
			
		||||
    kobo_support = feature_support['kobo'] and config.config_kobo_sync
 | 
			
		||||
    return render_title_template("user_table.html",
 | 
			
		||||
                                 users=allUser.all(),
 | 
			
		||||
                                 tags=tags,
 | 
			
		||||
| 
						 | 
				
			
			@ -245,6 +245,7 @@ def edit_user_table():
 | 
			
		|||
                                 languages=languages,
 | 
			
		||||
                                 visiblility=visibility,
 | 
			
		||||
                                 all_roles=constants.ALL_ROLES,
 | 
			
		||||
                                 kobo_support=kobo_support,
 | 
			
		||||
                                 sidebar_settings=constants.sidebar_settings,
 | 
			
		||||
                                 title=_(u"Edit Users"),
 | 
			
		||||
                                 page="usertable")
 | 
			
		||||
| 
						 | 
				
			
			@ -391,6 +392,8 @@ def edit_list_user(param):
 | 
			
		|||
                    user.name = check_username(vals['value'])
 | 
			
		||||
                elif param =='email':
 | 
			
		||||
                    user.email = check_email(vals['value'])
 | 
			
		||||
                elif param =='kobo_only_shelves_sync':
 | 
			
		||||
                    user.kobo_only_shelves_sync = int(vals['value'] == 'true')
 | 
			
		||||
                elif param == 'kindle_mail':
 | 
			
		||||
                    user.kindle_mail = valid_email(vals['value']) if vals['value'] else ""
 | 
			
		||||
                elif param.endswith('role'):
 | 
			
		||||
| 
						 | 
				
			
			@ -495,30 +498,30 @@ def check_valid_restricted_column(column):
 | 
			
		|||
def update_view_configuration():
 | 
			
		||||
    to_save = request.form.to_dict()
 | 
			
		||||
 | 
			
		||||
    _config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
 | 
			
		||||
    _config_int = lambda x: config.set_from_dictionary(to_save, x, int)
 | 
			
		||||
    # _config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
 | 
			
		||||
    # _config_int = lambda x: config.set_from_dictionary(to_save, x, int)
 | 
			
		||||
 | 
			
		||||
    _config_string("config_calibre_web_title")
 | 
			
		||||
    _config_string("config_columns_to_ignore")
 | 
			
		||||
    if _config_string("config_title_regex"):
 | 
			
		||||
    _config_string(to_save, "config_calibre_web_title")
 | 
			
		||||
    _config_string(to_save, "config_columns_to_ignore")
 | 
			
		||||
    if _config_string(to_save, "config_title_regex"):
 | 
			
		||||
        calibre_db.update_title_sort(config)
 | 
			
		||||
 | 
			
		||||
    if not check_valid_read_column(to_save.get("config_read_column", "0")):
 | 
			
		||||
        flash(_(u"Invalid Read Column"), category="error")
 | 
			
		||||
        log.debug("Invalid Read column")
 | 
			
		||||
        return view_configuration()
 | 
			
		||||
    _config_int("config_read_column")
 | 
			
		||||
    _config_int(to_save, "config_read_column")
 | 
			
		||||
 | 
			
		||||
    if not check_valid_restricted_column(to_save.get("config_restricted_column", "0")):
 | 
			
		||||
        flash(_(u"Invalid Restricted Column"), category="error")
 | 
			
		||||
        log.debug("Invalid Restricted Column")
 | 
			
		||||
        return view_configuration()
 | 
			
		||||
    _config_int("config_restricted_column")
 | 
			
		||||
    _config_int(to_save, "config_restricted_column")
 | 
			
		||||
 | 
			
		||||
    _config_int("config_theme")
 | 
			
		||||
    _config_int("config_random_books")
 | 
			
		||||
    _config_int("config_books_per_page")
 | 
			
		||||
    _config_int("config_authors_max")
 | 
			
		||||
    _config_int(to_save, "config_theme")
 | 
			
		||||
    _config_int(to_save, "config_random_books")
 | 
			
		||||
    _config_int(to_save, "config_books_per_page")
 | 
			
		||||
    _config_int(to_save, "config_authors_max")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    config.config_default_role = constants.selected_roles(to_save)
 | 
			
		||||
| 
						 | 
				
			
			@ -558,6 +561,8 @@ def load_dialogtexts(element_id):
 | 
			
		|||
        texts["main"] = _('Are you sure you want to change the selected restrictions for the selected user(s)?')
 | 
			
		||||
    elif element_id == "sidebar_view":
 | 
			
		||||
        texts["main"] = _('Are you sure you want to change the selected visibility restrictions for the selected user(s)?')
 | 
			
		||||
    elif element_id == "kobo_only_shelves_sync":
 | 
			
		||||
        texts["main"] = _('Are you sure you want to change shelf sync behavior for the selected user(s)?')
 | 
			
		||||
    return json.dumps(texts)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1317,6 +1322,7 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support):
 | 
			
		|||
        content.denied_tags = config.config_denied_tags
 | 
			
		||||
        content.allowed_column_value = config.config_allowed_column_value
 | 
			
		||||
        content.denied_column_value = config.config_denied_column_value
 | 
			
		||||
        content.kobo_only_shelves_sync = 0  # No default value for kobo sync shelf setting
 | 
			
		||||
        ub.session.add(content)
 | 
			
		||||
        ub.session.commit()
 | 
			
		||||
        flash(_(u"User '%(user)s' created", user=content.name), category="success")
 | 
			
		||||
| 
						 | 
				
			
			@ -1384,6 +1390,8 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
 | 
			
		|||
        else:
 | 
			
		||||
            content.sidebar_view &= ~constants.DETAIL_RANDOM
 | 
			
		||||
 | 
			
		||||
        content.kobo_only_shelves_sync = int(to_save.get("kobo_only_shelves_sync") == "on") or 0
 | 
			
		||||
 | 
			
		||||
        if to_save.get("default_language"):
 | 
			
		||||
            content.default_language = to_save["default_language"]
 | 
			
		||||
        if to_save.get("locale"):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,15 @@ import json
 | 
			
		|||
from shutil import copyfile
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
# Improve this to check if scholarly is available in a global way, like other pythonic libraries
 | 
			
		||||
have_scholar = True
 | 
			
		||||
try:
 | 
			
		||||
    from scholarly import scholarly
 | 
			
		||||
except ImportError:
 | 
			
		||||
    have_scholar = False
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from babel import Locale as LC
 | 
			
		||||
from babel.core import UnknownLocaleError
 | 
			
		||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
 | 
			
		||||
| 
						 | 
				
			
			@ -1061,6 +1070,23 @@ def convert_bookformat(book_id):
 | 
			
		|||
        flash(_(u"There was an error converting this book: %(res)s", res=rtn), category="error")
 | 
			
		||||
    return redirect(url_for('editbook.edit_book', book_id=book_id))
 | 
			
		||||
 | 
			
		||||
@editbook.route("/scholarsearch/<query>",methods=['GET'])
 | 
			
		||||
@login_required_if_no_ano
 | 
			
		||||
@edit_required
 | 
			
		||||
def scholar_search(query):
 | 
			
		||||
    if have_scholar:
 | 
			
		||||
        scholar_gen = scholarly.search_pubs(' '.join(query.split('+')))
 | 
			
		||||
        i=0
 | 
			
		||||
        result = []
 | 
			
		||||
        for publication in scholar_gen:
 | 
			
		||||
            del publication['source']
 | 
			
		||||
            result.append(publication)
 | 
			
		||||
            i+=1
 | 
			
		||||
            if(i>=10):
 | 
			
		||||
                break
 | 
			
		||||
        return Response(json.dumps(result),mimetype='application/json')
 | 
			
		||||
    else:
 | 
			
		||||
        return []
 | 
			
		||||
 | 
			
		||||
@editbook.route("/ajax/editbooks/<param>", methods=['POST'])
 | 
			
		||||
@login_required_if_no_ano
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										149
									
								
								cps/kobo.py
									
									
									
									
									
								
							
							
						
						
									
										149
									
								
								cps/kobo.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -42,7 +42,7 @@ from flask import (
 | 
			
		|||
from flask_login import current_user
 | 
			
		||||
from werkzeug.datastructures import Headers
 | 
			
		||||
from sqlalchemy import func
 | 
			
		||||
from sqlalchemy.sql.expression import and_
 | 
			
		||||
from sqlalchemy.sql.expression import and_, or_
 | 
			
		||||
from sqlalchemy.exc import StatementError
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +81,7 @@ CONNECTION_SPECIFIC_HEADERS = [
 | 
			
		|||
    "transfer-encoding",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_kobo_activated():
 | 
			
		||||
    return config.config_kobo_sync
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -151,32 +152,42 @@ def HandleSyncRequest():
 | 
			
		|||
    # in case of external changes (e.g: adding a book through Calibre).
 | 
			
		||||
    calibre_db.reconnect_db(config, ub.app_DB_path)
 | 
			
		||||
 | 
			
		||||
    if sync_token.books_last_id > -1:
 | 
			
		||||
    only_kobo_shelves = current_user.kobo_only_shelves_sync
 | 
			
		||||
    # calibre_db.session.query(ub.Shelf).filter(ub.Shelf.user_id == current_user.id).filter(ub.Shelf.kobo_sync).count() > 0
 | 
			
		||||
 | 
			
		||||
    if only_kobo_shelves:
 | 
			
		||||
        changed_entries = (
 | 
			
		||||
            calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived)
 | 
			
		||||
            .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id)
 | 
			
		||||
            .filter(db.Books.last_modified >= sync_token.books_last_modified)
 | 
			
		||||
            .filter(db.Books.id>sync_token.books_last_id)
 | 
			
		||||
            .filter(db.Data.format.in_(KOBO_FORMATS))
 | 
			
		||||
            .filter(calibre_db.common_filters())
 | 
			
		||||
            .order_by(db.Books.last_modified)
 | 
			
		||||
            .order_by(db.Books.id)
 | 
			
		||||
            .limit(SYNC_ITEM_LIMIT)
 | 
			
		||||
            calibre_db.session.query(db.Books,
 | 
			
		||||
                                     ub.ArchivedBook.last_modified,
 | 
			
		||||
                                     ub.BookShelf.date_added,
 | 
			
		||||
                                     ub.ArchivedBook.is_archived)
 | 
			
		||||
                .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id)
 | 
			
		||||
                .filter(or_(db.Books.last_modified > sync_token.books_last_modified,
 | 
			
		||||
                            ub.BookShelf.date_added > sync_token.books_last_modified))
 | 
			
		||||
                .filter(db.Data.format.in_(KOBO_FORMATS)).filter(calibre_db.common_filters())
 | 
			
		||||
                .order_by(db.Books.id)
 | 
			
		||||
                .order_by(ub.ArchivedBook.last_modified)
 | 
			
		||||
                .join(ub.BookShelf, db.Books.id == ub.BookShelf.book_id)
 | 
			
		||||
                .join(ub.Shelf)
 | 
			
		||||
                .filter(ub.Shelf.kobo_sync)
 | 
			
		||||
                .distinct()
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        changed_entries = (
 | 
			
		||||
            calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived)
 | 
			
		||||
            .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id)
 | 
			
		||||
            .filter(db.Books.last_modified > sync_token.books_last_modified)
 | 
			
		||||
            .filter(db.Data.format.in_(KOBO_FORMATS))
 | 
			
		||||
            .filter(calibre_db.common_filters())
 | 
			
		||||
            .order_by(db.Books.last_modified)
 | 
			
		||||
            .order_by(db.Books.id)
 | 
			
		||||
            .limit(SYNC_ITEM_LIMIT)
 | 
			
		||||
                .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id)
 | 
			
		||||
                .filter(db.Books.last_modified > sync_token.books_last_modified)
 | 
			
		||||
                .filter(calibre_db.common_filters())
 | 
			
		||||
                .filter(db.Data.format.in_(KOBO_FORMATS))
 | 
			
		||||
                .order_by(db.Books.last_modified)
 | 
			
		||||
                .order_by(db.Books.id)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if sync_token.books_last_id > -1:
 | 
			
		||||
        changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id)
 | 
			
		||||
 | 
			
		||||
    reading_states_in_new_entitlements = []
 | 
			
		||||
    for book in changed_entries:
 | 
			
		||||
    for book in changed_entries.limit(SYNC_ITEM_LIMIT):
 | 
			
		||||
        formats = [data.format for data in book.Books.data]
 | 
			
		||||
        if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats:
 | 
			
		||||
            helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name)
 | 
			
		||||
| 
						 | 
				
			
			@ -192,7 +203,14 @@ def HandleSyncRequest():
 | 
			
		|||
            new_reading_state_last_modified = max(new_reading_state_last_modified, kobo_reading_state.last_modified)
 | 
			
		||||
            reading_states_in_new_entitlements.append(book.Books.id)
 | 
			
		||||
 | 
			
		||||
        if book.Books.timestamp > sync_token.books_last_created:
 | 
			
		||||
        ts_created = book.Books.timestamp
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            ts_created = max(ts_created, book.date_added)
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if ts_created > sync_token.books_last_created:
 | 
			
		||||
            sync_results.append({"NewEntitlement": entitlement})
 | 
			
		||||
        else:
 | 
			
		||||
            sync_results.append({"ChangedEntitlement": entitlement})
 | 
			
		||||
| 
						 | 
				
			
			@ -200,35 +218,48 @@ def HandleSyncRequest():
 | 
			
		|||
        new_books_last_modified = max(
 | 
			
		||||
            book.Books.last_modified, new_books_last_modified
 | 
			
		||||
        )
 | 
			
		||||
        new_books_last_created = max(book.Books.timestamp, new_books_last_created)
 | 
			
		||||
        try:
 | 
			
		||||
            new_books_last_modified = max(
 | 
			
		||||
                new_books_last_modified, book.date_added
 | 
			
		||||
            )
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        new_books_last_created = max(ts_created, new_books_last_created)
 | 
			
		||||
 | 
			
		||||
    max_change = changed_entries.from_self().filter(ub.ArchivedBook.is_archived)\
 | 
			
		||||
        .order_by(func.datetime(ub.ArchivedBook.last_modified).desc()).first()
 | 
			
		||||
 | 
			
		||||
    max_change = max_change.last_modified if max_change else new_archived_last_modified
 | 
			
		||||
 | 
			
		||||
    max_change = (changed_entries
 | 
			
		||||
        .from_self()
 | 
			
		||||
        .filter(ub.ArchivedBook.is_archived)
 | 
			
		||||
        .order_by(func.datetime(ub.ArchivedBook.last_modified).desc())
 | 
			
		||||
        .first()
 | 
			
		||||
    )
 | 
			
		||||
    if max_change:
 | 
			
		||||
        max_change = max_change.last_modified
 | 
			
		||||
    else:
 | 
			
		||||
        max_change = new_archived_last_modified
 | 
			
		||||
    new_archived_last_modified = max(new_archived_last_modified, max_change)
 | 
			
		||||
 | 
			
		||||
    # no. of books returned
 | 
			
		||||
    book_count = changed_entries.count()
 | 
			
		||||
 | 
			
		||||
    # last entry:
 | 
			
		||||
    if book_count:
 | 
			
		||||
        books_last_id = changed_entries.all()[-1].Books.id or -1
 | 
			
		||||
    else:
 | 
			
		||||
        books_last_id = -1
 | 
			
		||||
    books_last_id = changed_entries.all()[-1].Books.id or -1 if book_count else -1
 | 
			
		||||
 | 
			
		||||
    # generate reading state data
 | 
			
		||||
    changed_reading_states = (
 | 
			
		||||
        ub.session.query(ub.KoboReadingState)
 | 
			
		||||
        .filter(and_(func.datetime(ub.KoboReadingState.last_modified) > sync_token.reading_state_last_modified,
 | 
			
		||||
                     ub.KoboReadingState.user_id == current_user.id,
 | 
			
		||||
                     ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements))))
 | 
			
		||||
    changed_reading_states = ub.session.query(ub.KoboReadingState)
 | 
			
		||||
 | 
			
		||||
    if only_kobo_shelves:
 | 
			
		||||
        changed_reading_states = changed_reading_states.join(ub.BookShelf,
 | 
			
		||||
                                                             ub.KoboReadingState.book_id == ub.BookShelf.book_id)\
 | 
			
		||||
            .join(ub.Shelf)\
 | 
			
		||||
            .filter(ub.Shelf.kobo_sync,
 | 
			
		||||
                    or_(
 | 
			
		||||
                        func.datetime(ub.KoboReadingState.last_modified) > sync_token.reading_state_last_modified,
 | 
			
		||||
                        ub.BookShelf.date_added > sync_token.books_last_modified
 | 
			
		||||
                    )).distinct()
 | 
			
		||||
    else:
 | 
			
		||||
        changed_reading_states = changed_reading_states.filter(
 | 
			
		||||
            func.datetime(ub.KoboReadingState.last_modified) > sync_token.reading_state_last_modified)
 | 
			
		||||
 | 
			
		||||
    changed_reading_states = changed_reading_states.filter(
 | 
			
		||||
        and_(ub.KoboReadingState.user_id == current_user.id,
 | 
			
		||||
             ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements)))
 | 
			
		||||
 | 
			
		||||
    for kobo_reading_state in changed_reading_states.all():
 | 
			
		||||
        book = calibre_db.session.query(db.Books).filter(db.Books.id == kobo_reading_state.book_id).one_or_none()
 | 
			
		||||
        if book:
 | 
			
		||||
| 
						 | 
				
			
			@ -239,7 +270,7 @@ def HandleSyncRequest():
 | 
			
		|||
            })
 | 
			
		||||
            new_reading_state_last_modified = max(new_reading_state_last_modified, kobo_reading_state.last_modified)
 | 
			
		||||
 | 
			
		||||
    sync_shelves(sync_token, sync_results)
 | 
			
		||||
    sync_shelves(sync_token, sync_results, only_kobo_shelves)
 | 
			
		||||
 | 
			
		||||
    sync_token.books_last_created = new_books_last_created
 | 
			
		||||
    sync_token.books_last_modified = new_books_last_modified
 | 
			
		||||
| 
						 | 
				
			
			@ -394,7 +425,7 @@ def get_metadata(book):
 | 
			
		|||
 | 
			
		||||
    book_uuid = book.uuid
 | 
			
		||||
    metadata = {
 | 
			
		||||
        "Categories": ["00000000-0000-0000-0000-000000000001",],
 | 
			
		||||
        "Categories": ["00000000-0000-0000-0000-000000000001", ],
 | 
			
		||||
        # "Contributors": get_author(book),
 | 
			
		||||
        "CoverImageId": book_uuid,
 | 
			
		||||
        "CrossRevisionId": book_uuid,
 | 
			
		||||
| 
						 | 
				
			
			@ -601,13 +632,14 @@ def HandleTagRemoveItem(tag_id):
 | 
			
		|||
 | 
			
		||||
# Add new, changed, or deleted shelves to the sync_results.
 | 
			
		||||
# Note: Public shelves that aren't owned by the user aren't supported.
 | 
			
		||||
def sync_shelves(sync_token, sync_results):
 | 
			
		||||
def sync_shelves(sync_token, sync_results, only_kobo_shelves=False):
 | 
			
		||||
    new_tags_last_modified = sync_token.tags_last_modified
 | 
			
		||||
 | 
			
		||||
    for shelf in ub.session.query(ub.ShelfArchive).filter(func.datetime(ub.ShelfArchive.last_modified) > sync_token.tags_last_modified,
 | 
			
		||||
                                                          ub.ShelfArchive.user_id == current_user.id):
 | 
			
		||||
    for shelf in ub.session.query(ub.ShelfArchive).filter(
 | 
			
		||||
        func.datetime(ub.ShelfArchive.last_modified) > sync_token.tags_last_modified,
 | 
			
		||||
        ub.ShelfArchive.user_id == current_user.id
 | 
			
		||||
    ):
 | 
			
		||||
        new_tags_last_modified = max(shelf.last_modified, new_tags_last_modified)
 | 
			
		||||
 | 
			
		||||
        sync_results.append({
 | 
			
		||||
            "DeletedTag": {
 | 
			
		||||
                "Tag": {
 | 
			
		||||
| 
						 | 
				
			
			@ -617,8 +649,29 @@ def sync_shelves(sync_token, sync_results):
 | 
			
		|||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    for shelf in ub.session.query(ub.Shelf).filter(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified,
 | 
			
		||||
                                                   ub.Shelf.user_id == current_user.id):
 | 
			
		||||
    extra_filters = []
 | 
			
		||||
    if only_kobo_shelves:
 | 
			
		||||
        for shelf in ub.session.query(ub.Shelf).filter(
 | 
			
		||||
            func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified,
 | 
			
		||||
            ub.Shelf.user_id == current_user.id,
 | 
			
		||||
            not ub.Shelf.kobo_sync
 | 
			
		||||
        ):
 | 
			
		||||
            sync_results.append({
 | 
			
		||||
                "DeletedTag": {
 | 
			
		||||
                    "Tag": {
 | 
			
		||||
                        "Id": shelf.uuid,
 | 
			
		||||
                        "LastModified": convert_to_kobo_timestamp_string(shelf.last_modified)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        extra_filters.append(ub.Shelf.kobo_sync)
 | 
			
		||||
 | 
			
		||||
    for shelf in ub.session.query(ub.Shelf).outerjoin(ub.BookShelf).filter(
 | 
			
		||||
        or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified,
 | 
			
		||||
            ub.BookShelf.date_added > sync_token.tags_last_modified),
 | 
			
		||||
        ub.Shelf.user_id == current_user.id,
 | 
			
		||||
        *extra_filters
 | 
			
		||||
    ).distinct().order_by(func.datetime(ub.Shelf.last_modified).asc()):
 | 
			
		||||
        if not shelf_lib.check_shelf_view_permissions(shelf):
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										48
									
								
								cps/shelf.py
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								cps/shelf.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -21,20 +21,20 @@
 | 
			
		|||
#  along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
from __future__ import division, print_function, unicode_literals
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from flask import Blueprint, request, flash, redirect, url_for
 | 
			
		||||
from flask import Blueprint, flash, redirect, request, url_for
 | 
			
		||||
from flask_babel import gettext as _
 | 
			
		||||
from flask_login import login_required, current_user
 | 
			
		||||
from flask_login import current_user, login_required
 | 
			
		||||
from sqlalchemy.exc import InvalidRequestError, OperationalError
 | 
			
		||||
from sqlalchemy.sql.expression import func, true
 | 
			
		||||
from sqlalchemy.exc import OperationalError, InvalidRequestError
 | 
			
		||||
 | 
			
		||||
from . import logger, ub, calibre_db, db
 | 
			
		||||
from . import calibre_db, config, db, logger, ub
 | 
			
		||||
from .render_template import render_title_template
 | 
			
		||||
from .usermanagement import login_required_if_no_ano
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
shelf = Blueprint('shelf', __name__)
 | 
			
		||||
log = logger.create()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -240,15 +240,16 @@ def edit_shelf(shelf_id):
 | 
			
		|||
 | 
			
		||||
# if shelf ID is set, we are editing a shelf
 | 
			
		||||
def create_edit_shelf(shelf, title, page, shelf_id=False):
 | 
			
		||||
    sync_only_selected_shelves = current_user.kobo_only_shelves_sync
 | 
			
		||||
    # calibre_db.session.query(ub.Shelf).filter(ub.Shelf.user_id == current_user.id).filter(ub.Shelf.kobo_sync).count()
 | 
			
		||||
    if request.method == "POST":
 | 
			
		||||
        to_save = request.form.to_dict()
 | 
			
		||||
        if "is_public" in to_save:
 | 
			
		||||
            shelf.is_public = 1
 | 
			
		||||
        else:
 | 
			
		||||
            shelf.is_public = 0
 | 
			
		||||
        shelf.is_public = 1 if to_save.get("is_public") else 0
 | 
			
		||||
        if config.config_kobo_sync:
 | 
			
		||||
            shelf.kobo_sync = True if to_save.get("kobo_sync") else False
 | 
			
		||||
 | 
			
		||||
        if check_shelf_is_unique(shelf, to_save, shelf_id):
 | 
			
		||||
            shelf.name = to_save["title"]
 | 
			
		||||
            # shelf.last_modified = datetime.utcnow()
 | 
			
		||||
            if not shelf_id:
 | 
			
		||||
                shelf.user_id = int(current_user.id)
 | 
			
		||||
                ub.session.add(shelf)
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +272,12 @@ def create_edit_shelf(shelf, title, page, shelf_id=False):
 | 
			
		|||
                ub.session.rollback()
 | 
			
		||||
                log.debug_or_exception(ex)
 | 
			
		||||
                flash(_(u"There was an error"), category="error")
 | 
			
		||||
    return render_title_template('shelf_edit.html', shelf=shelf, title=title, page=page)
 | 
			
		||||
    return render_title_template('shelf_edit.html',
 | 
			
		||||
                                 shelf=shelf,
 | 
			
		||||
                                 title=title,
 | 
			
		||||
                                 page=page,
 | 
			
		||||
                                 kobo_sync_enabled=config.config_kobo_sync,
 | 
			
		||||
                                 sync_only_selected_shelves=sync_only_selected_shelves)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_shelf_is_unique(shelf, to_save, shelf_id=False):
 | 
			
		||||
| 
						 | 
				
			
			@ -362,8 +368,8 @@ def order_shelf(shelf_id):
 | 
			
		|||
    shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
 | 
			
		||||
    result = list()
 | 
			
		||||
    if shelf and check_shelf_view_permissions(shelf):
 | 
			
		||||
        result = calibre_db.session.query(db.Books)\
 | 
			
		||||
            .join(ub.BookShelf,ub.BookShelf.book_id == db.Books.id , isouter=True) \
 | 
			
		||||
        result = calibre_db.session.query(db.Books) \
 | 
			
		||||
            .join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id, isouter=True) \
 | 
			
		||||
            .add_columns(calibre_db.common_filters().label("visible")) \
 | 
			
		||||
            .filter(ub.BookShelf.shelf == shelf_id).order_by(ub.BookShelf.order.asc()).all()
 | 
			
		||||
    return render_title_template('shelf_order.html', entries=result,
 | 
			
		||||
| 
						 | 
				
			
			@ -372,7 +378,7 @@ def order_shelf(shelf_id):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def change_shelf_order(shelf_id, order):
 | 
			
		||||
    result = calibre_db.session.query(db.Books).join(ub.BookShelf,ub.BookShelf.book_id == db.Books.id)\
 | 
			
		||||
    result = calibre_db.session.query(db.Books).join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id) \
 | 
			
		||||
        .filter(ub.BookShelf.shelf == shelf_id).order_by(*order).all()
 | 
			
		||||
    for index, entry in enumerate(result):
 | 
			
		||||
        book = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
 | 
			
		||||
| 
						 | 
				
			
			@ -412,13 +418,13 @@ def render_show_shelf(shelf_type, shelf_id, page_no, sort_param):
 | 
			
		|||
            page = 'shelfdown.html'
 | 
			
		||||
 | 
			
		||||
        result, __, pagination = calibre_db.fill_indexpage(page_no, pagesize,
 | 
			
		||||
                                                            db.Books,
 | 
			
		||||
                                                            ub.BookShelf.shelf == shelf_id,
 | 
			
		||||
                                                            [ub.BookShelf.order.asc()],
 | 
			
		||||
                                                            ub.BookShelf,ub.BookShelf.book_id == db.Books.id)
 | 
			
		||||
                                                           db.Books,
 | 
			
		||||
                                                           ub.BookShelf.shelf == shelf_id,
 | 
			
		||||
                                                           [ub.BookShelf.order.asc()],
 | 
			
		||||
                                                           ub.BookShelf, ub.BookShelf.book_id == db.Books.id)
 | 
			
		||||
        # delete chelf entries where book is not existent anymore, can happen if book is deleted outside calibre-web
 | 
			
		||||
        wrong_entries = calibre_db.session.query(ub.BookShelf)\
 | 
			
		||||
            .join(db.Books, ub.BookShelf.book_id == db.Books.id, isouter=True)\
 | 
			
		||||
        wrong_entries = calibre_db.session.query(ub.BookShelf) \
 | 
			
		||||
            .join(db.Books, ub.BookShelf.book_id == db.Books.id, isouter=True) \
 | 
			
		||||
            .filter(db.Books.id == None).all()
 | 
			
		||||
        for entry in wrong_entries:
 | 
			
		||||
            log.info('Not existing book {} in {} deleted'.format(entry.book_id, shelf))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								cps/static/img/academicpaper.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								cps/static/img/academicpaper.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 175 KiB  | 
| 
						 | 
				
			
			@ -1,449 +0,0 @@
 | 
			
		|||
/* alphanum.js (C) Brian Huisman
 | 
			
		||||
 * Based on the Alphanum Algorithm by David Koelle
 | 
			
		||||
 * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
 | 
			
		||||
 *
 | 
			
		||||
 * Distributed under same license as original
 | 
			
		||||
 *
 | 
			
		||||
 * Released under the MIT License - https://opensource.org/licenses/MIT
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining
 | 
			
		||||
 * a copy of this software and associated documentation files (the "Software"),
 | 
			
		||||
 * to deal in the Software without restriction, including without limitation
 | 
			
		||||
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 | 
			
		||||
 * and/or sell copies of the Software, and to permit persons to whom the
 | 
			
		||||
 * Software is furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included
 | 
			
		||||
 * in all copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 | 
			
		||||
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | 
			
		||||
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 | 
			
		||||
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 | 
			
		||||
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 | 
			
		||||
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 | 
			
		||||
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
/* ********************************************************************
 | 
			
		||||
* Alphanum sort() function version - case insensitive
 | 
			
		||||
*  - Slower, but easier to modify for arrays of objects which contain
 | 
			
		||||
*    string properties
 | 
			
		||||
*
 | 
			
		||||
*/
 | 
			
		||||
/* exported alphanumCase */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function alphanumCase(a, b) {
 | 
			
		||||
    function chunkify(t) {
 | 
			
		||||
        var tz = new Array();
 | 
			
		||||
        var x = 0, y = -1, n = 0, i, j;
 | 
			
		||||
 | 
			
		||||
        while (i = (j = t.charAt(x++)).charCodeAt(0)) {
 | 
			
		||||
            var m = (i === 46 || (i >= 48 && i <= 57));
 | 
			
		||||
            // Compare has to be with != otherwise fails
 | 
			
		||||
            if (m != n) {
 | 
			
		||||
                tz[++y] = "";
 | 
			
		||||
                n = m;
 | 
			
		||||
            }
 | 
			
		||||
            tz[y] += j;
 | 
			
		||||
        }
 | 
			
		||||
        return tz;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var aa = chunkify(a.filename.toLowerCase());
 | 
			
		||||
    var bb = chunkify(b.filename.toLowerCase());
 | 
			
		||||
 | 
			
		||||
    for (var x = 0; aa[x] && bb[x]; x++) {
 | 
			
		||||
        if (aa[x] !== bb[x]) {
 | 
			
		||||
            var c = Number(aa[x]), d = Number(bb[x]);
 | 
			
		||||
            // Compare has to be with == otherwise fails
 | 
			
		||||
            if (c == aa[x] && d == bb[x]) {
 | 
			
		||||
                return c - d;
 | 
			
		||||
            } else {
 | 
			
		||||
                return (aa[x] > bb[x]) ? 1 : -1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return aa.length - bb.length;
 | 
			
		||||
}
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * archive.js
 | 
			
		||||
 *
 | 
			
		||||
 * Provides base functionality for unarchiving.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the MIT License
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright(c) 2011 Google Inc.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* global bitjs, Uint8Array */
 | 
			
		||||
 | 
			
		||||
var bitjs = bitjs || {};
 | 
			
		||||
bitjs.archive = bitjs.archive || {};
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
    // ===========================================================================
 | 
			
		||||
    // Stolen from Closure because it's the best way to do Java-like inheritance.
 | 
			
		||||
    bitjs.base = function(me, optMethodName, varArgs) {
 | 
			
		||||
        var caller = arguments.callee.caller;
 | 
			
		||||
        if (caller.superClass_) {
 | 
			
		||||
            // This is a constructor. Call the superclass constructor.
 | 
			
		||||
            return caller.superClass_.constructor.apply(
 | 
			
		||||
                me, Array.prototype.slice.call(arguments, 1));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var args = Array.prototype.slice.call(arguments, 2);
 | 
			
		||||
        var foundCaller = false;
 | 
			
		||||
        for (var ctor = me.constructor; ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
 | 
			
		||||
            if (ctor.prototype[optMethodName] === caller) {
 | 
			
		||||
                foundCaller = true;
 | 
			
		||||
            } else if (foundCaller) {
 | 
			
		||||
                return ctor.prototype[optMethodName].apply(me, args);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we did not find the caller in the prototype chain,
 | 
			
		||||
        // then one of two things happened:
 | 
			
		||||
        // 1) The caller is an instance method.
 | 
			
		||||
        // 2) This method was not called by the right caller.
 | 
			
		||||
        if (me[optMethodName] === caller) {
 | 
			
		||||
            return me.constructor.prototype[optMethodName].apply(me, args);
 | 
			
		||||
        } else {
 | 
			
		||||
            throw Error(
 | 
			
		||||
                "goog.base called from a method of one name " +
 | 
			
		||||
                "to a method of a different name");
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits = function(childCtor, parentCtor) {
 | 
			
		||||
        /** @constructor */
 | 
			
		||||
        function TempCtor() {}
 | 
			
		||||
        TempCtor.prototype = parentCtor.prototype;
 | 
			
		||||
        childCtor.superClass_ = parentCtor.prototype;
 | 
			
		||||
        childCtor.prototype = new TempCtor();
 | 
			
		||||
        childCtor.prototype.constructor = childCtor;
 | 
			
		||||
    };
 | 
			
		||||
    // ===========================================================================
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An unarchive event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} type The event type.
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.UnarchiveEvent = function(type) {
 | 
			
		||||
        /**
 | 
			
		||||
         * The event type.
 | 
			
		||||
         *
 | 
			
		||||
         * @type {string}
 | 
			
		||||
         */
 | 
			
		||||
        this.type = type;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The UnarchiveEvent types.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.UnarchiveEvent.Type = {
 | 
			
		||||
        START: "start",
 | 
			
		||||
        PROGRESS: "progress",
 | 
			
		||||
        EXTRACT: "extract",
 | 
			
		||||
        FINISH: "finish",
 | 
			
		||||
        INFO: "info",
 | 
			
		||||
        ERROR: "error"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Useful for passing info up to the client (for debugging).
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} msg The info message.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.UnarchiveInfoEvent = function(msg) {
 | 
			
		||||
        bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The information message.
 | 
			
		||||
         *
 | 
			
		||||
         * @type {string}
 | 
			
		||||
         */
 | 
			
		||||
        this.msg = msg;
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An unrecoverable error has occured.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} msg The error message.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.UnarchiveErrorEvent = function(msg) {
 | 
			
		||||
        bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The information message.
 | 
			
		||||
         *
 | 
			
		||||
         * @type {string}
 | 
			
		||||
         */
 | 
			
		||||
        this.msg = msg;
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} msg The info message.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.UnarchiveStartEvent = function() {
 | 
			
		||||
        bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.START);
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.UnarchiveStartEvent, bitjs.archive.UnarchiveEvent);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Finish event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} msg The info message.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.UnarchiveFinishEvent = function() {
 | 
			
		||||
        bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.FINISH);
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.UnarchiveFinishEvent, bitjs.archive.UnarchiveEvent);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Progress event.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.UnarchiveProgressEvent = function(
 | 
			
		||||
        currentFilename,
 | 
			
		||||
        currentFileNumber,
 | 
			
		||||
        currentBytesUnarchivedInFile,
 | 
			
		||||
        currentBytesUnarchived,
 | 
			
		||||
        totalUncompressedBytesInArchive,
 | 
			
		||||
        totalFilesInArchive) {
 | 
			
		||||
        bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.PROGRESS);
 | 
			
		||||
 | 
			
		||||
        this.currentFilename = currentFilename;
 | 
			
		||||
        this.currentFileNumber = currentFileNumber;
 | 
			
		||||
        this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile;
 | 
			
		||||
        this.totalFilesInArchive = totalFilesInArchive;
 | 
			
		||||
        this.currentBytesUnarchived = currentBytesUnarchived;
 | 
			
		||||
        this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive;
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEvent);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * All extracted files returned by an Unarchiver will implement
 | 
			
		||||
     * the following interface:
 | 
			
		||||
     *
 | 
			
		||||
     * interface UnarchivedFile {
 | 
			
		||||
     *   string filename
 | 
			
		||||
     *   TypedArray fileData
 | 
			
		||||
     * }
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Extract event.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.UnarchiveExtractEvent = function(unarchivedFile) {
 | 
			
		||||
        bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @type {UnarchivedFile}
 | 
			
		||||
         */
 | 
			
		||||
        this.unarchivedFile = unarchivedFile;
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Base class for all Unarchivers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {ArrayBuffer} arrayBuffer The Array Buffer.
 | 
			
		||||
     * @param {string} optPathToBitJS Optional string for where the BitJS files are located.
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unarchiver = function(arrayBuffer, optPathToBitJS) {
 | 
			
		||||
        /**
 | 
			
		||||
         * The ArrayBuffer object.
 | 
			
		||||
         * @type {ArrayBuffer}
 | 
			
		||||
         * @protected
 | 
			
		||||
         */
 | 
			
		||||
        this.ab = arrayBuffer;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The path to the BitJS files.
 | 
			
		||||
         * @type {string}
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        this.pathToBitJS_ = optPathToBitJS || "/";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * A map from event type to an array of listeners.
 | 
			
		||||
         * @type {Map.<string, Array>}
 | 
			
		||||
         */
 | 
			
		||||
        this.listeners_ = {};
 | 
			
		||||
        for (var type in bitjs.archive.UnarchiveEvent.Type) {
 | 
			
		||||
            this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Private web worker initialized during start().
 | 
			
		||||
     * @type {Worker}
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unarchiver.prototype.worker_ = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This method must be overridden by the subclass to return the script filename.
 | 
			
		||||
     * @return {string} The script filename.
 | 
			
		||||
     * @protected.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unarchiver.prototype.getScriptFileName = function() {
 | 
			
		||||
        throw "Subclasses of AbstractUnarchiver must overload getScriptFileName()";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds an event listener for UnarchiveEvents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} Event type.
 | 
			
		||||
     * @param {function} An event handler function.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unarchiver.prototype.addEventListener = function(type, listener) {
 | 
			
		||||
        if (type in this.listeners_) {
 | 
			
		||||
            if (this.listeners_[type].indexOf(listener) === -1) {
 | 
			
		||||
                this.listeners_[type].push(listener);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes an event listener.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} Event type.
 | 
			
		||||
     * @param {EventListener|function} An event listener or handler function.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unarchiver.prototype.removeEventListener = function(type, listener) {
 | 
			
		||||
        if (type in this.listeners_) {
 | 
			
		||||
            var index = this.listeners_[type].indexOf(listener);
 | 
			
		||||
            if (index !== -1) {
 | 
			
		||||
                this.listeners_[type].splice(index, 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Receive an event and pass it to the listener functions.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {bitjs.archive.UnarchiveEvent} e
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unarchiver.prototype.handleWorkerEvent_ = function(e) {
 | 
			
		||||
        if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) &&
 | 
			
		||||
            this.listeners_[e.type] instanceof Array) {
 | 
			
		||||
            this.listeners_[e.type].forEach(function (listener) {
 | 
			
		||||
                listener(e);
 | 
			
		||||
            });
 | 
			
		||||
            if (e.type === bitjs.archive.UnarchiveEvent.Type.FINISH) {
 | 
			
		||||
                this.worker_.terminate();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the unarchive in a separate Web Worker thread and returns immediately.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unarchiver.prototype.start = function() {
 | 
			
		||||
        var me = this;
 | 
			
		||||
        var scriptFileName = this.pathToBitJS_ + this.getScriptFileName();
 | 
			
		||||
        if (scriptFileName) {
 | 
			
		||||
            this.worker_ = new Worker(scriptFileName);
 | 
			
		||||
 | 
			
		||||
            this.worker_.onerror = function(e) {
 | 
			
		||||
                throw e;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.worker_.onmessage = function(e) {
 | 
			
		||||
                if (typeof e.data !== "string") {
 | 
			
		||||
                    // Assume that it is an UnarchiveEvent.  Some browsers preserve the 'type'
 | 
			
		||||
                    // so that instanceof UnarchiveEvent returns true, but others do not.
 | 
			
		||||
                    me.handleWorkerEvent_(e.data);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.worker_.postMessage({file: this.ab});
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Terminates the Web Worker for this Unarchiver and returns immediately.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unarchiver.prototype.stop = function() {
 | 
			
		||||
        if (this.worker_) {
 | 
			
		||||
            this.worker_.terminate();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unzipper
 | 
			
		||||
     * @extends {bitjs.archive.Unarchiver}
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unzipper = function(arrayBuffer, optPathToBitJS) {
 | 
			
		||||
        bitjs.base(this, arrayBuffer, optPathToBitJS);
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver);
 | 
			
		||||
    bitjs.archive.Unzipper.prototype.getScriptFileName = function() {
 | 
			
		||||
        return "unzip.js";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unrarrer
 | 
			
		||||
     * @extends {bitjs.archive.Unarchiver}
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Unrarrer = function(arrayBuffer, optPathToBitJS) {
 | 
			
		||||
        bitjs.base(this, arrayBuffer, optPathToBitJS);
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver);
 | 
			
		||||
    bitjs.archive.Unrarrer.prototype.getScriptFileName = function() {
 | 
			
		||||
        return "unrar.js";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Untarrer
 | 
			
		||||
     * @extends {bitjs.archive.Unarchiver}
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.Untarrer = function(arrayBuffer, optPathToBitJS) {
 | 
			
		||||
        bitjs.base(this, arrayBuffer, optPathToBitJS);
 | 
			
		||||
    };
 | 
			
		||||
    bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver);
 | 
			
		||||
    bitjs.archive.Untarrer.prototype.getScriptFileName = function() {
 | 
			
		||||
        return "untar.js";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Factory method that creates an unarchiver based on the byte signature found
 | 
			
		||||
     * in the arrayBuffer.
 | 
			
		||||
     * @param {ArrayBuffer} ab
 | 
			
		||||
     * @param {string=} optPathToBitJS Path to the unarchiver script files.
 | 
			
		||||
     * @return {bitjs.archive.Unarchiver}
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.archive.GetUnarchiver = function(ab, optPathToBitJS) {
 | 
			
		||||
        var unarchiver = null;
 | 
			
		||||
        var pathToBitJS = optPathToBitJS || "";
 | 
			
		||||
        var h = new Uint8Array(ab, 0, 10);
 | 
			
		||||
 | 
			
		||||
        if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { // Rar!
 | 
			
		||||
            unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS);
 | 
			
		||||
        } else if (h[0] === 80 && h[1] === 75) { // PK (Zip)
 | 
			
		||||
            unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS);
 | 
			
		||||
        } else { // Try with tar
 | 
			
		||||
            unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
 | 
			
		||||
        }
 | 
			
		||||
        return unarchiver;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			@ -1,872 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * rarvm.js
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the MIT License
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright(c) 2017 Google Inc.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * CRC Implementation.
 | 
			
		||||
 */
 | 
			
		||||
/* global Uint8Array, Uint32Array, bitjs, DataView, mem */
 | 
			
		||||
/* exported MAXWINMASK, UnpackFilter */
 | 
			
		||||
 | 
			
		||||
function emptyArr(n, v) {
 | 
			
		||||
    var arr = [];
 | 
			
		||||
    for (var i = 0; i < n; i += 1) {
 | 
			
		||||
        arr[i] = v;
 | 
			
		||||
    }
 | 
			
		||||
    return arr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var CRCTab = emptyArr(256, 0);
 | 
			
		||||
 | 
			
		||||
function initCRC() {
 | 
			
		||||
    for (var i = 0; i < 256; ++i) {
 | 
			
		||||
        var c = i;
 | 
			
		||||
        for (var j = 0; j < 8; ++j) {
 | 
			
		||||
            // Read http://stackoverflow.com/questions/6798111/bitwise-operations-on-32-bit-unsigned-ints
 | 
			
		||||
            // for the bitwise operator issue (JS interprets operands as 32-bit signed
 | 
			
		||||
            // integers and we need to deal with unsigned ones here).
 | 
			
		||||
            c = ((c & 1) ? ((c >>> 1) ^ 0xEDB88320) : (c >>> 1)) >>> 0;
 | 
			
		||||
        }
 | 
			
		||||
        CRCTab[i] = c;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {number} startCRC
 | 
			
		||||
 * @param {Uint8Array} arr
 | 
			
		||||
 * @return {number}
 | 
			
		||||
 */
 | 
			
		||||
function CRC(startCRC, arr) {
 | 
			
		||||
    if (CRCTab[1] === 0) {
 | 
			
		||||
        initCRC();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    #if defined(LITTLE_ENDIAN) && defined(PRESENT_INT32) && defined(ALLOW_NOT_ALIGNED_INT)
 | 
			
		||||
      while (Size>0 && ((long)Data & 7))
 | 
			
		||||
      {
 | 
			
		||||
        StartCRC=CRCTab[(byte)(StartCRC^Data[0])]^(StartCRC>>8);
 | 
			
		||||
        Size--;
 | 
			
		||||
        Data++;
 | 
			
		||||
      }
 | 
			
		||||
      while (Size>=8)
 | 
			
		||||
      {
 | 
			
		||||
        StartCRC^=*(uint32 *)Data;
 | 
			
		||||
        StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
 | 
			
		||||
        StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
 | 
			
		||||
        StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
 | 
			
		||||
        StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
 | 
			
		||||
        StartCRC^=*(uint32 *)(Data+4);
 | 
			
		||||
        StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
 | 
			
		||||
        StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
 | 
			
		||||
        StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
 | 
			
		||||
        StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
 | 
			
		||||
        Data+=8;
 | 
			
		||||
        Size-=8;
 | 
			
		||||
      }
 | 
			
		||||
    #endif
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    for (var i = 0; i < arr.length; ++i) {
 | 
			
		||||
        var byte = ((startCRC ^ arr[i]) >>> 0) & 0xff;
 | 
			
		||||
        startCRC = (CRCTab[byte] ^ (startCRC >>> 8)) >>> 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return startCRC;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ============================================================================================== //
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * RarVM Implementation.
 | 
			
		||||
 */
 | 
			
		||||
var VM_MEMSIZE = 0x40000;
 | 
			
		||||
var VM_MEMMASK = (VM_MEMSIZE - 1);
 | 
			
		||||
var VM_GLOBALMEMADDR = 0x3C000;
 | 
			
		||||
var VM_GLOBALMEMSIZE = 0x2000;
 | 
			
		||||
var VM_FIXEDGLOBALSIZE = 64;
 | 
			
		||||
var MAXWINSIZE = 0x400000;
 | 
			
		||||
var MAXWINMASK = (MAXWINSIZE - 1);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 */
 | 
			
		||||
var VmCommands = {
 | 
			
		||||
    VM_MOV: 0,
 | 
			
		||||
    VM_CMP: 1,
 | 
			
		||||
    VM_ADD: 2,
 | 
			
		||||
    VM_SUB: 3,
 | 
			
		||||
    VM_JZ: 4,
 | 
			
		||||
    VM_JNZ: 5,
 | 
			
		||||
    VM_INC: 6,
 | 
			
		||||
    VM_DEC: 7,
 | 
			
		||||
    VM_JMP: 8,
 | 
			
		||||
    VM_XOR: 9,
 | 
			
		||||
    VM_AND: 10,
 | 
			
		||||
    VM_OR: 11,
 | 
			
		||||
    VM_TEST: 12,
 | 
			
		||||
    VM_JS: 13,
 | 
			
		||||
    VM_JNS: 14,
 | 
			
		||||
    VM_JB: 15,
 | 
			
		||||
    VM_JBE: 16,
 | 
			
		||||
    VM_JA: 17,
 | 
			
		||||
    VM_JAE: 18,
 | 
			
		||||
    VM_PUSH: 19,
 | 
			
		||||
    VM_POP: 20,
 | 
			
		||||
    VM_CALL: 21,
 | 
			
		||||
    VM_RET: 22,
 | 
			
		||||
    VM_NOT: 23,
 | 
			
		||||
    VM_SHL: 24,
 | 
			
		||||
    VM_SHR: 25,
 | 
			
		||||
    VM_SAR: 26,
 | 
			
		||||
    VM_NEG: 27,
 | 
			
		||||
    VM_PUSHA: 28,
 | 
			
		||||
    VM_POPA: 29,
 | 
			
		||||
    VM_PUSHF: 30,
 | 
			
		||||
    VM_POPF: 31,
 | 
			
		||||
    VM_MOVZX: 32,
 | 
			
		||||
    VM_MOVSX: 33,
 | 
			
		||||
    VM_XCHG: 34,
 | 
			
		||||
    VM_MUL: 35,
 | 
			
		||||
    VM_DIV: 36,
 | 
			
		||||
    VM_ADC: 37,
 | 
			
		||||
    VM_SBB: 38,
 | 
			
		||||
    VM_PRINT: 39,
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    #ifdef VM_OPTIMIZE
 | 
			
		||||
      VM_MOVB, VM_MOVD, VM_CMPB, VM_CMPD,
 | 
			
		||||
 | 
			
		||||
      VM_ADDB, VM_ADDD, VM_SUBB, VM_SUBD, VM_INCB, VM_INCD, VM_DECB, VM_DECD,
 | 
			
		||||
      VM_NEGB, VM_NEGD,
 | 
			
		||||
    #endif
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    // TODO: This enum value would be much larger if VM_OPTIMIZE.
 | 
			
		||||
    VM_STANDARD: 40,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 */
 | 
			
		||||
var VmStandardFilters = {
 | 
			
		||||
    VMSF_NONE: 0,
 | 
			
		||||
    VMSF_E8: 1,
 | 
			
		||||
    VMSF_E8E9: 2,
 | 
			
		||||
    VMSF_ITANIUM: 3,
 | 
			
		||||
    VMSF_RGB: 4,
 | 
			
		||||
    VMSF_AUDIO: 5,
 | 
			
		||||
    VMSF_DELTA: 6,
 | 
			
		||||
    VMSF_UPCASE: 7,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 */
 | 
			
		||||
var VmFlags = {
 | 
			
		||||
    VM_FC: 1,
 | 
			
		||||
    VM_FZ: 2,
 | 
			
		||||
    VM_FS: 0x80000000,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 */
 | 
			
		||||
var VmOpType = {
 | 
			
		||||
    VM_OPREG: 0,
 | 
			
		||||
    VM_OPINT: 1,
 | 
			
		||||
    VM_OPREGMEM: 2,
 | 
			
		||||
    VM_OPNONE: 3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Finds the key that maps to a given value in an object.  This function is useful in debugging
 | 
			
		||||
 * variables that use the above enums.
 | 
			
		||||
 * @param {Object} obj
 | 
			
		||||
 * @param {number} val
 | 
			
		||||
 * @return {string} The key/enum value as a string.
 | 
			
		||||
 */
 | 
			
		||||
function findKeyForValue(obj, val) {
 | 
			
		||||
    for (var key in obj) {
 | 
			
		||||
        if (obj[key] === val) {
 | 
			
		||||
            return key;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getDebugString(obj, val) {
 | 
			
		||||
    var s = "Unknown.";
 | 
			
		||||
    if (obj === VmCommands) {
 | 
			
		||||
        s = "VmCommands.";
 | 
			
		||||
    } else if (obj === VmStandardFilters) {
 | 
			
		||||
        s = "VmStandardFilters.";
 | 
			
		||||
    } else if (obj === VmFlags) {
 | 
			
		||||
        s = "VmOpType.";
 | 
			
		||||
    } else if (obj === VmOpType) {
 | 
			
		||||
        s = "VmOpType.";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return s + findKeyForValue(obj, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @struct
 | 
			
		||||
 * @constructor
 | 
			
		||||
 */
 | 
			
		||||
var VmPreparedOperand = function() {
 | 
			
		||||
    /** @type {VmOpType} */
 | 
			
		||||
    this.Type;
 | 
			
		||||
 | 
			
		||||
    /** @type {number} */
 | 
			
		||||
    this.Data = 0;
 | 
			
		||||
 | 
			
		||||
    /** @type {number} */
 | 
			
		||||
    this.Base = 0;
 | 
			
		||||
 | 
			
		||||
    // TODO: In C++ this is a uint*
 | 
			
		||||
    /** @type {Array<number>} */
 | 
			
		||||
    this.Addr = null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** @return {string} */
 | 
			
		||||
VmPreparedOperand.prototype.toString = function() {
 | 
			
		||||
    if (this.Type === null) {
 | 
			
		||||
        return "Error: Type was null in VmPreparedOperand";
 | 
			
		||||
    }
 | 
			
		||||
    return "{ " +
 | 
			
		||||
        "Type: " + getDebugString(VmOpType, this.Type) +
 | 
			
		||||
        ", Data: " + this.Data +
 | 
			
		||||
        ", Base: " + this.Base +
 | 
			
		||||
        " }";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @struct
 | 
			
		||||
 * @constructor
 | 
			
		||||
 */
 | 
			
		||||
var VmPreparedCommand = function() {
 | 
			
		||||
    /** @type {VmCommands} */
 | 
			
		||||
    this.OpCode;
 | 
			
		||||
 | 
			
		||||
    /** @type {boolean} */
 | 
			
		||||
    this.ByteMode = false;
 | 
			
		||||
 | 
			
		||||
    /** @type {VmPreparedOperand} */
 | 
			
		||||
    this.Op1 = new VmPreparedOperand();
 | 
			
		||||
 | 
			
		||||
    /** @type {VmPreparedOperand} */
 | 
			
		||||
    this.Op2 = new VmPreparedOperand();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** @return {string} */
 | 
			
		||||
VmPreparedCommand.prototype.toString = function(indent) {
 | 
			
		||||
    if (this.OpCode === null) {
 | 
			
		||||
        return "Error: OpCode was null in VmPreparedCommand";
 | 
			
		||||
    }
 | 
			
		||||
    indent = indent || "";
 | 
			
		||||
    return indent + "{\n" +
 | 
			
		||||
        indent + "  OpCode: " + getDebugString(VmCommands, this.OpCode) + ",\n" +
 | 
			
		||||
        indent + "  ByteMode: " + this.ByteMode + ",\n" +
 | 
			
		||||
        indent + "  Op1: " + this.Op1.toString() + ",\n" +
 | 
			
		||||
        indent + "  Op2: " + this.Op2.toString() + ",\n" +
 | 
			
		||||
        indent + "}";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @struct
 | 
			
		||||
 * @constructor
 | 
			
		||||
 */
 | 
			
		||||
var VmPreparedProgram = function() {
 | 
			
		||||
    /** @type {Array<VmPreparedCommand>} */
 | 
			
		||||
    this.Cmd = [];
 | 
			
		||||
 | 
			
		||||
    /** @type {Array<VmPreparedCommand>} */
 | 
			
		||||
    this.AltCmd = null;
 | 
			
		||||
 | 
			
		||||
    /** @type {Uint8Array} */
 | 
			
		||||
    this.GlobalData = new Uint8Array();
 | 
			
		||||
 | 
			
		||||
    /** @type {Uint8Array} */
 | 
			
		||||
    this.StaticData = new Uint8Array(); // static data contained in DB operators
 | 
			
		||||
 | 
			
		||||
    /** @type {Uint32Array} */
 | 
			
		||||
    this.InitR = new Uint32Array(7);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A pointer to bytes that have been filtered by a program.
 | 
			
		||||
     * @type {Uint8Array}
 | 
			
		||||
     */
 | 
			
		||||
    this.FilteredData = null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** @return {string} */
 | 
			
		||||
VmPreparedProgram.prototype.toString = function() {
 | 
			
		||||
    var s = "{\n  Cmd: [\n";
 | 
			
		||||
    for (var i = 0; i < this.Cmd.length; ++i) {
 | 
			
		||||
        s += this.Cmd[i].toString("  ") + ",\n";
 | 
			
		||||
    }
 | 
			
		||||
    s += "],\n";
 | 
			
		||||
    // TODO: Dump GlobalData, StaticData, InitR?
 | 
			
		||||
    s += " }\n";
 | 
			
		||||
    return s;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @struct
 | 
			
		||||
 * @constructor
 | 
			
		||||
 */
 | 
			
		||||
var UnpackFilter = function() {
 | 
			
		||||
    /** @type {number} */
 | 
			
		||||
    this.BlockStart = 0;
 | 
			
		||||
 | 
			
		||||
    /** @type {number} */
 | 
			
		||||
    this.BlockLength = 0;
 | 
			
		||||
 | 
			
		||||
    /** @type {number} */
 | 
			
		||||
    this.ExecCount = 0;
 | 
			
		||||
 | 
			
		||||
    /** @type {boolean} */
 | 
			
		||||
    this.NextWindow = false;
 | 
			
		||||
 | 
			
		||||
    // position of parent filter in Filters array used as prototype for filter
 | 
			
		||||
    // in PrgStack array. Not defined for filters in Filters array.
 | 
			
		||||
    /** @type {number} */
 | 
			
		||||
    this.ParentFilter = null;
 | 
			
		||||
 | 
			
		||||
    /** @type {VmPreparedProgram} */
 | 
			
		||||
    this.Prg = new VmPreparedProgram();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var VMCF_OP0 = 0;
 | 
			
		||||
var VMCF_OP1 = 1;
 | 
			
		||||
var VMCF_OP2 = 2;
 | 
			
		||||
var VMCF_OPMASK = 3;
 | 
			
		||||
var VMCF_BYTEMODE = 4;
 | 
			
		||||
var VMCF_JUMP = 8;
 | 
			
		||||
var VMCF_PROC = 16;
 | 
			
		||||
var VMCF_USEFLAGS = 32;
 | 
			
		||||
var VMCF_CHFLAGS = 64;
 | 
			
		||||
 | 
			
		||||
var VmCmdFlags = [
 | 
			
		||||
    /* VM_MOV   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE,
 | 
			
		||||
    /* VM_CMP   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_ADD   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_SUB   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_JZ    */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_JNZ   */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_INC   */
 | 
			
		||||
    VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_DEC   */
 | 
			
		||||
    VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_JMP   */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP,
 | 
			
		||||
    /* VM_XOR   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_AND   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_OR    */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_TEST  */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_JS    */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_JNS   */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_JB    */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_JBE   */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_JA    */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_JAE   */
 | 
			
		||||
    VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_PUSH  */
 | 
			
		||||
    VMCF_OP1,
 | 
			
		||||
    /* VM_POP   */
 | 
			
		||||
    VMCF_OP1,
 | 
			
		||||
    /* VM_CALL  */
 | 
			
		||||
    VMCF_OP1 | VMCF_PROC,
 | 
			
		||||
    /* VM_RET   */
 | 
			
		||||
    VMCF_OP0 | VMCF_PROC,
 | 
			
		||||
    /* VM_NOT   */
 | 
			
		||||
    VMCF_OP1 | VMCF_BYTEMODE,
 | 
			
		||||
    /* VM_SHL   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_SHR   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_SAR   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_NEG   */
 | 
			
		||||
    VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_PUSHA */
 | 
			
		||||
    VMCF_OP0,
 | 
			
		||||
    /* VM_POPA  */
 | 
			
		||||
    VMCF_OP0,
 | 
			
		||||
    /* VM_PUSHF */
 | 
			
		||||
    VMCF_OP0 | VMCF_USEFLAGS,
 | 
			
		||||
    /* VM_POPF  */
 | 
			
		||||
    VMCF_OP0 | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_MOVZX */
 | 
			
		||||
    VMCF_OP2,
 | 
			
		||||
    /* VM_MOVSX */
 | 
			
		||||
    VMCF_OP2,
 | 
			
		||||
    /* VM_XCHG  */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE,
 | 
			
		||||
    /* VM_MUL   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE,
 | 
			
		||||
    /* VM_DIV   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE,
 | 
			
		||||
    /* VM_ADC   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_SBB   */
 | 
			
		||||
    VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS,
 | 
			
		||||
    /* VM_PRINT */
 | 
			
		||||
    VMCF_OP0,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {number} length
 | 
			
		||||
 * @param {number} crc
 | 
			
		||||
 * @param {VmStandardFilters} type
 | 
			
		||||
 * @struct
 | 
			
		||||
 * @constructor
 | 
			
		||||
 */
 | 
			
		||||
var StandardFilterSignature = function(length, crc, type) {
 | 
			
		||||
    /** @type {number} */
 | 
			
		||||
    this.Length = length;
 | 
			
		||||
 | 
			
		||||
    /** @type {number} */
 | 
			
		||||
    this.CRC = crc;
 | 
			
		||||
 | 
			
		||||
    /** @type {VmStandardFilters} */
 | 
			
		||||
    this.Type = type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @type {Array<StandardFilterSignature>}
 | 
			
		||||
 */
 | 
			
		||||
var StdList = [
 | 
			
		||||
    new StandardFilterSignature(53, 0xad576887, VmStandardFilters.VMSF_E8),
 | 
			
		||||
    new StandardFilterSignature(57, 0x3cd7e57e, VmStandardFilters.VMSF_E8E9),
 | 
			
		||||
    new StandardFilterSignature(120, 0x3769893f, VmStandardFilters.VMSF_ITANIUM),
 | 
			
		||||
    new StandardFilterSignature(29, 0x0e06077d, VmStandardFilters.VMSF_DELTA),
 | 
			
		||||
    new StandardFilterSignature(149, 0x1c2c5dc8, VmStandardFilters.VMSF_RGB),
 | 
			
		||||
    new StandardFilterSignature(216, 0xbc85e701, VmStandardFilters.VMSF_AUDIO),
 | 
			
		||||
    new StandardFilterSignature(40, 0x46b9c560, VmStandardFilters.VMSF_UPCASE),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @constructor
 | 
			
		||||
 */
 | 
			
		||||
var RarVM = function() {
 | 
			
		||||
    /** @private {Uint8Array} */
 | 
			
		||||
    this.mem_ = null;
 | 
			
		||||
 | 
			
		||||
    /** @private {Uint32Array<number>} */
 | 
			
		||||
    this.R_ = new Uint32Array(8);
 | 
			
		||||
 | 
			
		||||
    /** @private {number} */
 | 
			
		||||
    this.flags_ = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Initializes the memory of the VM.
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.init = function() {
 | 
			
		||||
    if (!this.mem_) {
 | 
			
		||||
        this.mem_ = new Uint8Array(VM_MEMSIZE);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Uint8Array} code
 | 
			
		||||
 * @return {VmStandardFilters}
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.isStandardFilter = function(code) {
 | 
			
		||||
    var codeCRC = (CRC(0xffffffff, code, code.length) ^ 0xffffffff) >>> 0;
 | 
			
		||||
    for (var i = 0; i < StdList.length; ++i) {
 | 
			
		||||
        if (StdList[i].CRC === codeCRC && StdList[i].Length === code.length) {
 | 
			
		||||
            return StdList[i].Type;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return VmStandardFilters.VMSF_NONE;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {VmPreparedOperand} op
 | 
			
		||||
 * @param {boolean} byteMode
 | 
			
		||||
 * @param {bitjs.io.BitStream} bstream A rtl bit stream.
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.decodeArg = function(op, byteMode, bstream) {
 | 
			
		||||
    var data = bstream.peekBits(16);
 | 
			
		||||
    if (data & 0x8000) {
 | 
			
		||||
        op.Type = VmOpType.VM_OPREG; // Operand is register (R[0]..R[7])
 | 
			
		||||
        bstream.readBits(1); // 1 flag bit and...
 | 
			
		||||
        op.Data = bstream.readBits(3); // ... 3 register number bits
 | 
			
		||||
        op.Addr = [this.R_[op.Data]]; // TODO &R[Op.Data] // Register address
 | 
			
		||||
    } else {
 | 
			
		||||
        if ((data & 0xc000) === 0) {
 | 
			
		||||
            op.Type = VmOpType.VM_OPINT; // Operand is integer
 | 
			
		||||
            bstream.readBits(2); // 2 flag bits
 | 
			
		||||
            if (byteMode) {
 | 
			
		||||
                op.Data = bstream.readBits(8); // Byte integer.
 | 
			
		||||
            } else {
 | 
			
		||||
                op.Data = RarVM.readData(bstream); // 32 bit integer.
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Operand is data addressed by register data, base address or both.
 | 
			
		||||
            op.Type = VmOpType.VM_OPREGMEM;
 | 
			
		||||
            if ((data & 0x2000) === 0) {
 | 
			
		||||
                bstream.readBits(3); // 3 flag bits
 | 
			
		||||
                // Base address is zero, just use the address from register.
 | 
			
		||||
                op.Data = bstream.readBits(3); // (Data>>10)&7
 | 
			
		||||
                op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data]
 | 
			
		||||
                op.Base = 0;
 | 
			
		||||
            } else {
 | 
			
		||||
                bstream.readBits(4); // 4 flag bits
 | 
			
		||||
                if ((data & 0x1000) === 0) {
 | 
			
		||||
                    // Use both register and base address.
 | 
			
		||||
                    op.Data = bstream.readBits(3);
 | 
			
		||||
                    op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data]
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Use base address only. Access memory by fixed address.
 | 
			
		||||
                    op.Data = 0;
 | 
			
		||||
                }
 | 
			
		||||
                op.Base = RarVM.readData(bstream); // Read base address.
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {VmPreparedProgram} prg
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.execute = function(prg) {
 | 
			
		||||
    this.R_.set(prg.InitR);
 | 
			
		||||
 | 
			
		||||
    var globalSize = Math.min(prg.GlobalData.length, VM_GLOBALMEMSIZE);
 | 
			
		||||
    if (globalSize) {
 | 
			
		||||
        this.mem_.set(prg.GlobalData.subarray(0, globalSize), VM_GLOBALMEMADDR);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var staticSize = Math.min(prg.StaticData.length, VM_GLOBALMEMSIZE - globalSize);
 | 
			
		||||
    if (staticSize) {
 | 
			
		||||
        this.mem_.set(prg.StaticData.subarray(0, staticSize), VM_GLOBALMEMADDR + globalSize);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.R_[7] = VM_MEMSIZE;
 | 
			
		||||
    this.flags_ = 0;
 | 
			
		||||
 | 
			
		||||
    var preparedCodes = prg.AltCmd ? prg.AltCmd : prg.Cmd;
 | 
			
		||||
    if (prg.Cmd.length > 0 && !this.executeCode(preparedCodes)) {
 | 
			
		||||
        // Invalid VM program. Let's replace it with 'return' command.
 | 
			
		||||
        preparedCodes.OpCode = VmCommands.VM_RET;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR);
 | 
			
		||||
    var newBlockPos = dataView.getUint32(0x20, true /* little endian */ ) & VM_MEMMASK;
 | 
			
		||||
    var newBlockSize = dataView.getUint32(0x1c, true /* little endian */ ) & VM_MEMMASK;
 | 
			
		||||
    if (newBlockPos + newBlockSize >= VM_MEMSIZE) {
 | 
			
		||||
        newBlockPos = newBlockSize = 0;
 | 
			
		||||
    }
 | 
			
		||||
    prg.FilteredData = this.mem_.subarray(newBlockPos, newBlockPos + newBlockSize);
 | 
			
		||||
 | 
			
		||||
    prg.GlobalData = new Uint8Array(0);
 | 
			
		||||
 | 
			
		||||
    var dataSize = Math.min(dataView.getUint32(0x30),
 | 
			
		||||
        (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE));
 | 
			
		||||
    if (dataSize !== 0) {
 | 
			
		||||
        var len = dataSize + VM_FIXEDGLOBALSIZE;
 | 
			
		||||
        prg.GlobalData = new Uint8Array(len);
 | 
			
		||||
        prg.GlobalData.set(mem.subarray(VM_GLOBALMEMADDR, VM_GLOBALMEMADDR + len));
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Array<VmPreparedCommand>} preparedCodes
 | 
			
		||||
 * @return {boolean}
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.executeCode = function(preparedCodes) {
 | 
			
		||||
    var codeIndex = 0;
 | 
			
		||||
    var cmd = preparedCodes[codeIndex];
 | 
			
		||||
    // TODO: Why is this an infinite loop instead of just returning
 | 
			
		||||
    // when a VM_RET is hit?
 | 
			
		||||
    while (1) {
 | 
			
		||||
        switch (cmd.OpCode) {
 | 
			
		||||
            case VmCommands.VM_RET:
 | 
			
		||||
                if (this.R_[7] >= VM_MEMSIZE) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                //SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK]));
 | 
			
		||||
                this.R_[7] += 4;
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            case VmCommands.VM_STANDARD:
 | 
			
		||||
                this.executeStandardFilter(cmd.Op1.Data);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                console.error("RarVM OpCode not supported: " + getDebugString(VmCommands, cmd.OpCode));
 | 
			
		||||
                break;
 | 
			
		||||
        } // switch (cmd.OpCode)
 | 
			
		||||
        codeIndex++;
 | 
			
		||||
        cmd = preparedCodes[codeIndex];
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {number} filterType
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.executeStandardFilter = function(filterType) {
 | 
			
		||||
    switch (filterType) {
 | 
			
		||||
        case VmStandardFilters.VMSF_DELTA:
 | 
			
		||||
            var dataSize = this.R_[4];
 | 
			
		||||
            var channels = this.R_[0];
 | 
			
		||||
            var srcPos = 0;
 | 
			
		||||
            var border = dataSize * 2;
 | 
			
		||||
 | 
			
		||||
            //SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize);
 | 
			
		||||
            var dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR);
 | 
			
		||||
            dataView.setUint32(0x20, dataSize, true /* little endian */ );
 | 
			
		||||
 | 
			
		||||
            if (dataSize >= VM_GLOBALMEMADDR / 2) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Bytes from same channels are grouped to continual data blocks,
 | 
			
		||||
            // so we need to place them back to their interleaving positions.
 | 
			
		||||
            for (var curChannel = 0; curChannel < channels; ++curChannel) {
 | 
			
		||||
                var prevByte = 0;
 | 
			
		||||
                for (var destPos = dataSize + curChannel; destPos < border; destPos += channels) {
 | 
			
		||||
                    prevByte = (prevByte - this.mem_[srcPos++]) & 0xff;
 | 
			
		||||
                    this.mem_[destPos] = prevByte;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            console.error("RarVM Standard Filter not supported: " + getDebugString(VmStandardFilters, filterType));
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Uint8Array} code
 | 
			
		||||
 * @param {VmPreparedProgram} prg
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.prepare = function(code, prg) {
 | 
			
		||||
    var codeSize = code.length;
 | 
			
		||||
    var i;
 | 
			
		||||
    var curCmd;
 | 
			
		||||
 | 
			
		||||
    //InitBitInput();
 | 
			
		||||
    //memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE));
 | 
			
		||||
    var bstream = new bitjs.io.BitStream(code.buffer, true /* rtl */ );
 | 
			
		||||
 | 
			
		||||
    // Calculate the single byte XOR checksum to check validity of VM code.
 | 
			
		||||
    var xorSum = 0;
 | 
			
		||||
    for (i = 1; i < codeSize; ++i) {
 | 
			
		||||
        xorSum ^= code[i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bstream.readBits(8);
 | 
			
		||||
 | 
			
		||||
    prg.Cmd = []; // TODO: Is this right?  I don't see it being done in rarvm.cpp.
 | 
			
		||||
 | 
			
		||||
    // VM code is valid if equal.
 | 
			
		||||
    if (xorSum === code[0]) {
 | 
			
		||||
        var filterType = this.isStandardFilter(code);
 | 
			
		||||
        if (filterType !== VmStandardFilters.VMSF_NONE) {
 | 
			
		||||
            // VM code is found among standard filters.
 | 
			
		||||
            curCmd = new VmPreparedCommand();
 | 
			
		||||
            prg.Cmd.push(curCmd);
 | 
			
		||||
 | 
			
		||||
            curCmd.OpCode = VmCommands.VM_STANDARD;
 | 
			
		||||
            curCmd.Op1.Data = filterType;
 | 
			
		||||
            // TODO: Addr=&CurCmd->Op1.Data
 | 
			
		||||
            curCmd.Op1.Addr = [curCmd.Op1.Data];
 | 
			
		||||
            curCmd.Op2.Addr = [null]; // &CurCmd->Op2.Data;
 | 
			
		||||
            curCmd.Op1.Type = VmOpType.VM_OPNONE;
 | 
			
		||||
            curCmd.Op2.Type = VmOpType.VM_OPNONE;
 | 
			
		||||
            codeSize = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var dataFlag = bstream.readBits(1);
 | 
			
		||||
 | 
			
		||||
        // Read static data contained in DB operators. This data cannot be
 | 
			
		||||
        // changed, it is a part of VM code, not a filter parameter.
 | 
			
		||||
 | 
			
		||||
        if (dataFlag & 0x8000) {
 | 
			
		||||
            var dataSize = RarVM.readData(bstream) + 1;
 | 
			
		||||
            // TODO: This accesses the byte pointer of the bstream directly.  Is that ok?
 | 
			
		||||
            for (i = 0; i < bstream.bytePtr < codeSize && i < dataSize; ++i) {
 | 
			
		||||
                // Append a byte to the program's static data.
 | 
			
		||||
                var newStaticData = new Uint8Array(prg.StaticData.length + 1);
 | 
			
		||||
                newStaticData.set(prg.StaticData);
 | 
			
		||||
                newStaticData[newStaticData.length - 1] = bstream.readBits(8);
 | 
			
		||||
                prg.StaticData = newStaticData;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        while (bstream.bytePtr < codeSize) {
 | 
			
		||||
            curCmd = new VmPreparedCommand();
 | 
			
		||||
            prg.Cmd.push(curCmd); // Prg->Cmd.Add(1)
 | 
			
		||||
            var flag = bstream.peekBits(1);
 | 
			
		||||
            if (!flag) { // (Data&0x8000)==0
 | 
			
		||||
                curCmd.OpCode = bstream.readBits(4);
 | 
			
		||||
            } else {
 | 
			
		||||
                curCmd.OpCode = (bstream.readBits(6) - 24);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (VmCmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) {
 | 
			
		||||
                curCmd.ByteMode = (bstream.readBits(1) !== 0);
 | 
			
		||||
            } else {
 | 
			
		||||
                curCmd.ByteMode = 0;
 | 
			
		||||
            }
 | 
			
		||||
            curCmd.Op1.Type = VmOpType.VM_OPNONE;
 | 
			
		||||
            curCmd.Op2.Type = VmOpType.VM_OPNONE;
 | 
			
		||||
            var opNum = (VmCmdFlags[curCmd.OpCode] & VMCF_OPMASK);
 | 
			
		||||
            curCmd.Op1.Addr = null;
 | 
			
		||||
            curCmd.Op2.Addr = null;
 | 
			
		||||
            if (opNum > 0) {
 | 
			
		||||
                this.decodeArg(curCmd.Op1, curCmd.ByteMode, bstream); // reading the first operand
 | 
			
		||||
                if (opNum === 2) {
 | 
			
		||||
                    this.decodeArg(curCmd.Op2, curCmd.ByteMode, bstream); // reading the second operand
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (curCmd.Op1.Type === VmOpType.VM_OPINT && (VmCmdFlags[curCmd.OpCode] & (VMCF_JUMP | VMCF_PROC))) {
 | 
			
		||||
                        // Calculating jump distance.
 | 
			
		||||
                        var distance = curCmd.Op1.Data;
 | 
			
		||||
                        if (distance >= 256) {
 | 
			
		||||
                            distance -= 256;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (distance >= 136) {
 | 
			
		||||
                                distance -= 264;
 | 
			
		||||
                            } else {
 | 
			
		||||
                                if (distance >= 16) {
 | 
			
		||||
                                    distance -= 8;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    if (distance >= 8) {
 | 
			
		||||
                                        distance -= 16;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            distance += prg.Cmd.length;
 | 
			
		||||
                        }
 | 
			
		||||
                        curCmd.Op1.Data = distance;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } // if (OpNum>0)
 | 
			
		||||
        } // while ((uint)InAddr<CodeSize)
 | 
			
		||||
    } // if (XorSum==Code[0])
 | 
			
		||||
 | 
			
		||||
    curCmd = new VmPreparedCommand();
 | 
			
		||||
    prg.Cmd.push(curCmd);
 | 
			
		||||
    curCmd.OpCode = VmCommands.VM_RET;
 | 
			
		||||
    // TODO: Addr=&CurCmd->Op1.Data
 | 
			
		||||
    curCmd.Op1.Addr = [curCmd.Op1.Data];
 | 
			
		||||
    curCmd.Op2.Addr = [curCmd.Op2.Data];
 | 
			
		||||
    curCmd.Op1.Type = VmOpType.VM_OPNONE;
 | 
			
		||||
    curCmd.Op2.Type = VmOpType.VM_OPNONE;
 | 
			
		||||
 | 
			
		||||
    // If operand 'Addr' field has not been set by DecodeArg calls above,
 | 
			
		||||
    // let's set it to point to operand 'Data' field. It is necessary for
 | 
			
		||||
    // VM_OPINT type operands (usual integers) or maybe if something was
 | 
			
		||||
    // not set properly for other operands. 'Addr' field is required
 | 
			
		||||
    // for quicker addressing of operand data.
 | 
			
		||||
    for (i = 0; i < prg.Cmd.length; ++i) {
 | 
			
		||||
        var cmd = prg.Cmd[i];
 | 
			
		||||
        if (cmd.Op1.Addr === null) {
 | 
			
		||||
            cmd.Op1.Addr = [cmd.Op1.Data];
 | 
			
		||||
        }
 | 
			
		||||
        if (cmd.Op2.Addr === null) {
 | 
			
		||||
            cmd.Op2.Addr = [cmd.Op2.Data];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    #ifdef VM_OPTIMIZE
 | 
			
		||||
      if (CodeSize!=0)
 | 
			
		||||
        Optimize(Prg);
 | 
			
		||||
    #endif
 | 
			
		||||
      */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Uint8Array} arr The byte array to set a value in.
 | 
			
		||||
 * @param {number} value The unsigned 32-bit value to set.
 | 
			
		||||
 * @param {number} offset Offset into arr to start setting the value, defaults to 0.
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.setLowEndianValue = function(arr, value, offset) {
 | 
			
		||||
    var i = offset || 0;
 | 
			
		||||
    arr[i] = value & 0xff;
 | 
			
		||||
    arr[i + 1] = (value >>> 8) & 0xff;
 | 
			
		||||
    arr[i + 2] = (value >>> 16) & 0xff;
 | 
			
		||||
    arr[i + 3] = (value >>> 24) & 0xff;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sets a number of bytes of the VM memory at the given position from a
 | 
			
		||||
 * source buffer of bytes.
 | 
			
		||||
 * @param {number} pos The position in the VM memory to start writing to.
 | 
			
		||||
 * @param {Uint8Array} buffer The source buffer of bytes.
 | 
			
		||||
 * @param {number} dataSize The number of bytes to set.
 | 
			
		||||
 */
 | 
			
		||||
RarVM.prototype.setMemory = function(pos, buffer, dataSize) {
 | 
			
		||||
    if (pos < VM_MEMSIZE) {
 | 
			
		||||
        var numBytes = Math.min(dataSize, VM_MEMSIZE - pos);
 | 
			
		||||
        for (var i = 0; i < numBytes; ++i) {
 | 
			
		||||
            this.mem_[pos + i] = buffer[i];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Static function that reads in the next set of bits for the VM
 | 
			
		||||
 * (might return 4, 8, 16 or 32 bits).
 | 
			
		||||
 * @param {bitjs.io.BitStream} bstream A RTL bit stream.
 | 
			
		||||
 * @return {number} The value of the bits read.
 | 
			
		||||
 */
 | 
			
		||||
RarVM.readData = function(bstream) {
 | 
			
		||||
    // Read in the first 2 bits.
 | 
			
		||||
    var flags = bstream.readBits(2);
 | 
			
		||||
    switch (flags) { // Data&0xc000
 | 
			
		||||
        // Return the next 4 bits.
 | 
			
		||||
        case 0:
 | 
			
		||||
            return bstream.readBits(4); // (Data>>10)&0xf
 | 
			
		||||
 | 
			
		||||
        case 1: // 0x4000
 | 
			
		||||
            // 0x3c00 => 0011 1100 0000 0000
 | 
			
		||||
            if (bstream.peekBits(4) === 0) { // (Data&0x3c00)==0
 | 
			
		||||
                // Skip the 4 zero bits.
 | 
			
		||||
                bstream.readBits(4);
 | 
			
		||||
                // Read in the next 8 and pad with 1s to 32 bits.
 | 
			
		||||
                return (0xffffff00 | bstream.readBits(8)) >>> 0; // ((Data>>2)&0xff)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Else, read in the next 8.
 | 
			
		||||
            return bstream.readBits(8);
 | 
			
		||||
 | 
			
		||||
            // Read in the next 16.
 | 
			
		||||
        case 2: // 0x8000
 | 
			
		||||
            var val = bstream.getBits();
 | 
			
		||||
            bstream.readBits(16);
 | 
			
		||||
            return val; //bstream.readBits(16);
 | 
			
		||||
 | 
			
		||||
            // case 3
 | 
			
		||||
        default:
 | 
			
		||||
            return (bstream.readBits(16) << 16) | bstream.readBits(16);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// ============================================================================================== //
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,179 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * untar.js
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the MIT License
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright(c) 2011 Google Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * Reference Documentation:
 | 
			
		||||
 *
 | 
			
		||||
 * TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* global bitjs, importScripts, Uint8Array */
 | 
			
		||||
 | 
			
		||||
// This file expects to be invoked as a Worker (see onmessage below).
 | 
			
		||||
importScripts("../io/bytestream.js");
 | 
			
		||||
importScripts("archive.js");
 | 
			
		||||
 | 
			
		||||
// Progress variables.
 | 
			
		||||
var currentFilename = "";
 | 
			
		||||
var currentFileNumber = 0;
 | 
			
		||||
var currentBytesUnarchivedInFile = 0;
 | 
			
		||||
var currentBytesUnarchived = 0;
 | 
			
		||||
var totalUncompressedBytesInArchive = 0;
 | 
			
		||||
var totalFilesInArchive = 0;
 | 
			
		||||
var allLocalFiles = [];
 | 
			
		||||
 | 
			
		||||
// Helper functions.
 | 
			
		||||
var info = function(str) {
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
 | 
			
		||||
};
 | 
			
		||||
var err = function(str) {
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Removes all characters from the first zero-byte in the string onwards.
 | 
			
		||||
var readCleanString = function(bstr, numBytes) {
 | 
			
		||||
    var str = bstr.readString(numBytes);
 | 
			
		||||
    var zIndex = str.indexOf(String.fromCharCode(0));
 | 
			
		||||
    return zIndex != -1 ? str.substr(0, zIndex) : str;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var postProgress = function() {
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveProgressEvent(
 | 
			
		||||
        currentFilename,
 | 
			
		||||
        currentFileNumber,
 | 
			
		||||
        currentBytesUnarchivedInFile,
 | 
			
		||||
        currentBytesUnarchived,
 | 
			
		||||
        totalUncompressedBytesInArchive,
 | 
			
		||||
        totalFilesInArchive
 | 
			
		||||
    ));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// takes a ByteStream and parses out the local file information
 | 
			
		||||
var TarLocalFile = function(bstream) {
 | 
			
		||||
    this.isValid = false;
 | 
			
		||||
 | 
			
		||||
    var bytesRead = 0;
 | 
			
		||||
 | 
			
		||||
    // Read in the header block
 | 
			
		||||
    this.name = readCleanString(bstream, 100);
 | 
			
		||||
    this.mode = readCleanString(bstream, 8);
 | 
			
		||||
    this.uid = readCleanString(bstream, 8);
 | 
			
		||||
    this.gid = readCleanString(bstream, 8);
 | 
			
		||||
    this.size = parseInt(readCleanString(bstream, 12), 8);
 | 
			
		||||
    this.mtime = readCleanString(bstream, 12);
 | 
			
		||||
    this.chksum = readCleanString(bstream, 8);
 | 
			
		||||
    this.typeflag = readCleanString(bstream, 1);
 | 
			
		||||
    this.linkname = readCleanString(bstream, 100);
 | 
			
		||||
    this.maybeMagic = readCleanString(bstream, 6);
 | 
			
		||||
 | 
			
		||||
    if (this.maybeMagic === "ustar") {
 | 
			
		||||
        this.version = readCleanString(bstream, 2);
 | 
			
		||||
        this.uname = readCleanString(bstream, 32);
 | 
			
		||||
        this.gname = readCleanString(bstream, 32);
 | 
			
		||||
        this.devmajor = readCleanString(bstream, 8);
 | 
			
		||||
        this.devminor = readCleanString(bstream, 8);
 | 
			
		||||
        this.prefix = readCleanString(bstream, 155);
 | 
			
		||||
 | 
			
		||||
        if (this.prefix.length) {
 | 
			
		||||
            this.name = this.prefix + this.name;
 | 
			
		||||
        }
 | 
			
		||||
        bstream.readBytes(12); // 512 - 500
 | 
			
		||||
    } else {
 | 
			
		||||
        bstream.readBytes(255); // 512 - 257
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bytesRead += 512;
 | 
			
		||||
 | 
			
		||||
    // Done header, now rest of blocks are the file contents.
 | 
			
		||||
    this.filename = this.name;
 | 
			
		||||
    this.fileData = null;
 | 
			
		||||
 | 
			
		||||
    info("Untarring file '" + this.filename + "'");
 | 
			
		||||
    info("  size = " + this.size);
 | 
			
		||||
    info("  typeflag = " + this.typeflag);
 | 
			
		||||
 | 
			
		||||
    // A regular file.
 | 
			
		||||
    if (this.typeflag == 0) {
 | 
			
		||||
        info("  This is a regular file.");
 | 
			
		||||
        var sizeInBytes = parseInt(this.size);
 | 
			
		||||
        this.fileData = new Uint8Array(bstream.readBytes(sizeInBytes));
 | 
			
		||||
        bytesRead += sizeInBytes;
 | 
			
		||||
        if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) {
 | 
			
		||||
            this.isValid = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Round up to 512-byte blocks.
 | 
			
		||||
        var remaining = 512 - (bytesRead % 512);
 | 
			
		||||
        if (remaining > 0 && remaining < 512) {
 | 
			
		||||
            bstream.readBytes(remaining);
 | 
			
		||||
        }
 | 
			
		||||
    } else if (this.typeflag == 5) {
 | 
			
		||||
        info("  This is a directory.");
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var untar = function(arrayBuffer) {
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveStartEvent());
 | 
			
		||||
    currentFilename = "";
 | 
			
		||||
    currentFileNumber = 0;
 | 
			
		||||
    currentBytesUnarchivedInFile = 0;
 | 
			
		||||
    currentBytesUnarchived = 0;
 | 
			
		||||
    totalUncompressedBytesInArchive = 0;
 | 
			
		||||
    totalFilesInArchive = 0;
 | 
			
		||||
    allLocalFiles = [];
 | 
			
		||||
 | 
			
		||||
    var bstream = new bitjs.io.ByteStream(arrayBuffer);
 | 
			
		||||
    postProgress();
 | 
			
		||||
    /*
 | 
			
		||||
    // go through whole file, read header of each block and memorize, filepointer
 | 
			
		||||
    */
 | 
			
		||||
    while (bstream.peekNumber(4) !== 0) {
 | 
			
		||||
        var localFile = new TarLocalFile(bstream);
 | 
			
		||||
        allLocalFiles.push(localFile);
 | 
			
		||||
        postProgress();
 | 
			
		||||
    }
 | 
			
		||||
    // got all local files, now sort them
 | 
			
		||||
    allLocalFiles.sort(alphanumCase);
 | 
			
		||||
 | 
			
		||||
    allLocalFiles.forEach(function(oneLocalFile) {
 | 
			
		||||
        // While we don't encounter an empty block, keep making TarLocalFiles.
 | 
			
		||||
        if (oneLocalFile && oneLocalFile.isValid) {
 | 
			
		||||
            // If we make it to this point and haven't thrown an error, we have successfully
 | 
			
		||||
            // read in the data for a local file, so we can update the actual bytestream.
 | 
			
		||||
            totalUncompressedBytesInArchive += oneLocalFile.size;
 | 
			
		||||
 | 
			
		||||
            // update progress
 | 
			
		||||
            currentFilename = oneLocalFile.filename;
 | 
			
		||||
            currentFileNumber = totalFilesInArchive++;
 | 
			
		||||
            currentBytesUnarchivedInFile = oneLocalFile.size;
 | 
			
		||||
            currentBytesUnarchived += oneLocalFile.size;
 | 
			
		||||
            postMessage(new bitjs.archive.UnarchiveExtractEvent(oneLocalFile));
 | 
			
		||||
            postProgress();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    totalFilesInArchive = allLocalFiles.length;
 | 
			
		||||
 | 
			
		||||
    postProgress();
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveFinishEvent());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// event.data.file has the first ArrayBuffer.
 | 
			
		||||
// event.data.bytes has all subsequent ArrayBuffers.
 | 
			
		||||
onmessage = function(event) {
 | 
			
		||||
    try {
 | 
			
		||||
        untar(event.data.file, true);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        if (typeof e === "string" && e.startsWith("Error!  Overflowed")) {
 | 
			
		||||
            // Overrun the buffer.
 | 
			
		||||
            // unarchiveState = UnarchiveState.WAITING;
 | 
			
		||||
        } else {
 | 
			
		||||
            err("Found an error while untarring");
 | 
			
		||||
            err(e);
 | 
			
		||||
            throw e;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,660 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * unzip.js
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the MIT License
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright(c) 2011 Google Inc.
 | 
			
		||||
 * Copyright(c) 2011 antimatter15
 | 
			
		||||
 *
 | 
			
		||||
 * Reference Documentation:
 | 
			
		||||
 *
 | 
			
		||||
 * ZIP format: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
 | 
			
		||||
 * DEFLATE format: http://tools.ietf.org/html/rfc1951
 | 
			
		||||
 */
 | 
			
		||||
/* global bitjs, importScripts, Uint8Array*/
 | 
			
		||||
 | 
			
		||||
// This file expects to be invoked as a Worker (see onmessage below).
 | 
			
		||||
importScripts("../io/bitstream.js");
 | 
			
		||||
importScripts("../io/bytebuffer.js");
 | 
			
		||||
importScripts("../io/bytestream.js");
 | 
			
		||||
importScripts("archive.js");
 | 
			
		||||
 | 
			
		||||
// Progress variables.
 | 
			
		||||
var currentFilename = "";
 | 
			
		||||
var currentFileNumber = 0;
 | 
			
		||||
var currentBytesUnarchivedInFile = 0;
 | 
			
		||||
var currentBytesUnarchived = 0;
 | 
			
		||||
var totalUncompressedBytesInArchive = 0;
 | 
			
		||||
var totalFilesInArchive = 0;
 | 
			
		||||
 | 
			
		||||
// Helper functions.
 | 
			
		||||
var info = function(str) {
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
 | 
			
		||||
};
 | 
			
		||||
var err = function(str) {
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
 | 
			
		||||
};
 | 
			
		||||
var postProgress = function() {
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveProgressEvent(
 | 
			
		||||
        currentFilename,
 | 
			
		||||
        currentFileNumber,
 | 
			
		||||
        currentBytesUnarchivedInFile,
 | 
			
		||||
        currentBytesUnarchived,
 | 
			
		||||
        totalUncompressedBytesInArchive,
 | 
			
		||||
        totalFilesInArchive));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var zLocalFileHeaderSignature = 0x04034b50;
 | 
			
		||||
var zArchiveExtraDataSignature = 0x08064b50;
 | 
			
		||||
var zCentralFileHeaderSignature = 0x02014b50;
 | 
			
		||||
var zDigitalSignatureSignature = 0x05054b50;
 | 
			
		||||
 | 
			
		||||
// takes a ByteStream and parses out the local file information
 | 
			
		||||
var ZipLocalFile = function(bstream) {
 | 
			
		||||
    if (typeof bstream !== typeof {} || !bstream.readNumber || typeof bstream.readNumber !== typeof function() {}) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bstream.readNumber(4); // swallow signature
 | 
			
		||||
    this.version = bstream.readNumber(2);
 | 
			
		||||
    this.generalPurpose = bstream.readNumber(2);
 | 
			
		||||
    this.compressionMethod = bstream.readNumber(2);
 | 
			
		||||
    this.lastModFileTime = bstream.readNumber(2);
 | 
			
		||||
    this.lastModFileDate = bstream.readNumber(2);
 | 
			
		||||
    this.crc32 = bstream.readNumber(4);
 | 
			
		||||
    this.compressedSize = bstream.readNumber(4);
 | 
			
		||||
    this.uncompressedSize = bstream.readNumber(4);
 | 
			
		||||
    this.fileNameLength = bstream.readNumber(2);
 | 
			
		||||
    this.extraFieldLength = bstream.readNumber(2);
 | 
			
		||||
 | 
			
		||||
    this.filename = null;
 | 
			
		||||
    if (this.fileNameLength > 0) {
 | 
			
		||||
        this.filename = bstream.readString(this.fileNameLength);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.extraField = null;
 | 
			
		||||
    if (this.extraFieldLength > 0) {
 | 
			
		||||
        this.extraField = bstream.readString(this.extraFieldLength);
 | 
			
		||||
        info(" extra field=" + this.extraField);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // read in the compressed data
 | 
			
		||||
    this.fileData = null;
 | 
			
		||||
    if (this.compressedSize > 0) {
 | 
			
		||||
        this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.compressedSize);
 | 
			
		||||
        bstream.ptr += this.compressedSize;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: deal with data descriptor if present (we currently assume no data descriptor!)
 | 
			
		||||
    // "This descriptor exists only if bit 3 of the general purpose bit flag is set"
 | 
			
		||||
    // But how do you figure out how big the file data is if you don't know the compressedSize
 | 
			
		||||
    // from the header?!?
 | 
			
		||||
    if ((this.generalPurpose & bitjs.BIT[3]) !== 0) {
 | 
			
		||||
        this.crc32 = bstream.readNumber(4);
 | 
			
		||||
        this.compressedSize = bstream.readNumber(4);
 | 
			
		||||
        this.uncompressedSize = bstream.readNumber(4);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Now that we have all the bytes for this file, we can print out some information.
 | 
			
		||||
    info("Zip Local File Header:");
 | 
			
		||||
    info(" version=" + this.version);
 | 
			
		||||
    info(" general purpose=" + this.generalPurpose);
 | 
			
		||||
    info(" compression method=" + this.compressionMethod);
 | 
			
		||||
    info(" last mod file time=" + this.lastModFileTime);
 | 
			
		||||
    info(" last mod file date=" + this.lastModFileDate);
 | 
			
		||||
    info(" crc32=" + this.crc32);
 | 
			
		||||
    info(" compressed size=" + this.compressedSize);
 | 
			
		||||
    info(" uncompressed size=" + this.uncompressedSize);
 | 
			
		||||
    info(" file name length=" + this.fileNameLength);
 | 
			
		||||
    info(" extra field length=" + this.extraFieldLength);
 | 
			
		||||
    info(" filename = '" + this.filename + "'");
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// determine what kind of compressed data we have and decompress
 | 
			
		||||
ZipLocalFile.prototype.unzip = function() {
 | 
			
		||||
 | 
			
		||||
    // Zip Version 1.0, no compression (store only)
 | 
			
		||||
    if (this.compressionMethod === 0 ) {
 | 
			
		||||
        info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)");
 | 
			
		||||
        currentBytesUnarchivedInFile = this.compressedSize;
 | 
			
		||||
        currentBytesUnarchived += this.compressedSize;
 | 
			
		||||
        this.fileData = zeroCompression(this.fileData, this.uncompressedSize);
 | 
			
		||||
    } else if (this.compressionMethod === 8) {
 | 
			
		||||
        // version == 20, compression method == 8 (DEFLATE)
 | 
			
		||||
        info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)");
 | 
			
		||||
        this.fileData = inflate(this.fileData, this.uncompressedSize);
 | 
			
		||||
    } else {
 | 
			
		||||
        err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)");
 | 
			
		||||
        this.fileData = null;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Takes an ArrayBuffer of a zip file in
 | 
			
		||||
// returns null on error
 | 
			
		||||
// returns an array of DecompressedFile objects on success
 | 
			
		||||
// ToDo This function differs
 | 
			
		||||
var unzip = function(arrayBuffer) {
 | 
			
		||||
    postMessage(new bitjs.archive.UnarchiveStartEvent());
 | 
			
		||||
 | 
			
		||||
    currentFilename = "";
 | 
			
		||||
    currentFileNumber = 0;
 | 
			
		||||
    currentBytesUnarchivedInFile = 0;
 | 
			
		||||
    currentBytesUnarchived = 0;
 | 
			
		||||
    totalUncompressedBytesInArchive = 0;
 | 
			
		||||
    totalFilesInArchive = 0;
 | 
			
		||||
    currentBytesUnarchived = 0;
 | 
			
		||||
 | 
			
		||||
    var bstream = new bitjs.io.ByteStream(arrayBuffer);
 | 
			
		||||
    // detect local file header signature or return null
 | 
			
		||||
    if (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
 | 
			
		||||
        var localFiles = [];
 | 
			
		||||
        // loop until we don't see any more local files
 | 
			
		||||
        while (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
 | 
			
		||||
            var oneLocalFile = new ZipLocalFile(bstream);
 | 
			
		||||
            // this should strip out directories/folders
 | 
			
		||||
            if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) {
 | 
			
		||||
                localFiles.push(oneLocalFile);
 | 
			
		||||
                totalUncompressedBytesInArchive += oneLocalFile.uncompressedSize;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        totalFilesInArchive = localFiles.length;
 | 
			
		||||
 | 
			
		||||
        // got all local files, now sort them
 | 
			
		||||
        localFiles.sort(alphanumCase);
 | 
			
		||||
 | 
			
		||||
        // archive extra data record
 | 
			
		||||
        if (bstream.peekNumber(4) === zArchiveExtraDataSignature) {
 | 
			
		||||
            info(" Found an Archive Extra Data Signature");
 | 
			
		||||
 | 
			
		||||
            // skipping this record for now
 | 
			
		||||
            bstream.readNumber(4);
 | 
			
		||||
            var archiveExtraFieldLength = bstream.readNumber(4);
 | 
			
		||||
            bstream.readString(archiveExtraFieldLength);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // central directory structure
 | 
			
		||||
        // TODO: handle the rest of the structures (Zip64 stuff)
 | 
			
		||||
        if (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
 | 
			
		||||
            info(" Found a Central File Header");
 | 
			
		||||
 | 
			
		||||
            // read all file headers
 | 
			
		||||
            while (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
 | 
			
		||||
                bstream.readNumber(4); // signature
 | 
			
		||||
                bstream.readNumber(2); // version made by
 | 
			
		||||
                bstream.readNumber(2); // version needed to extract
 | 
			
		||||
                bstream.readNumber(2); // general purpose bit flag
 | 
			
		||||
                bstream.readNumber(2); // compression method
 | 
			
		||||
                bstream.readNumber(2); // last mod file time
 | 
			
		||||
                bstream.readNumber(2); // last mod file date
 | 
			
		||||
                bstream.readNumber(4); // crc32
 | 
			
		||||
                bstream.readNumber(4); // compressed size
 | 
			
		||||
                bstream.readNumber(4); // uncompressed size
 | 
			
		||||
                var fileNameLength = bstream.readNumber(2); // file name length
 | 
			
		||||
                var extraFieldLength = bstream.readNumber(2); // extra field length
 | 
			
		||||
                var fileCommentLength = bstream.readNumber(2); // file comment length
 | 
			
		||||
                bstream.readNumber(2); // disk number start
 | 
			
		||||
                bstream.readNumber(2); // internal file attributes
 | 
			
		||||
                bstream.readNumber(4); // external file attributes
 | 
			
		||||
                bstream.readNumber(4); // relative offset of local header
 | 
			
		||||
 | 
			
		||||
                bstream.readString(fileNameLength); // file name
 | 
			
		||||
                bstream.readString(extraFieldLength); // extra field
 | 
			
		||||
                bstream.readString(fileCommentLength); // file comment
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // digital signature
 | 
			
		||||
        if (bstream.peekNumber(4) === zDigitalSignatureSignature) {
 | 
			
		||||
            info(" Found a Digital Signature");
 | 
			
		||||
 | 
			
		||||
            bstream.readNumber(4);
 | 
			
		||||
            var sizeOfSignature = bstream.readNumber(2);
 | 
			
		||||
            bstream.readString(sizeOfSignature); // digital signature data
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // report # files and total length
 | 
			
		||||
        if (localFiles.length > 0) {
 | 
			
		||||
            postProgress();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // now do the unzipping of each file
 | 
			
		||||
        for (var i = 0; i < localFiles.length; ++i) {
 | 
			
		||||
            var localfile = localFiles[i];
 | 
			
		||||
 | 
			
		||||
            // update progress
 | 
			
		||||
            currentFilename = localfile.filename;
 | 
			
		||||
            currentFileNumber = i;
 | 
			
		||||
            currentBytesUnarchivedInFile = 0;
 | 
			
		||||
 | 
			
		||||
            // actually do the unzipping
 | 
			
		||||
            localfile.unzip();
 | 
			
		||||
 | 
			
		||||
            if (localfile.fileData !== null) {
 | 
			
		||||
                postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
 | 
			
		||||
                postProgress();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        postProgress();
 | 
			
		||||
        postMessage(new bitjs.archive.UnarchiveFinishEvent());
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// returns a table of Huffman codes
 | 
			
		||||
// each entry's index is its code and its value is a JavaScript object
 | 
			
		||||
// containing {length: 6, symbol: X}
 | 
			
		||||
function getHuffmanCodes(bitLengths) {
 | 
			
		||||
    // ensure bitLengths is an array containing at least one element
 | 
			
		||||
    if (typeof bitLengths !== typeof [] || bitLengths.length < 1) {
 | 
			
		||||
        err("Error! getHuffmanCodes() called with an invalid array");
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Reference: http://tools.ietf.org/html/rfc1951#page-8
 | 
			
		||||
    var numLengths = bitLengths.length;
 | 
			
		||||
    var blCount = [];
 | 
			
		||||
    var MAX_BITS = 1;
 | 
			
		||||
 | 
			
		||||
    // Step 1: count up how many codes of each length we have
 | 
			
		||||
    for (var i = 0; i < numLengths; ++i) {
 | 
			
		||||
        var length = bitLengths[i];
 | 
			
		||||
        // test to ensure each bit length is a positive, non-zero number
 | 
			
		||||
        if (typeof length !== typeof 1 || length < 0) {
 | 
			
		||||
            err("bitLengths contained an invalid number in getHuffmanCodes(): " + length + " of type " + (typeof length));
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        // increment the appropriate bitlength count
 | 
			
		||||
        if (typeof blCount[length] === "undefined") blCount[length] = 0;
 | 
			
		||||
        // a length of zero means this symbol is not participating in the huffman coding
 | 
			
		||||
        if (length > 0) blCount[length]++;
 | 
			
		||||
 | 
			
		||||
        if (length > MAX_BITS) MAX_BITS = length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Step 2: Find the numerical value of the smallest code for each code length
 | 
			
		||||
    var nextCode = [];
 | 
			
		||||
    var code = 0;
 | 
			
		||||
    for (var bits = 1; bits <= MAX_BITS; ++bits) {
 | 
			
		||||
        var length2 = bits - 1;
 | 
			
		||||
        // ensure undefined lengths are zero
 | 
			
		||||
        if (typeof blCount[length2] === "undefined") blCount[length2] = 0;
 | 
			
		||||
        code = (code + blCount[bits - 1]) << 1;
 | 
			
		||||
        nextCode [bits] = code;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Step 3: Assign numerical values to all codes
 | 
			
		||||
    var table = {};
 | 
			
		||||
    var tableLength = 0;
 | 
			
		||||
    for (var n = 0; n < numLengths; ++n) {
 | 
			
		||||
        var len = bitLengths[n];
 | 
			
		||||
        if (len !== 0) {
 | 
			
		||||
            table[nextCode [len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(nextCode [len],len) };
 | 
			
		||||
            tableLength++;
 | 
			
		||||
            nextCode [len]++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    table.maxLength = tableLength;
 | 
			
		||||
 | 
			
		||||
    return table;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
     The Huffman codes for the two alphabets are fixed, and are not
 | 
			
		||||
     represented explicitly in the data.  The Huffman code lengths
 | 
			
		||||
     for the literal/length alphabet are:
 | 
			
		||||
 | 
			
		||||
               Lit Value    Bits        Codes
 | 
			
		||||
               ---------    ----        -----
 | 
			
		||||
                 0 - 143     8          00110000 through
 | 
			
		||||
                                        10111111
 | 
			
		||||
               144 - 255     9          110010000 through
 | 
			
		||||
                                        111111111
 | 
			
		||||
               256 - 279     7          0000000 through
 | 
			
		||||
                                        0010111
 | 
			
		||||
               280 - 287     8          11000000 through
 | 
			
		||||
                                        11000111
 | 
			
		||||
*/
 | 
			
		||||
// fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits
 | 
			
		||||
var fixedHCtoLiteral = null;
 | 
			
		||||
var fixedHCtoDistance = null;
 | 
			
		||||
 | 
			
		||||
function getFixedLiteralTable() {
 | 
			
		||||
    // create once
 | 
			
		||||
    if (!fixedHCtoLiteral) {
 | 
			
		||||
        var bitlengths = new Array(288);
 | 
			
		||||
        var i;
 | 
			
		||||
        for (i = 0; i <= 143; ++i) bitlengths[i] = 8;
 | 
			
		||||
        for (i = 144; i <= 255; ++i) bitlengths[i] = 9;
 | 
			
		||||
        for (i = 256; i <= 279; ++i) bitlengths[i] = 7;
 | 
			
		||||
        for (i = 280; i <= 287; ++i) bitlengths[i] = 8;
 | 
			
		||||
 | 
			
		||||
        // get huffman code table
 | 
			
		||||
        fixedHCtoLiteral = getHuffmanCodes(bitlengths);
 | 
			
		||||
    }
 | 
			
		||||
    return fixedHCtoLiteral;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getFixedDistanceTable() {
 | 
			
		||||
    // create once
 | 
			
		||||
    if (!fixedHCtoDistance) {
 | 
			
		||||
        var bitlengths = new Array(32);
 | 
			
		||||
        for (var i = 0; i < 32; ++i) {
 | 
			
		||||
            bitlengths[i] = 5;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // get huffman code table
 | 
			
		||||
        fixedHCtoDistance = getHuffmanCodes(bitlengths);
 | 
			
		||||
    }
 | 
			
		||||
    return fixedHCtoDistance;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// extract one bit at a time until we find a matching Huffman Code
 | 
			
		||||
// then return that symbol
 | 
			
		||||
function decodeSymbol(bstream, hcTable) {
 | 
			
		||||
    var code = 0;
 | 
			
		||||
    var len = 0;
 | 
			
		||||
 | 
			
		||||
    // loop until we match
 | 
			
		||||
    for (;;) {
 | 
			
		||||
        // read in next bit
 | 
			
		||||
        var bit = bstream.readBits(1);
 | 
			
		||||
        code = (code << 1) | bit;
 | 
			
		||||
        ++len;
 | 
			
		||||
 | 
			
		||||
        // check against Huffman Code table and break if found
 | 
			
		||||
        if (hcTable.hasOwnProperty(code) && hcTable[code].length === len) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if (len > hcTable.maxLength) {
 | 
			
		||||
            err("Bit stream out of sync, didn't find a Huffman Code, length was " + len +
 | 
			
		||||
                " and table only max code length of " + hcTable.maxLength);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return hcTable[code].symbol;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var CodeLengthCodeOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
 | 
			
		||||
/*
 | 
			
		||||
     Extra               Extra               Extra
 | 
			
		||||
Code Bits Length(s) Code Bits Lengths   Code Bits Length(s)
 | 
			
		||||
---- ---- ------     ---- ---- -------   ---- ---- -------
 | 
			
		||||
 257   0     3       267   1   15,16     277   4   67-82
 | 
			
		||||
 258   0     4       268   1   17,18     278   4   83-98
 | 
			
		||||
 259   0     5       269   2   19-22     279   4   99-114
 | 
			
		||||
 260   0     6       270   2   23-26     280   4  115-130
 | 
			
		||||
 261   0     7       271   2   27-30     281   5  131-162
 | 
			
		||||
 262   0     8       272   2   31-34     282   5  163-194
 | 
			
		||||
 263   0     9       273   3   35-42     283   5  195-226
 | 
			
		||||
 264   0    10       274   3   43-50     284   5  227-257
 | 
			
		||||
 265   1  11,12      275   3   51-58     285   0    258
 | 
			
		||||
 266   1  13,14      276   3   59-66
 | 
			
		||||
*/
 | 
			
		||||
var LengthLookupTable = [
 | 
			
		||||
    [0, 3],
 | 
			
		||||
    [0, 4],
 | 
			
		||||
    [0, 5],
 | 
			
		||||
    [0, 6],
 | 
			
		||||
    [0, 7],
 | 
			
		||||
    [0, 8],
 | 
			
		||||
    [0, 9],
 | 
			
		||||
    [0, 10],
 | 
			
		||||
    [1, 11],
 | 
			
		||||
    [1, 13],
 | 
			
		||||
    [1, 15],
 | 
			
		||||
    [1, 17],
 | 
			
		||||
    [2, 19],
 | 
			
		||||
    [2, 23],
 | 
			
		||||
    [2, 27],
 | 
			
		||||
    [2, 31],
 | 
			
		||||
    [3, 35],
 | 
			
		||||
    [3, 43],
 | 
			
		||||
    [3, 51],
 | 
			
		||||
    [3, 59],
 | 
			
		||||
    [4, 67],
 | 
			
		||||
    [4, 83],
 | 
			
		||||
    [4, 99],
 | 
			
		||||
    [4, 115],
 | 
			
		||||
    [5, 131],
 | 
			
		||||
    [5, 163],
 | 
			
		||||
    [5, 195],
 | 
			
		||||
    [5, 227],
 | 
			
		||||
    [0, 258]
 | 
			
		||||
];
 | 
			
		||||
/*
 | 
			
		||||
      Extra           Extra                Extra
 | 
			
		||||
 Code Bits Dist  Code Bits   Dist     Code Bits Distance
 | 
			
		||||
 ---- ---- ----  ---- ----  ------    ---- ---- --------
 | 
			
		||||
   0   0    1     10   4     33-48    20    9   1025-1536
 | 
			
		||||
   1   0    2     11   4     49-64    21    9   1537-2048
 | 
			
		||||
   2   0    3     12   5     65-96    22   10   2049-3072
 | 
			
		||||
   3   0    4     13   5     97-128   23   10   3073-4096
 | 
			
		||||
   4   1   5,6    14   6    129-192   24   11   4097-6144
 | 
			
		||||
   5   1   7,8    15   6    193-256   25   11   6145-8192
 | 
			
		||||
   6   2   9-12   16   7    257-384   26   12  8193-12288
 | 
			
		||||
   7   2  13-16   17   7    385-512   27   12 12289-16384
 | 
			
		||||
   8   3  17-24   18   8    513-768   28   13 16385-24576
 | 
			
		||||
   9   3  25-32   19   8   769-1024   29   13 24577-32768
 | 
			
		||||
*/
 | 
			
		||||
var DistLookupTable = [
 | 
			
		||||
    [0, 1],
 | 
			
		||||
    [0, 2],
 | 
			
		||||
    [0, 3],
 | 
			
		||||
    [0, 4],
 | 
			
		||||
    [1, 5],
 | 
			
		||||
    [1, 7],
 | 
			
		||||
    [2, 9],
 | 
			
		||||
    [2, 13],
 | 
			
		||||
    [3, 17],
 | 
			
		||||
    [3, 25],
 | 
			
		||||
    [4, 33],
 | 
			
		||||
    [4, 49],
 | 
			
		||||
    [5, 65],
 | 
			
		||||
    [5, 97],
 | 
			
		||||
    [6, 129],
 | 
			
		||||
    [6, 193],
 | 
			
		||||
    [7, 257],
 | 
			
		||||
    [7, 385],
 | 
			
		||||
    [8, 513],
 | 
			
		||||
    [8, 769],
 | 
			
		||||
    [9, 1025],
 | 
			
		||||
    [9, 1537],
 | 
			
		||||
    [10, 2049],
 | 
			
		||||
    [10, 3073],
 | 
			
		||||
    [11, 4097],
 | 
			
		||||
    [11, 6145],
 | 
			
		||||
    [12, 8193],
 | 
			
		||||
    [12, 12289],
 | 
			
		||||
    [13, 16385],
 | 
			
		||||
    [13, 24577]
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
 | 
			
		||||
    /*
 | 
			
		||||
          loop (until end of block code recognized)
 | 
			
		||||
             decode literal/length value from input stream
 | 
			
		||||
             if value < 256
 | 
			
		||||
                copy value (literal byte) to output stream
 | 
			
		||||
             otherwise
 | 
			
		||||
                if value = end of block (256)
 | 
			
		||||
                   break from loop
 | 
			
		||||
                otherwise (value = 257..285)
 | 
			
		||||
                   decode distance from input stream
 | 
			
		||||
 | 
			
		||||
                   move backwards distance bytes in the output
 | 
			
		||||
                   stream, and copy length bytes from this
 | 
			
		||||
                   position to the output stream.
 | 
			
		||||
    */
 | 
			
		||||
    var blockSize = 0;
 | 
			
		||||
    for (;;) {
 | 
			
		||||
        var symbol = decodeSymbol(bstream, hcLiteralTable);
 | 
			
		||||
        if (symbol < 256) {
 | 
			
		||||
            // copy literal byte to output
 | 
			
		||||
            buffer.insertByte(symbol);
 | 
			
		||||
            blockSize++;
 | 
			
		||||
        } else {
 | 
			
		||||
            // end of block reached
 | 
			
		||||
            if (symbol === 256) {
 | 
			
		||||
                break;
 | 
			
		||||
            } else {
 | 
			
		||||
                var lengthLookup = LengthLookupTable[symbol - 257];
 | 
			
		||||
                var length = lengthLookup[1] + bstream.readBits(lengthLookup[0]);
 | 
			
		||||
                var distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)];
 | 
			
		||||
                var distance = distLookup[1] + bstream.readBits(distLookup[0]);
 | 
			
		||||
 | 
			
		||||
                // now apply length and distance appropriately and copy to output
 | 
			
		||||
 | 
			
		||||
                // TODO: check that backward distance < data.length?
 | 
			
		||||
 | 
			
		||||
                // http://tools.ietf.org/html/rfc1951#page-11
 | 
			
		||||
                // "Note also that the referenced string may overlap the current
 | 
			
		||||
                //  position; for example, if the last 2 bytes decoded have values
 | 
			
		||||
                //  X and Y, a string reference with <length = 5, distance = 2>
 | 
			
		||||
                //  adds X,Y,X,Y,X to the output stream."
 | 
			
		||||
                //
 | 
			
		||||
                // loop for each character
 | 
			
		||||
                var ch = buffer.ptr - distance;
 | 
			
		||||
                blockSize += length;
 | 
			
		||||
                if (length > distance) {
 | 
			
		||||
                    var data = buffer.data;
 | 
			
		||||
                    while (length--) {
 | 
			
		||||
                        buffer.insertByte(data[ch++]);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    buffer.insertBytes(buffer.data.subarray(ch, ch + length));
 | 
			
		||||
                }
 | 
			
		||||
            } // length-distance pair
 | 
			
		||||
        } // length-distance pair or end-of-block
 | 
			
		||||
    } // loop until we reach end of block
 | 
			
		||||
    return blockSize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function zeroCompression(compressedData, numDecompressedBytes) {
 | 
			
		||||
    var bstream = new bitjs.io.BitStream(compressedData.buffer,
 | 
			
		||||
        false /* rtl */,
 | 
			
		||||
        compressedData.byteOffset,
 | 
			
		||||
        compressedData.byteLength);
 | 
			
		||||
    var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
 | 
			
		||||
    buffer.insertBytes(bstream.readBytes(numDecompressedBytes));
 | 
			
		||||
    return buffer.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// {Uint8Array} compressedData A Uint8Array of the compressed file data.
 | 
			
		||||
// compression method 8
 | 
			
		||||
// deflate: http://tools.ietf.org/html/rfc1951
 | 
			
		||||
function inflate(compressedData, numDecompressedBytes) {
 | 
			
		||||
    // Bit stream representing the compressed data.
 | 
			
		||||
    var bstream = new bitjs.io.BitStream(compressedData.buffer,
 | 
			
		||||
        false /* rtl */,
 | 
			
		||||
        compressedData.byteOffset,
 | 
			
		||||
        compressedData.byteLength);
 | 
			
		||||
    var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
 | 
			
		||||
    var blockSize = 0;
 | 
			
		||||
 | 
			
		||||
    // block format: http://tools.ietf.org/html/rfc1951#page-9
 | 
			
		||||
    var bFinal = 0;
 | 
			
		||||
    do {
 | 
			
		||||
        bFinal = bstream.readBits(1);
 | 
			
		||||
        var bType = bstream.readBits(2);
 | 
			
		||||
        blockSize = 0;
 | 
			
		||||
        // ++numBlocks;
 | 
			
		||||
        // no compression
 | 
			
		||||
        if (bType === 0) {
 | 
			
		||||
            // skip remaining bits in this byte
 | 
			
		||||
            while (bstream.bitPtr !== 0) bstream.readBits(1);
 | 
			
		||||
            var len = bstream.readBits(16);
 | 
			
		||||
            bstream.readBits(16);
 | 
			
		||||
            // TODO: check if nlen is the ones-complement of len?
 | 
			
		||||
 | 
			
		||||
            if (len > 0) buffer.insertBytes(bstream.readBytes(len));
 | 
			
		||||
            blockSize = len;
 | 
			
		||||
        } else if (bType === 1) {
 | 
			
		||||
            // fixed Huffman codes
 | 
			
		||||
            blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer);
 | 
			
		||||
        } else if (bType === 2) {
 | 
			
		||||
            // dynamic Huffman codes
 | 
			
		||||
            var numLiteralLengthCodes = bstream.readBits(5) + 257;
 | 
			
		||||
            var numDistanceCodes = bstream.readBits(5) + 1,
 | 
			
		||||
                numCodeLengthCodes = bstream.readBits(4) + 4;
 | 
			
		||||
 | 
			
		||||
            // populate the array of code length codes (first de-compaction)
 | 
			
		||||
            var codeLengthsCodeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
 | 
			
		||||
            for (var i = 0; i < numCodeLengthCodes; ++i) {
 | 
			
		||||
                codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // get the Huffman Codes for the code lengths
 | 
			
		||||
            var codeLengthsCodes = getHuffmanCodes(codeLengthsCodeLengths);
 | 
			
		||||
 | 
			
		||||
            // now follow this mapping
 | 
			
		||||
            /*
 | 
			
		||||
               0 - 15: Represent code lengths of 0 - 15
 | 
			
		||||
                   16: Copy the previous code length 3 - 6 times.
 | 
			
		||||
                       The next 2 bits indicate repeat length
 | 
			
		||||
                             (0 = 3, ... , 3 = 6)
 | 
			
		||||
                          Example:  Codes 8, 16 (+2 bits 11),
 | 
			
		||||
                                    16 (+2 bits 10) will expand to
 | 
			
		||||
                                    12 code lengths of 8 (1 + 6 + 5)
 | 
			
		||||
                   17: Repeat a code length of 0 for 3 - 10 times.
 | 
			
		||||
                       (3 bits of length)
 | 
			
		||||
                   18: Repeat a code length of 0 for 11 - 138 times
 | 
			
		||||
                       (7 bits of length)
 | 
			
		||||
            */
 | 
			
		||||
            // to generate the true code lengths of the Huffman Codes for the literal
 | 
			
		||||
            // and distance tables together
 | 
			
		||||
            var literalCodeLengths = [];
 | 
			
		||||
            var prevCodeLength = 0;
 | 
			
		||||
            while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) {
 | 
			
		||||
                var symbol = decodeSymbol(bstream, codeLengthsCodes);
 | 
			
		||||
                if (symbol <= 15) {
 | 
			
		||||
                    literalCodeLengths.push(symbol);
 | 
			
		||||
                    prevCodeLength = symbol;
 | 
			
		||||
                } else if (symbol === 16) {
 | 
			
		||||
                    var repeat = bstream.readBits(2) + 3;
 | 
			
		||||
                    while (repeat--) {
 | 
			
		||||
                        literalCodeLengths.push(prevCodeLength);
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (symbol === 17) {
 | 
			
		||||
                    var repeat1 = bstream.readBits(3) + 3;
 | 
			
		||||
                    while (repeat1--) {
 | 
			
		||||
                        literalCodeLengths.push(0);
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (symbol === 18) {
 | 
			
		||||
                    var repeat2 = bstream.readBits(7) + 11;
 | 
			
		||||
                    while (repeat2--) {
 | 
			
		||||
                        literalCodeLengths.push(0);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // now split the distance code lengths out of the literal code array
 | 
			
		||||
            var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes);
 | 
			
		||||
 | 
			
		||||
            // now generate the true Huffman Code tables using these code lengths
 | 
			
		||||
            var hcLiteralTable = getHuffmanCodes(literalCodeLengths);
 | 
			
		||||
            var hcDistanceTable = getHuffmanCodes(distanceCodeLengths);
 | 
			
		||||
            blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer);
 | 
			
		||||
        } else {
 | 
			
		||||
            // error
 | 
			
		||||
            err("Error! Encountered deflate block of type 3");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // update progress
 | 
			
		||||
        currentBytesUnarchivedInFile += blockSize;
 | 
			
		||||
        currentBytesUnarchived += blockSize;
 | 
			
		||||
        postProgress();
 | 
			
		||||
 | 
			
		||||
    } while (bFinal !== 1);
 | 
			
		||||
    // we are done reading blocks if the bFinal bit was set for this block
 | 
			
		||||
 | 
			
		||||
    // return the buffer data bytes
 | 
			
		||||
    return buffer.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// event.data.file has the ArrayBuffer.
 | 
			
		||||
onmessage = function(event) {
 | 
			
		||||
    unzip(event.data.file, true);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										9155
									
								
								cps/static/js/compress/jszip.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9155
									
								
								cps/static/js/compress/jszip.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										434
									
								
								cps/static/js/compress/libunrar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										434
									
								
								cps/static/js/compress/libunrar.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								cps/static/js/compress/libunrar.js.mem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cps/static/js/compress/libunrar.js.mem
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										90
									
								
								cps/static/js/compress/libuntar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								cps/static/js/compress/libuntar.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,90 @@
 | 
			
		|||
// Copyright (c) 2017 Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
 | 
			
		||||
// This software is licensed under a MIT License
 | 
			
		||||
// https://github.com/workhorsy/uncompress.js
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
// Based on the information from:
 | 
			
		||||
// https://en.wikipedia.org/wiki/Tar_(computing)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
const TAR_TYPE_FILE = 0;
 | 
			
		||||
const TAR_TYPE_DIR = 5;
 | 
			
		||||
 | 
			
		||||
const TAR_HEADER_SIZE = 512;
 | 
			
		||||
const TAR_TYPE_OFFSET = 156;
 | 
			
		||||
const TAR_TYPE_SIZE = 1;
 | 
			
		||||
const TAR_SIZE_OFFSET = 124;
 | 
			
		||||
const TAR_SIZE_SIZE = 12;
 | 
			
		||||
const TAR_NAME_OFFSET = 0;
 | 
			
		||||
const TAR_NAME_SIZE = 100;
 | 
			
		||||
 | 
			
		||||
function _tarRead(view, offset, size) {
 | 
			
		||||
	return view.slice(offset, offset + size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function tarGetEntries(filename, array_buffer) {
 | 
			
		||||
	let view = new Uint8Array(array_buffer);
 | 
			
		||||
	let offset = 0;
 | 
			
		||||
	let entries = [];
 | 
			
		||||
 | 
			
		||||
	while (offset + TAR_HEADER_SIZE < view.byteLength) {
 | 
			
		||||
		// Get entry name
 | 
			
		||||
		let entry_name = saneMap(_tarRead(view, offset + TAR_NAME_OFFSET, TAR_NAME_SIZE), String.fromCharCode);
 | 
			
		||||
		entry_name = entry_name.join('').replace(/\0/g, '');
 | 
			
		||||
 | 
			
		||||
		// No entry name, so probably the last block
 | 
			
		||||
		if (entry_name.length === 0) {
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Get entry size
 | 
			
		||||
		let entry_size = parseInt(saneJoin(saneMap(_tarRead(view, offset + TAR_SIZE_OFFSET, TAR_SIZE_SIZE), String.fromCharCode), ''), 8);
 | 
			
		||||
		let entry_type = saneMap(_tarRead(view, offset + TAR_TYPE_OFFSET, TAR_TYPE_SIZE), String.fromCharCode) | 0;
 | 
			
		||||
 | 
			
		||||
		// Save this as en entry if it is a file or directory
 | 
			
		||||
		if (entry_type === TAR_TYPE_FILE || entry_type === TAR_TYPE_DIR) {
 | 
			
		||||
			let entry = {
 | 
			
		||||
				name: entry_name,
 | 
			
		||||
				size: entry_size,
 | 
			
		||||
				is_file: entry_type == TAR_TYPE_FILE,
 | 
			
		||||
				offset: offset
 | 
			
		||||
			};
 | 
			
		||||
			entries.push(entry);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Round the offset up to be divisible by TAR_HEADER_SIZE
 | 
			
		||||
		offset += (entry_size + TAR_HEADER_SIZE);
 | 
			
		||||
		if (offset % TAR_HEADER_SIZE > 0) {
 | 
			
		||||
			let even = (offset / TAR_HEADER_SIZE) | 0; // number of times it goes evenly into TAR_HEADER_SIZE
 | 
			
		||||
			offset = (even + 1) * TAR_HEADER_SIZE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return entries;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function tarGetEntryData(entry, array_buffer) {
 | 
			
		||||
	let view = new Uint8Array(array_buffer);
 | 
			
		||||
	let offset = entry.offset;
 | 
			
		||||
	let size = entry.size;
 | 
			
		||||
 | 
			
		||||
	// Get entry data
 | 
			
		||||
	let entry_data = _tarRead(view, offset + TAR_HEADER_SIZE, size);
 | 
			
		||||
	return entry_data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Figure out if we are running in a Window or Web Worker
 | 
			
		||||
let scope = null;
 | 
			
		||||
if (typeof window === 'object') {
 | 
			
		||||
	scope = window;
 | 
			
		||||
} else if (typeof importScripts === 'function') {
 | 
			
		||||
	scope = self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set exports
 | 
			
		||||
scope.tarGetEntries = tarGetEntries;
 | 
			
		||||
scope.tarGetEntryData = tarGetEntryData;
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										420
									
								
								cps/static/js/compress/uncompress.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										420
									
								
								cps/static/js/compress/uncompress.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,420 @@
 | 
			
		|||
// Copyright (c) 2017 Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
 | 
			
		||||
// This software is licensed under a MIT License
 | 
			
		||||
// https://github.com/workhorsy/uncompress.js
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function loadScript(url, cb) {
 | 
			
		||||
	// Window
 | 
			
		||||
	if (typeof window === 'object') {
 | 
			
		||||
		let script = document.createElement('script');
 | 
			
		||||
		script.type = "text/javascript";
 | 
			
		||||
		script.src = url;
 | 
			
		||||
		script.onload = function() {
 | 
			
		||||
			if (cb) cb();
 | 
			
		||||
		};
 | 
			
		||||
		document.head.appendChild(script);
 | 
			
		||||
	// Web Worker
 | 
			
		||||
	} else if (typeof importScripts === 'function') {
 | 
			
		||||
		importScripts(url);
 | 
			
		||||
		if (cb) cb();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function currentScriptPath() {
 | 
			
		||||
	// NOTE: document.currentScript does not work in a Web Worker
 | 
			
		||||
	// So we have to parse a stack trace maually
 | 
			
		||||
	try {
 | 
			
		||||
		throw new Error('');
 | 
			
		||||
	} catch(e) {
 | 
			
		||||
		let stack = e.stack;
 | 
			
		||||
		let line = null;
 | 
			
		||||
 | 
			
		||||
		// Chrome and IE
 | 
			
		||||
		if (stack.indexOf('@') !== -1) {
 | 
			
		||||
			line = stack.split('@')[1].split('\n')[0];
 | 
			
		||||
		// Firefox
 | 
			
		||||
		} else {
 | 
			
		||||
			line = stack.split('(')[1].split(')')[0];
 | 
			
		||||
		}
 | 
			
		||||
		line = line.substring(0, line.lastIndexOf('/')) + '/';
 | 
			
		||||
		return line;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This is used by libunrar.js to load libunrar.js.mem
 | 
			
		||||
let unrarMemoryFileLocation = null;
 | 
			
		||||
let g_on_loaded_cb = null;
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
let _loaded_archive_formats = [];
 | 
			
		||||
 | 
			
		||||
// Polyfill for missing array slice method (IE 11)
 | 
			
		||||
if (typeof Uint8Array !== 'undefined') {
 | 
			
		||||
if (! Uint8Array.prototype.slice) {
 | 
			
		||||
	Uint8Array.prototype.slice = function(start, end) {
 | 
			
		||||
		let retval = new Uint8Array(end - start);
 | 
			
		||||
		let j = 0;
 | 
			
		||||
		for (let i=start; i<end; ++i) {
 | 
			
		||||
			retval[j] = this[i];
 | 
			
		||||
			j++;
 | 
			
		||||
		}
 | 
			
		||||
		return retval;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FIXME: This function is super inefficient
 | 
			
		||||
function saneJoin(array, separator) {
 | 
			
		||||
	let retval = '';
 | 
			
		||||
	for (let i=0; i<array.length; ++i) {
 | 
			
		||||
		if (i === 0) {
 | 
			
		||||
			retval += array[i];
 | 
			
		||||
		} else {
 | 
			
		||||
			retval += separator + array[i];
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function saneMap(array, cb) {
 | 
			
		||||
	let retval = new Array(array.length);
 | 
			
		||||
	for (let i=0; i<retval.length; ++i) {
 | 
			
		||||
		retval[i] = cb(array[i]);
 | 
			
		||||
	}
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loadArchiveFormats(formats, cb) {
 | 
			
		||||
	// Get the path of the current script
 | 
			
		||||
	let path = currentScriptPath();
 | 
			
		||||
	let load_counter = 0;
 | 
			
		||||
 | 
			
		||||
	let checkForLoadDone = function() {
 | 
			
		||||
		load_counter++;
 | 
			
		||||
 | 
			
		||||
		// Get the total number of loads before we are done loading
 | 
			
		||||
		// If loading RAR in a Window, have 1 extra load.
 | 
			
		||||
		let load_total = formats.length;
 | 
			
		||||
		if (formats.indexOf('rar') !== -1 && typeof window === 'object') {
 | 
			
		||||
			load_total++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// run the callback if the last script has loaded
 | 
			
		||||
		if (load_counter === load_total) {
 | 
			
		||||
			cb();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	g_on_loaded_cb = checkForLoadDone;
 | 
			
		||||
 | 
			
		||||
	// Load the formats
 | 
			
		||||
	formats.forEach(function(archive_format) {
 | 
			
		||||
		// Skip this format if it is already loaded
 | 
			
		||||
		if (_loaded_archive_formats.indexOf(archive_format) !== -1) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Load the archive format
 | 
			
		||||
		switch (archive_format) {
 | 
			
		||||
			case 'rar':
 | 
			
		||||
				unrarMemoryFileLocation = path + 'libunrar.js.mem';
 | 
			
		||||
				loadScript(path + 'libunrar.js', checkForLoadDone);
 | 
			
		||||
				_loaded_archive_formats.push(archive_format);
 | 
			
		||||
				break;
 | 
			
		||||
			case 'zip':
 | 
			
		||||
				loadScript(path + 'jszip.js', checkForLoadDone);
 | 
			
		||||
				_loaded_archive_formats.push(archive_format);
 | 
			
		||||
				break;
 | 
			
		||||
			case 'tar':
 | 
			
		||||
				loadScript(path + 'libuntar.js', checkForLoadDone);
 | 
			
		||||
				_loaded_archive_formats.push(archive_format);
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				throw new Error("Unknown archive format '" + archive_format + "'.");
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function archiveOpenFile(array_buffer, cb) {
 | 
			
		||||
	let file_name = "Hugo"; //file.name;
 | 
			
		||||
	let password = null;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        let archive = archiveOpenArrayBuffer(file_name, password, array_buffer);
 | 
			
		||||
        cb(archive, null);
 | 
			
		||||
    } catch(e) {
 | 
			
		||||
        cb(null, e);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function archiveOpenArrayBuffer(file_name, password, array_buffer) {
 | 
			
		||||
	// Get the archive type
 | 
			
		||||
	let archive_type = null;
 | 
			
		||||
	if (isRarFile(array_buffer)) {
 | 
			
		||||
		archive_type = 'rar';
 | 
			
		||||
	} else if(isZipFile(array_buffer)) {
 | 
			
		||||
		archive_type = 'zip';
 | 
			
		||||
	} else if(isTarFile(array_buffer)) {
 | 
			
		||||
		archive_type = 'tar';
 | 
			
		||||
	} else {
 | 
			
		||||
		throw new Error("The archive type is unknown");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure the archive format is loaded
 | 
			
		||||
	if (_loaded_archive_formats.indexOf(archive_type) === -1) {
 | 
			
		||||
		throw new Error("The archive format '" + archive_type + "' is not loaded.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get the entries
 | 
			
		||||
	let handle = null;
 | 
			
		||||
	let entries = [];
 | 
			
		||||
	try {
 | 
			
		||||
		switch (archive_type) {
 | 
			
		||||
			case 'rar':
 | 
			
		||||
				handle = _rarOpen(file_name, password, array_buffer);
 | 
			
		||||
				entries = _rarGetEntries(handle);
 | 
			
		||||
				break;
 | 
			
		||||
			case 'zip':
 | 
			
		||||
				handle = _zipOpen(file_name, password, array_buffer);
 | 
			
		||||
				entries = _zipGetEntries(handle);
 | 
			
		||||
				break;
 | 
			
		||||
			case 'tar':
 | 
			
		||||
				handle = _tarOpen(file_name, password, array_buffer);
 | 
			
		||||
				entries = _tarGetEntries(handle);
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	} catch(e) {
 | 
			
		||||
		throw new Error("Failed to open '" + archive_type + "' archive.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sort the entries by name
 | 
			
		||||
	entries.sort(function(a, b) {
 | 
			
		||||
		if(a.name < b.name) return -1;
 | 
			
		||||
		if(a.name > b.name) return 1;
 | 
			
		||||
		return 0;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Return the archive object
 | 
			
		||||
	return {
 | 
			
		||||
		file_name: file_name,
 | 
			
		||||
		archive_type: archive_type,
 | 
			
		||||
		array_buffer: array_buffer,
 | 
			
		||||
		entries: entries,
 | 
			
		||||
		handle: handle
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function archiveClose(archive) {
 | 
			
		||||
	archive.file_name = null;
 | 
			
		||||
	archive.archive_type = null;
 | 
			
		||||
	archive.array_buffer = null;
 | 
			
		||||
	archive.entries = null;
 | 
			
		||||
	archive.handle = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _rarOpen(file_name, password, array_buffer) {
 | 
			
		||||
	// Create an array of rar files
 | 
			
		||||
	let rar_files = [{
 | 
			
		||||
		name: file_name,
 | 
			
		||||
		size: array_buffer.byteLength,
 | 
			
		||||
		type: '',
 | 
			
		||||
		content: new Uint8Array(array_buffer)
 | 
			
		||||
	}];
 | 
			
		||||
 | 
			
		||||
	// Return rar handle
 | 
			
		||||
	return {
 | 
			
		||||
		file_name: file_name,
 | 
			
		||||
		array_buffer: array_buffer,
 | 
			
		||||
		password: password,
 | 
			
		||||
		rar_files: rar_files
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _zipOpen(file_name, password, array_buffer) {
 | 
			
		||||
	let zip = new JSZip(array_buffer);
 | 
			
		||||
 | 
			
		||||
	// Return zip handle
 | 
			
		||||
	return {
 | 
			
		||||
		file_name: file_name,
 | 
			
		||||
		array_buffer: array_buffer,
 | 
			
		||||
		password: password,
 | 
			
		||||
		zip: zip
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _tarOpen(file_name, password, array_buffer) {
 | 
			
		||||
	// Return tar handle
 | 
			
		||||
	return {
 | 
			
		||||
		file_name: file_name,
 | 
			
		||||
		array_buffer: array_buffer,
 | 
			
		||||
		password: password
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _rarGetEntries(rar_handle) {
 | 
			
		||||
	// Get the entries
 | 
			
		||||
	let info = readRARFileNames(rar_handle.rar_files, rar_handle.password);
 | 
			
		||||
	let entries = [];
 | 
			
		||||
	Object.keys(info).forEach(function(i) {
 | 
			
		||||
		let name = info[i].name;
 | 
			
		||||
		let is_file = info[i].is_file;
 | 
			
		||||
        if (is_file) {
 | 
			
		||||
            entries.push({
 | 
			
		||||
                name: name,
 | 
			
		||||
                is_file: is_file, // info[i].is_file,
 | 
			
		||||
                size_compressed: info[i].size_compressed,
 | 
			
		||||
                size_uncompressed: info[i].size_uncompressed,
 | 
			
		||||
                readData: function (cb) {
 | 
			
		||||
                    setTimeout(function () {
 | 
			
		||||
                        if (is_file) {
 | 
			
		||||
                            try {
 | 
			
		||||
                                readRARContent(rar_handle.rar_files, rar_handle.password, name, cb);
 | 
			
		||||
                            } catch (e) {
 | 
			
		||||
                                cb(null, e);
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            cb(null, null);
 | 
			
		||||
                        }
 | 
			
		||||
                    }, 0);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return entries;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _zipGetEntries(zip_handle) {
 | 
			
		||||
	let zip = zip_handle.zip;
 | 
			
		||||
 | 
			
		||||
	// Get all the entries
 | 
			
		||||
	let entries = [];
 | 
			
		||||
	Object.keys(zip.files).forEach(function(i) {
 | 
			
		||||
		let zip_entry = zip.files[i];
 | 
			
		||||
		let name = zip_entry.name;
 | 
			
		||||
		let is_file = ! zip_entry.dir;
 | 
			
		||||
		let size_compressed = zip_entry._data ? zip_entry._data.compressedSize : 0;
 | 
			
		||||
		let size_uncompressed = zip_entry._data ? zip_entry._data.uncompressedSize : 0;
 | 
			
		||||
        if (is_file) {
 | 
			
		||||
            entries.push({
 | 
			
		||||
                name: name,
 | 
			
		||||
                is_file: is_file,
 | 
			
		||||
                size_compressed: size_compressed,
 | 
			
		||||
                size_uncompressed: size_uncompressed,
 | 
			
		||||
                readData: function (cb) {
 | 
			
		||||
                    setTimeout(function () {
 | 
			
		||||
                        if (is_file) {
 | 
			
		||||
                            let data = zip_entry.asArrayBuffer();
 | 
			
		||||
                            cb(data, null);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            cb(null, null);
 | 
			
		||||
                        }
 | 
			
		||||
                    }, 0);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return entries;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _tarGetEntries(tar_handle) {
 | 
			
		||||
	let tar_entries = tarGetEntries(tar_handle.file_name, tar_handle.array_buffer);
 | 
			
		||||
 | 
			
		||||
	// Get all the entries
 | 
			
		||||
	let entries = [];
 | 
			
		||||
	tar_entries.forEach(function(entry) {
 | 
			
		||||
		let name = entry.name;
 | 
			
		||||
		let is_file = entry.is_file;
 | 
			
		||||
		let size = entry.size;
 | 
			
		||||
        if (is_file) {
 | 
			
		||||
            entries.push({
 | 
			
		||||
                name: name,
 | 
			
		||||
                is_file: is_file,
 | 
			
		||||
                size_compressed: size,
 | 
			
		||||
                size_uncompressed: size,
 | 
			
		||||
                readData: function (cb) {
 | 
			
		||||
                    setTimeout(function () {
 | 
			
		||||
                        if (is_file) {
 | 
			
		||||
                            let data = tarGetEntryData(entry, tar_handle.array_buffer);
 | 
			
		||||
                            cb(data.buffer, null);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            cb(null, null);
 | 
			
		||||
                        }
 | 
			
		||||
                    }, 0);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return entries;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isRarFile(array_buffer) {
 | 
			
		||||
	// The three styles of RAR headers
 | 
			
		||||
	let rar_header1 = saneJoin([0x52, 0x45, 0x7E, 0x5E], ', '); // old
 | 
			
		||||
	let rar_header2 = saneJoin([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00], ', '); // 1.5 to 4.0
 | 
			
		||||
	let rar_header3 = saneJoin([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00], ', '); // 5.0
 | 
			
		||||
 | 
			
		||||
	// Just return false if the file is smaller than the header
 | 
			
		||||
	if (array_buffer.byteLength < 8) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Return true if the header matches one of the RAR headers
 | 
			
		||||
	let header1 = saneJoin(new Uint8Array(array_buffer).slice(0, 4), ', ');
 | 
			
		||||
	let header2 = saneJoin(new Uint8Array(array_buffer).slice(0, 7), ', ');
 | 
			
		||||
	let header3 = saneJoin(new Uint8Array(array_buffer).slice(0, 8), ', ');
 | 
			
		||||
	return (header1 === rar_header1 || header2 === rar_header2 || header3 === rar_header3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isZipFile(array_buffer) {
 | 
			
		||||
	// The ZIP header
 | 
			
		||||
	let zip_header = saneJoin([0x50, 0x4b, 0x03, 0x04], ', ');
 | 
			
		||||
 | 
			
		||||
	// Just return false if the file is smaller than the header
 | 
			
		||||
	if (array_buffer.byteLength < 4) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Return true if the header matches the ZIP header
 | 
			
		||||
	let header = saneJoin(new Uint8Array(array_buffer).slice(0, 4), ', ');
 | 
			
		||||
	return (header === zip_header);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isTarFile(array_buffer) {
 | 
			
		||||
	// The TAR header
 | 
			
		||||
	let tar_header = saneJoin(['u', 's', 't', 'a', 'r'], ', ');
 | 
			
		||||
 | 
			
		||||
	// Just return false if the file is smaller than the header size
 | 
			
		||||
	if (array_buffer.byteLength < 512) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Return true if the header matches the TAR header
 | 
			
		||||
	let header = saneJoin(saneMap(new Uint8Array(array_buffer).slice(257, 257 + 5), String.fromCharCode), ', ');
 | 
			
		||||
	return (header === tar_header);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Figure out if we are running in a Window or Web Worker
 | 
			
		||||
let scope = null;
 | 
			
		||||
if (typeof window === 'object') {
 | 
			
		||||
	scope = window;
 | 
			
		||||
} else if (typeof importScripts === 'function') {
 | 
			
		||||
	scope = self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set exports
 | 
			
		||||
scope.loadArchiveFormats = loadArchiveFormats;
 | 
			
		||||
scope.archiveOpenFile = archiveOpenFile;
 | 
			
		||||
scope.archiveOpenArrayBuffer = archiveOpenArrayBuffer;
 | 
			
		||||
scope.archiveClose = archiveClose;
 | 
			
		||||
scope.isRarFile = isRarFile;
 | 
			
		||||
scope.isZipFile = isZipFile;
 | 
			
		||||
scope.isTarFile = isTarFile;
 | 
			
		||||
scope.saneJoin = saneJoin;
 | 
			
		||||
scope.saneMap = saneMap;
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			@ -19,310 +19,375 @@
 | 
			
		|||
 * Google Books api document: https://developers.google.com/books/docs/v1/using
 | 
			
		||||
 * Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only)
 | 
			
		||||
 * ComicVine api document: https://comicvine.gamespot.com/api/documentation
 | 
			
		||||
*/
 | 
			
		||||
 */
 | 
			
		||||
/* global _, i18nMsg, tinymce */
 | 
			
		||||
var dbResults = [];
 | 
			
		||||
var ggResults = [];
 | 
			
		||||
var cvResults = [];
 | 
			
		||||
var gsResults = [];
 | 
			
		||||
 | 
			
		||||
$(function () {
 | 
			
		||||
    var msg = i18nMsg;
 | 
			
		||||
    var douban = "https://api.douban.com";
 | 
			
		||||
    var dbSearch = "/v2/book/search";
 | 
			
		||||
    var dbDone = 0;
 | 
			
		||||
  var msg = i18nMsg;
 | 
			
		||||
  var douban = "https://api.douban.com";
 | 
			
		||||
  var dbSearch = "/v2/book/search";
 | 
			
		||||
  var dbDone = 0;
 | 
			
		||||
 | 
			
		||||
    var google = "https://www.googleapis.com";
 | 
			
		||||
    var ggSearch = "/books/v1/volumes";
 | 
			
		||||
    var ggDone = 0;
 | 
			
		||||
  var google = "https://www.googleapis.com";
 | 
			
		||||
  var ggSearch = "/books/v1/volumes";
 | 
			
		||||
  var ggDone = 0;
 | 
			
		||||
 | 
			
		||||
    var comicvine = "https://comicvine.gamespot.com";
 | 
			
		||||
    var cvSearch = "/api/search/";
 | 
			
		||||
    var cvDone = 0;
 | 
			
		||||
  var comicvine = "https://comicvine.gamespot.com";
 | 
			
		||||
  var cvSearch = "/api/search/";
 | 
			
		||||
  var cvDone = 0;
 | 
			
		||||
 | 
			
		||||
    var showFlag = 0;
 | 
			
		||||
  var googlescholar = window.location.href.split('/admin/book')[0];
 | 
			
		||||
  var gsSearch = "/scholarsearch/"
 | 
			
		||||
  var gsDone = 0;
 | 
			
		||||
 | 
			
		||||
    var templates = {
 | 
			
		||||
        bookResult: _.template(
 | 
			
		||||
            $("#template-book-result").html()
 | 
			
		||||
        )
 | 
			
		||||
    };
 | 
			
		||||
  var showFlag = 0;
 | 
			
		||||
 | 
			
		||||
    function populateForm (book) {
 | 
			
		||||
        tinymce.get("description").setContent(book.description);
 | 
			
		||||
        var uniqueTags = [];
 | 
			
		||||
        $.each(book.tags, function(i, el) {
 | 
			
		||||
            if ($.inArray(el, uniqueTags) === -1) uniqueTags.push(el);
 | 
			
		||||
        });
 | 
			
		||||
  var templates = {
 | 
			
		||||
    bookResult: _.template(
 | 
			
		||||
      $("#template-book-result").html()
 | 
			
		||||
    )
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
        var ampSeparatedAuthors = (book.authors || []).join(" & ");
 | 
			
		||||
        $("#bookAuthor").val(ampSeparatedAuthors);
 | 
			
		||||
        $("#book_title").val(book.title);
 | 
			
		||||
        $("#tags").val(uniqueTags.join(","));
 | 
			
		||||
        $("#rating").data("rating").setValue(Math.round(book.rating));
 | 
			
		||||
        $(".cover img").attr("src", book.cover);
 | 
			
		||||
        $("#cover_url").val(book.cover);
 | 
			
		||||
        $("#pubdate").val(book.publishedDate);
 | 
			
		||||
        $("#publisher").val(book.publisher);
 | 
			
		||||
        if (typeof book.series !== "undefined") {
 | 
			
		||||
            $("#series").val(book.series);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showResult () {
 | 
			
		||||
        showFlag++;
 | 
			
		||||
        if (showFlag === 1) {
 | 
			
		||||
            $("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
 | 
			
		||||
        }
 | 
			
		||||
        if ((ggDone === 3 || (ggDone === 1 && ggResults.length === 0)) &&
 | 
			
		||||
            (dbDone === 3 || (dbDone === 1 && dbResults.length === 0)) &&
 | 
			
		||||
            (cvDone === 3 || (cvDone === 1 && cvResults.length === 0))) {
 | 
			
		||||
            $("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        function formatDate (date) {
 | 
			
		||||
            var d = new Date(date),
 | 
			
		||||
                month = "" + (d.getMonth() + 1),
 | 
			
		||||
                day = "" + d.getDate(),
 | 
			
		||||
                year = d.getFullYear();
 | 
			
		||||
 | 
			
		||||
            if (month.length < 2) {
 | 
			
		||||
                month = "0" + month;
 | 
			
		||||
            }
 | 
			
		||||
            if (day.length < 2) {
 | 
			
		||||
                day = "0" + day;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return [year, month, day].join("-");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ggResults.length > 0) {
 | 
			
		||||
            if (ggDone < 2) {
 | 
			
		||||
                ggResults.forEach(function(result) {
 | 
			
		||||
                    var book = {
 | 
			
		||||
                        id: result.id,
 | 
			
		||||
                        title: result.volumeInfo.title,
 | 
			
		||||
                        authors: result.volumeInfo.authors || [],
 | 
			
		||||
                        description: result.volumeInfo.description || "",
 | 
			
		||||
                        publisher: result.volumeInfo.publisher || "",
 | 
			
		||||
                        publishedDate: result.volumeInfo.publishedDate || "",
 | 
			
		||||
                        tags: result.volumeInfo.categories || [],
 | 
			
		||||
                        rating: result.volumeInfo.averageRating || 0,
 | 
			
		||||
                        cover: result.volumeInfo.imageLinks ?
 | 
			
		||||
                            result.volumeInfo.imageLinks.thumbnail : location + "/../../../static/generic_cover.jpg",
 | 
			
		||||
                        url: "https://books.google.com/books?id=" + result.id,
 | 
			
		||||
                        source: {
 | 
			
		||||
                            id: "google",
 | 
			
		||||
                            description: "Google Books",
 | 
			
		||||
                            url: "https://books.google.com/"
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    var $book = $(templates.bookResult(book));
 | 
			
		||||
                    $book.find("img").on("click", function () {
 | 
			
		||||
                        populateForm(book);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    $("#book-list").append($book);
 | 
			
		||||
                });
 | 
			
		||||
                ggDone = 2;
 | 
			
		||||
            } else {
 | 
			
		||||
                ggDone = 3;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (dbResults.length > 0) {
 | 
			
		||||
            if (dbDone < 2) {
 | 
			
		||||
                dbResults.forEach(function(result) {
 | 
			
		||||
                    var seriesTitle = "";
 | 
			
		||||
                    if (result.series) {
 | 
			
		||||
                        seriesTitle = result.series.title;
 | 
			
		||||
                    }
 | 
			
		||||
                    var dateFomers = result.pubdate.split("-");
 | 
			
		||||
                    var publishedYear = parseInt(dateFomers[0], 10);
 | 
			
		||||
                    var publishedMonth = parseInt(dateFomers[1], 10);
 | 
			
		||||
                    var publishedDate = new Date(publishedYear, publishedMonth - 1, 1);
 | 
			
		||||
 | 
			
		||||
                    publishedDate = formatDate(publishedDate);
 | 
			
		||||
 | 
			
		||||
                    var book = {
 | 
			
		||||
                        id: result.id,
 | 
			
		||||
                        title: result.title,
 | 
			
		||||
                        authors: result.author || [],
 | 
			
		||||
                        description: result.summary,
 | 
			
		||||
                        publisher: result.publisher || "",
 | 
			
		||||
                        publishedDate: publishedDate || "",
 | 
			
		||||
                        tags: result.tags.map(function(tag) {
 | 
			
		||||
                            return tag.title.toLowerCase().replace(/,/g, "_");
 | 
			
		||||
                        }),
 | 
			
		||||
                        rating: result.rating.average || 0,
 | 
			
		||||
                        series: seriesTitle || "",
 | 
			
		||||
                        cover: result.image,
 | 
			
		||||
                        url: "https://book.douban.com/subject/" + result.id,
 | 
			
		||||
                        source: {
 | 
			
		||||
                            id: "douban",
 | 
			
		||||
                            description: "Douban Books",
 | 
			
		||||
                            url: "https://book.douban.com/"
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    if (book.rating > 0) {
 | 
			
		||||
                        book.rating /= 2;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var $book = $(templates.bookResult(book));
 | 
			
		||||
                    $book.find("img").on("click", function () {
 | 
			
		||||
                        populateForm(book);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    $("#book-list").append($book);
 | 
			
		||||
                });
 | 
			
		||||
                dbDone = 2;
 | 
			
		||||
            } else {
 | 
			
		||||
                dbDone = 3;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (cvResults.length > 0) {
 | 
			
		||||
            if (cvDone < 2) {
 | 
			
		||||
                cvResults.forEach(function(result) {
 | 
			
		||||
                    var seriesTitle = "";
 | 
			
		||||
                    if (result.volume.name) {
 | 
			
		||||
                        seriesTitle = result.volume.name;
 | 
			
		||||
                    }
 | 
			
		||||
                    var dateFomers = "";
 | 
			
		||||
                    if (result.store_date) {
 | 
			
		||||
                        dateFomers = result.store_date.split("-");
 | 
			
		||||
                    } else {
 | 
			
		||||
                        dateFomers = result.date_added.split("-");
 | 
			
		||||
                    }
 | 
			
		||||
                    var publishedYear = parseInt(dateFomers[0], 10);
 | 
			
		||||
                    var publishedMonth = parseInt(dateFomers[1], 10);
 | 
			
		||||
                    var publishedDate = new Date(publishedYear, publishedMonth - 1, 1);
 | 
			
		||||
 | 
			
		||||
                    publishedDate = formatDate(publishedDate);
 | 
			
		||||
 | 
			
		||||
                    var book = {
 | 
			
		||||
                        id: result.id,
 | 
			
		||||
                        title: seriesTitle + " #" + ("00" + result.issue_number).slice(-3) + " - " + result.name,
 | 
			
		||||
                        authors: result.author || [],
 | 
			
		||||
                        description: result.description,
 | 
			
		||||
                        publisher: "",
 | 
			
		||||
                        publishedDate: publishedDate || "",
 | 
			
		||||
                        tags: ["Comics", seriesTitle],
 | 
			
		||||
                        rating: 0,
 | 
			
		||||
                        series: seriesTitle || "",
 | 
			
		||||
                        cover: result.image.original_url,
 | 
			
		||||
                        url: result.site_detail_url,
 | 
			
		||||
                        source: {
 | 
			
		||||
                            id: "comicvine",
 | 
			
		||||
                            description: "ComicVine Books",
 | 
			
		||||
                            url: "https://comicvine.gamespot.com/"
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    var $book = $(templates.bookResult(book));
 | 
			
		||||
                    $book.find("img").on("click", function () {
 | 
			
		||||
                        populateForm(book);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    $("#book-list").append($book);
 | 
			
		||||
                });
 | 
			
		||||
                cvDone = 2;
 | 
			
		||||
            } else {
 | 
			
		||||
                cvDone = 3;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function ggSearchBook (title) {
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: google + ggSearch + "?q=" + title.replace(/\s+/gm, "+"),
 | 
			
		||||
            type: "GET",
 | 
			
		||||
            dataType: "jsonp",
 | 
			
		||||
            jsonp: "callback",
 | 
			
		||||
            success: function success(data) {
 | 
			
		||||
                if ("items" in data) {
 | 
			
		||||
                    ggResults = data.items;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            complete: function complete() {
 | 
			
		||||
                ggDone = 1;
 | 
			
		||||
                showResult();
 | 
			
		||||
                $("#show-google").trigger("change");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function dbSearchBook (title) {
 | 
			
		||||
        var apikey = "054022eaeae0b00e0fc068c0c0a2102a";
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: douban + dbSearch + "?apikey=" + apikey + "&q=" + title + "&fields=all&count=10",
 | 
			
		||||
            type: "GET",
 | 
			
		||||
            dataType: "jsonp",
 | 
			
		||||
            jsonp: "callback",
 | 
			
		||||
            success: function success(data) {
 | 
			
		||||
                dbResults = data.books;
 | 
			
		||||
            },
 | 
			
		||||
            error: function error() {
 | 
			
		||||
                $("#meta-info").html("<p class=\"text-danger\">" + msg.search_error + "!</p>" + $("#meta-info")[0].innerHTML);
 | 
			
		||||
            },
 | 
			
		||||
            complete: function complete() {
 | 
			
		||||
                dbDone = 1;
 | 
			
		||||
                showResult();
 | 
			
		||||
                $("#show-douban").trigger("change");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function cvSearchBook (title) {
 | 
			
		||||
        var apikey = "57558043c53943d5d1e96a9ad425b0eb85532ee6";
 | 
			
		||||
        title = encodeURIComponent(title);
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: comicvine + cvSearch + "?api_key=" + apikey + "&resources=issue&query=" + title + "&sort=name:desc&format=jsonp",
 | 
			
		||||
            type: "GET",
 | 
			
		||||
            dataType: "jsonp",
 | 
			
		||||
            jsonp: "json_callback",
 | 
			
		||||
            success: function success(data) {
 | 
			
		||||
                cvResults = data.results;
 | 
			
		||||
            },
 | 
			
		||||
            error: function error() {
 | 
			
		||||
                $("#meta-info").html("<p class=\"text-danger\">" + msg.search_error + "!</p>" + $("#meta-info")[0].innerHTML);
 | 
			
		||||
            },
 | 
			
		||||
            complete: function complete() {
 | 
			
		||||
                cvDone = 1;
 | 
			
		||||
                showResult();
 | 
			
		||||
                $("#show-comics").trigger("change");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function doSearch (keyword) {
 | 
			
		||||
        showFlag = 0;
 | 
			
		||||
        dbDone = ggDone = cvDone = 0;
 | 
			
		||||
        dbResults = [];
 | 
			
		||||
        ggResults = [];
 | 
			
		||||
        cvResults = [];
 | 
			
		||||
        $("#meta-info").text(msg.loading);
 | 
			
		||||
        if (keyword) {
 | 
			
		||||
            dbSearchBook(keyword);
 | 
			
		||||
            ggSearchBook(keyword);
 | 
			
		||||
            cvSearchBook(keyword);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $("#meta-search").on("submit", function (e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        var keyword = $("#keyword").val();
 | 
			
		||||
        if (keyword) {
 | 
			
		||||
            doSearch(keyword);
 | 
			
		||||
        }
 | 
			
		||||
  function populateForm (book) {
 | 
			
		||||
    tinymce.get("description").setContent(book.description);
 | 
			
		||||
    var uniqueTags = [];
 | 
			
		||||
    $.each(book.tags, function(i, el) {
 | 
			
		||||
      if ($.inArray(el, uniqueTags) === -1) uniqueTags.push(el);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#get_meta").click(function () {
 | 
			
		||||
        var bookTitle = $("#book_title").val();
 | 
			
		||||
        if (bookTitle) {
 | 
			
		||||
            $("#keyword").val(bookTitle);
 | 
			
		||||
            doSearch(bookTitle);
 | 
			
		||||
    var ampSeparatedAuthors = (book.authors || []).join(" & ");
 | 
			
		||||
    $("#bookAuthor").val(ampSeparatedAuthors);
 | 
			
		||||
    $("#book_title").val(book.title);
 | 
			
		||||
    $("#tags").val(uniqueTags.join(","));
 | 
			
		||||
    $("#rating").data("rating").setValue(Math.round(book.rating));
 | 
			
		||||
    if(book.cover !== null){
 | 
			
		||||
      $(".cover img").attr("src", book.cover);
 | 
			
		||||
      $("#cover_url").val(book.cover);
 | 
			
		||||
    }
 | 
			
		||||
    $("#pubdate").val(book.publishedDate);
 | 
			
		||||
    $("#publisher").val(book.publisher);
 | 
			
		||||
    if (typeof book.series !== "undefined") {
 | 
			
		||||
      $("#series").val(book.series);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function showResult () {
 | 
			
		||||
    showFlag++;
 | 
			
		||||
    if (showFlag === 1) {
 | 
			
		||||
      $("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
 | 
			
		||||
    }
 | 
			
		||||
    if ((ggDone === 3 || (ggDone === 1 && ggResults.length === 0)) &&
 | 
			
		||||
      (dbDone === 3 || (dbDone === 1 && dbResults.length === 0)) &&
 | 
			
		||||
      (cvDone === 3 || (cvDone === 1 && cvResults.length === 0)) &&
 | 
			
		||||
      (gsDone === 3 || (gsDone === 1 && gsResults.length === 0))) {
 | 
			
		||||
      $("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    function formatDate (date) {
 | 
			
		||||
      var d = new Date(date),
 | 
			
		||||
        month = "" + (d.getMonth() + 1),
 | 
			
		||||
        day = "" + d.getDate(),
 | 
			
		||||
        year = d.getFullYear();
 | 
			
		||||
 | 
			
		||||
      if (month.length < 2) {
 | 
			
		||||
        month = "0" + month;
 | 
			
		||||
      }
 | 
			
		||||
      if (day.length < 2) {
 | 
			
		||||
        day = "0" + day;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return [year, month, day].join("-");
 | 
			
		||||
    }
 | 
			
		||||
    function generateID (title) {
 | 
			
		||||
      return title.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0).toString().substr(0,12);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ggResults.length > 0) {
 | 
			
		||||
      if (ggDone < 2) {
 | 
			
		||||
        ggResults.forEach(function(result) {
 | 
			
		||||
          var book = {
 | 
			
		||||
            id: result.id,
 | 
			
		||||
            title: result.volumeInfo.title,
 | 
			
		||||
            authors: result.volumeInfo.authors || [],
 | 
			
		||||
            description: result.volumeInfo.description || "",
 | 
			
		||||
            publisher: result.volumeInfo.publisher || "",
 | 
			
		||||
            publishedDate: result.volumeInfo.publishedDate || "",
 | 
			
		||||
            tags: result.volumeInfo.categories || [],
 | 
			
		||||
            rating: result.volumeInfo.averageRating || 0,
 | 
			
		||||
            cover: result.volumeInfo.imageLinks ?
 | 
			
		||||
            result.volumeInfo.imageLinks.thumbnail : location + "/../../../static/generic_cover.jpg",
 | 
			
		||||
            url: "https://books.google.com/books?id=" + result.id,
 | 
			
		||||
            source: {
 | 
			
		||||
              id: "google",
 | 
			
		||||
              description: "Google Books",
 | 
			
		||||
              url: "https://books.google.com/"
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          var $book = $(templates.bookResult(book));
 | 
			
		||||
          $book.find("img").on("click", function () {
 | 
			
		||||
            populateForm(book);
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          $("#book-list").append($book);
 | 
			
		||||
        });
 | 
			
		||||
        ggDone = 2;
 | 
			
		||||
      } else {
 | 
			
		||||
        ggDone = 3;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (gsResults.length > 0) {
 | 
			
		||||
      if (gsDone < 2) {
 | 
			
		||||
        gsResults.forEach(function(result) {
 | 
			
		||||
          var book = {
 | 
			
		||||
            id: generateID(result.bib.title),
 | 
			
		||||
            title: result.bib.title,
 | 
			
		||||
            authors: result.bib.author || [],
 | 
			
		||||
            description: result.bib.abstract || "",
 | 
			
		||||
            publisher: result.bib.venue || "",
 | 
			
		||||
            publishedDate: result.bib.pub_year ? result.bib.pub_year+"-01-01" : "",
 | 
			
		||||
            tags: [],
 | 
			
		||||
            rating: 0,
 | 
			
		||||
            series: "",
 | 
			
		||||
            cover: null,
 | 
			
		||||
            url: result.pub_url || result.eprint_url || "",
 | 
			
		||||
            source: {
 | 
			
		||||
              id: "googlescholar",
 | 
			
		||||
              description: "Google Scholar",
 | 
			
		||||
              link: "https://scholar.google.com/"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          var $book = $(templates.bookResult(book));
 | 
			
		||||
          $book.find("img").on("click", function () {
 | 
			
		||||
            populateForm(book);
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          $("#book-list").append($book);
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
        gsDone = 2;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        gsDone = 3;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dbResults.length > 0) {
 | 
			
		||||
      if (dbDone < 2) {
 | 
			
		||||
        dbResults.forEach(function(result) {
 | 
			
		||||
          var seriesTitle = "";
 | 
			
		||||
          if (result.series) {
 | 
			
		||||
            seriesTitle = result.series.title;
 | 
			
		||||
          }
 | 
			
		||||
          var dateFomers = result.pubdate.split("-");
 | 
			
		||||
          var publishedYear = parseInt(dateFomers[0], 10);
 | 
			
		||||
          var publishedMonth = parseInt(dateFomers[1], 10);
 | 
			
		||||
          var publishedDate = new Date(publishedYear, publishedMonth - 1, 1);
 | 
			
		||||
 | 
			
		||||
          publishedDate = formatDate(publishedDate);
 | 
			
		||||
 | 
			
		||||
          var book = {
 | 
			
		||||
            id: result.id,
 | 
			
		||||
            title: result.title,
 | 
			
		||||
            authors: result.author || [],
 | 
			
		||||
            description: result.summary,
 | 
			
		||||
            publisher: result.publisher || "",
 | 
			
		||||
            publishedDate: publishedDate || "",
 | 
			
		||||
            tags: result.tags.map(function(tag) {
 | 
			
		||||
              return tag.title.toLowerCase().replace(/,/g, "_");
 | 
			
		||||
            }),
 | 
			
		||||
            rating: result.rating.average || 0,
 | 
			
		||||
            series: seriesTitle || "",
 | 
			
		||||
            cover: result.image,
 | 
			
		||||
            url: "https://book.douban.com/subject/" + result.id,
 | 
			
		||||
            source: {
 | 
			
		||||
              id: "douban",
 | 
			
		||||
              description: "Douban Books",
 | 
			
		||||
              url: "https://book.douban.com/"
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          if (book.rating > 0) {
 | 
			
		||||
            book.rating /= 2;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          var $book = $(templates.bookResult(book));
 | 
			
		||||
          $book.find("img").on("click", function () {
 | 
			
		||||
            populateForm(book);
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          $("#book-list").append($book);
 | 
			
		||||
        });
 | 
			
		||||
        dbDone = 2;
 | 
			
		||||
      } else {
 | 
			
		||||
        dbDone = 3;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (cvResults.length > 0) {
 | 
			
		||||
      if (cvDone < 2) {
 | 
			
		||||
        cvResults.forEach(function(result) {
 | 
			
		||||
          var seriesTitle = "";
 | 
			
		||||
          if (result.volume.name) {
 | 
			
		||||
            seriesTitle = result.volume.name;
 | 
			
		||||
          }
 | 
			
		||||
          var dateFomers = "";
 | 
			
		||||
          if (result.store_date) {
 | 
			
		||||
            dateFomers = result.store_date.split("-");
 | 
			
		||||
          } else {
 | 
			
		||||
            dateFomers = result.date_added.split("-");
 | 
			
		||||
          }
 | 
			
		||||
          var publishedYear = parseInt(dateFomers[0], 10);
 | 
			
		||||
          var publishedMonth = parseInt(dateFomers[1], 10);
 | 
			
		||||
          var publishedDate = new Date(publishedYear, publishedMonth - 1, 1);
 | 
			
		||||
 | 
			
		||||
          publishedDate = formatDate(publishedDate);
 | 
			
		||||
 | 
			
		||||
          var book = {
 | 
			
		||||
            id: result.id,
 | 
			
		||||
            title: seriesTitle + " #" + ("00" + result.issue_number).slice(-3) + " - " + result.name,
 | 
			
		||||
            authors: result.author || [],
 | 
			
		||||
            description: result.description,
 | 
			
		||||
            publisher: "",
 | 
			
		||||
            publishedDate: publishedDate || "",
 | 
			
		||||
            tags: ["Comics", seriesTitle],
 | 
			
		||||
            rating: 0,
 | 
			
		||||
            series: seriesTitle || "",
 | 
			
		||||
            cover: result.image.original_url,
 | 
			
		||||
            url: result.site_detail_url,
 | 
			
		||||
            source: {
 | 
			
		||||
              id: "comicvine",
 | 
			
		||||
              description: "ComicVine Books",
 | 
			
		||||
              url: "https://comicvine.gamespot.com/"
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          var $book = $(templates.bookResult(book));
 | 
			
		||||
          $book.find("img").on("click", function () {
 | 
			
		||||
            populateForm(book);
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          $("#book-list").append($book);
 | 
			
		||||
        });
 | 
			
		||||
        cvDone = 2;
 | 
			
		||||
      } else {
 | 
			
		||||
        cvDone = 3;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function ggSearchBook (title) {
 | 
			
		||||
    $.ajax({
 | 
			
		||||
      url: google + ggSearch + "?q=" + title.replace(/\s+/gm, "+"),
 | 
			
		||||
      type: "GET",
 | 
			
		||||
      dataType: "jsonp",
 | 
			
		||||
      jsonp: "callback",
 | 
			
		||||
      success: function success(data) {
 | 
			
		||||
        if ("items" in data) {
 | 
			
		||||
          ggResults = data.items;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      complete: function complete() {
 | 
			
		||||
        ggDone = 1;
 | 
			
		||||
        showResult();
 | 
			
		||||
        $("#show-google").trigger("change");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function dbSearchBook (title) {
 | 
			
		||||
    var apikey = "054022eaeae0b00e0fc068c0c0a2102a";
 | 
			
		||||
    $.ajax({
 | 
			
		||||
      url: douban + dbSearch + "?apikey=" + apikey + "&q=" + title + "&fields=all&count=10",
 | 
			
		||||
      type: "GET",
 | 
			
		||||
      dataType: "jsonp",
 | 
			
		||||
      jsonp: "callback",
 | 
			
		||||
      success: function success(data) {
 | 
			
		||||
        dbResults = data.books;
 | 
			
		||||
      },
 | 
			
		||||
      error: function error() {
 | 
			
		||||
        $("#meta-info").html("<p class=\"text-danger\">" + msg.search_error + "!</p>" + $("#meta-info")[0].innerHTML);
 | 
			
		||||
      },
 | 
			
		||||
      complete: function complete() {
 | 
			
		||||
        dbDone = 1;
 | 
			
		||||
        showResult();
 | 
			
		||||
        $("#show-douban").trigger("change");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function cvSearchBook (title) {
 | 
			
		||||
    var apikey = "57558043c53943d5d1e96a9ad425b0eb85532ee6";
 | 
			
		||||
    title = encodeURIComponent(title);
 | 
			
		||||
    $.ajax({
 | 
			
		||||
      url: comicvine + cvSearch + "?api_key=" + apikey + "&resources=issue&query=" + title + "&sort=name:desc&format=jsonp",
 | 
			
		||||
      type: "GET",
 | 
			
		||||
      dataType: "jsonp",
 | 
			
		||||
      jsonp: "json_callback",
 | 
			
		||||
      success: function success(data) {
 | 
			
		||||
        cvResults = data.results;
 | 
			
		||||
      },
 | 
			
		||||
      error: function error() {
 | 
			
		||||
        $("#meta-info").html("<p class=\"text-danger\">" + msg.search_error + "!</p>" + $("#meta-info")[0].innerHTML);
 | 
			
		||||
      },
 | 
			
		||||
      complete: function complete() {
 | 
			
		||||
        cvDone = 1;
 | 
			
		||||
        showResult();
 | 
			
		||||
        $("#show-comics").trigger("change");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function gsSearchBook (title) {
 | 
			
		||||
    $.ajax({
 | 
			
		||||
      url: googlescholar + gsSearch + title.replace(/\s+/gm,'+'),
 | 
			
		||||
      type: "GET",
 | 
			
		||||
      dataType: "json",
 | 
			
		||||
      success: function success(data) {
 | 
			
		||||
        gsResults = data;
 | 
			
		||||
      },
 | 
			
		||||
      complete: function complete() {
 | 
			
		||||
        gsDone = 1;
 | 
			
		||||
        showResult();
 | 
			
		||||
        $("#show-googlescholar").trigger("change");
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function doSearch (keyword) {
 | 
			
		||||
    showFlag = 0;
 | 
			
		||||
    dbDone = ggDone = cvDone = 0;
 | 
			
		||||
    dbResults = [];
 | 
			
		||||
    ggResults = [];
 | 
			
		||||
    cvResults = [];
 | 
			
		||||
    gsResults = [];
 | 
			
		||||
    $("#meta-info").text(msg.loading);
 | 
			
		||||
    if (keyword) {
 | 
			
		||||
      dbSearchBook(keyword);
 | 
			
		||||
      ggSearchBook(keyword);
 | 
			
		||||
      cvSearchBook(keyword);
 | 
			
		||||
      gsSearchBook(keyword);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $("#meta-search").on("submit", function (e) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    var keyword = $("#keyword").val();
 | 
			
		||||
    if (keyword) {
 | 
			
		||||
      doSearch(keyword);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  $("#get_meta").click(function () {
 | 
			
		||||
    var bookTitle = $("#book_title").val();
 | 
			
		||||
    if (bookTitle) {
 | 
			
		||||
      $("#keyword").val(bookTitle);
 | 
			
		||||
      doSearch(bookTitle);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,237 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
 * bitstream.js
 | 
			
		||||
 *
 | 
			
		||||
 * Provides readers for bitstreams.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the MIT License
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright(c) 2011 Google Inc.
 | 
			
		||||
 * Copyright(c) 2011 antimatter15
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* global bitjs, Uint8Array */
 | 
			
		||||
 | 
			
		||||
var bitjs = bitjs || {};
 | 
			
		||||
bitjs.io = bitjs.io || {};
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
    // mask for getting the Nth bit (zero-based)
 | 
			
		||||
    bitjs.BIT = [0x01, 0x02, 0x04, 0x08,
 | 
			
		||||
        0x10, 0x20, 0x40, 0x80,
 | 
			
		||||
        0x100, 0x200, 0x400, 0x800,
 | 
			
		||||
        0x1000, 0x2000, 0x4000, 0x8000
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // mask for getting N number of bits (0-8)
 | 
			
		||||
    var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This bit stream peeks and consumes bits out of a binary stream.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
 | 
			
		||||
     * @param {boolean} rtl Whether the stream reads bits from the byte starting
 | 
			
		||||
     *     from bit 7 to 0 (true) or bit 0 to 7 (false).
 | 
			
		||||
     * @param {Number} optOffset The offset into the ArrayBuffer
 | 
			
		||||
     * @param {Number} optLength The length of this BitStream
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.BitStream = function(ab, rtl, optOffset, optLength) {
 | 
			
		||||
        if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
 | 
			
		||||
            throw "Error! BitArray constructed with an invalid ArrayBuffer object";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var offset = optOffset || 0;
 | 
			
		||||
        var length = optLength || ab.byteLength;
 | 
			
		||||
        this.bytes = new Uint8Array(ab, offset, length);
 | 
			
		||||
        this.bytePtr = 0; // tracks which byte we are on
 | 
			
		||||
        this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7)
 | 
			
		||||
        this.peekBits = rtl ? this.peekBitsRtl : this.peekBitsLtr;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *   byte0      byte1      byte2      byte3
 | 
			
		||||
     * 7......0 | 7......0 | 7......0 | 7......0
 | 
			
		||||
     *
 | 
			
		||||
     * The bit pointer starts at bit0 of byte0 and moves left until it reaches
 | 
			
		||||
     * bit7 of byte0, then jumps to bit0 of byte1, etc.
 | 
			
		||||
     * @param {number} n The number of bits to peek.
 | 
			
		||||
     * @param {boolean=} movePointers Whether to move the pointer, defaults false.
 | 
			
		||||
     * @return {number} The peeked bits, as an unsigned number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.BitStream.prototype.peekBitsLtr = function(n, movePointers) {
 | 
			
		||||
        if (n <= 0 || typeof n !== typeof 1) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var movePointers = movePointers || false,
 | 
			
		||||
            bytePtr = this.bytePtr,
 | 
			
		||||
            bitPtr = this.bitPtr,
 | 
			
		||||
            result = 0,
 | 
			
		||||
            bitsIn = 0,
 | 
			
		||||
            bytes = this.bytes;
 | 
			
		||||
 | 
			
		||||
        // keep going until we have no more bits left to peek at
 | 
			
		||||
        // TODO: Consider putting all bits from bytes we will need into a variable and then
 | 
			
		||||
        //       shifting/masking it to just extract the bits we want.
 | 
			
		||||
        //       This could be considerably faster when reading more than 3 or 4 bits at a time.
 | 
			
		||||
        while (n > 0) {
 | 
			
		||||
            if (bytePtr >= bytes.length) {
 | 
			
		||||
                throw "Error!  Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
 | 
			
		||||
                    bytes.length + ", bitPtr=" + bitPtr;
 | 
			
		||||
                // return -1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var numBitsLeftInThisByte = (8 - bitPtr);
 | 
			
		||||
            var mask;
 | 
			
		||||
            if (n >= numBitsLeftInThisByte) {
 | 
			
		||||
                mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
 | 
			
		||||
                result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
 | 
			
		||||
 | 
			
		||||
                bytePtr++;
 | 
			
		||||
                bitPtr = 0;
 | 
			
		||||
                bitsIn += numBitsLeftInThisByte;
 | 
			
		||||
                n -= numBitsLeftInThisByte;
 | 
			
		||||
            } else {
 | 
			
		||||
                mask = (BITMASK[n] << bitPtr);
 | 
			
		||||
                result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
 | 
			
		||||
 | 
			
		||||
                bitPtr += n;
 | 
			
		||||
                bitsIn += n;
 | 
			
		||||
                n = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (movePointers) {
 | 
			
		||||
            this.bitPtr = bitPtr;
 | 
			
		||||
            this.bytePtr = bytePtr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *   byte0      byte1      byte2      byte3
 | 
			
		||||
     * 7......0 | 7......0 | 7......0 | 7......0
 | 
			
		||||
     *
 | 
			
		||||
     * The bit pointer starts at bit7 of byte0 and moves right until it reaches
 | 
			
		||||
     * bit0 of byte0, then goes to bit7 of byte1, etc.
 | 
			
		||||
     * @param {number} n The number of bits to peek.
 | 
			
		||||
     * @param {boolean=} movePointers Whether to move the pointer, defaults false.
 | 
			
		||||
     * @return {number} The peeked bits, as an unsigned number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.BitStream.prototype.peekBitsRtl = function(n, movePointers) {
 | 
			
		||||
        if (n <= 0 || typeof n !== typeof 1) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var movePointers = movePointers || false,
 | 
			
		||||
            bytePtr = this.bytePtr,
 | 
			
		||||
            bitPtr = this.bitPtr,
 | 
			
		||||
            result = 0,
 | 
			
		||||
            bytes = this.bytes;
 | 
			
		||||
 | 
			
		||||
        // keep going until we have no more bits left to peek at
 | 
			
		||||
        // TODO: Consider putting all bits from bytes we will need into a variable and then
 | 
			
		||||
        //       shifting/masking it to just extract the bits we want.
 | 
			
		||||
        //       This could be considerably faster when reading more than 3 or 4 bits at a time.
 | 
			
		||||
        while (n > 0) {
 | 
			
		||||
 | 
			
		||||
            if (bytePtr >= bytes.length) {
 | 
			
		||||
                throw "Error!  Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
 | 
			
		||||
                    bytes.length + ", bitPtr=" + bitPtr;
 | 
			
		||||
                // return -1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var numBitsLeftInThisByte = (8 - bitPtr);
 | 
			
		||||
            if (n >= numBitsLeftInThisByte) {
 | 
			
		||||
                result <<= numBitsLeftInThisByte;
 | 
			
		||||
                result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]);
 | 
			
		||||
                bytePtr++;
 | 
			
		||||
                bitPtr = 0;
 | 
			
		||||
                n -= numBitsLeftInThisByte;
 | 
			
		||||
            } else {
 | 
			
		||||
                result <<= n;
 | 
			
		||||
                result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr));
 | 
			
		||||
 | 
			
		||||
                bitPtr += n;
 | 
			
		||||
                n = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (movePointers) {
 | 
			
		||||
            this.bitPtr = bitPtr;
 | 
			
		||||
            this.bytePtr = bytePtr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Peek at 16 bits from current position in the buffer.
 | 
			
		||||
     * Bit at (bytePtr,bitPtr) has the highest position in returning data.
 | 
			
		||||
     * Taken from getbits.hpp in unrar.
 | 
			
		||||
     * TODO: Move this out of BitStream and into unrar.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.BitStream.prototype.getBits = function() {
 | 
			
		||||
        return (((((this.bytes[this.bytePtr] & 0xff) << 16) +
 | 
			
		||||
            ((this.bytes[this.bytePtr + 1] & 0xff) << 8) +
 | 
			
		||||
            ((this.bytes[this.bytePtr + 2] & 0xff))) >>> (8 - this.bitPtr)) & 0xffff);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reads n bits out of the stream, consuming them (moving the bit pointer).
 | 
			
		||||
     * @param {number} n The number of bits to read.
 | 
			
		||||
     * @return {number} The read bits, as an unsigned number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.BitStream.prototype.readBits = function(n) {
 | 
			
		||||
        return this.peekBits(n, true);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This returns n bytes as a sub-array, advancing the pointer if movePointers
 | 
			
		||||
     * is true.  Only use this for uncompressed blocks as this throws away remaining
 | 
			
		||||
     * bits in the current byte.
 | 
			
		||||
     * @param {number} n The number of bytes to peek.
 | 
			
		||||
     * @param {boolean=} movePointers Whether to move the pointer, defaults false.
 | 
			
		||||
     * @return {Uint8Array} The subarray.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.BitStream.prototype.peekBytes = function(n, movePointers) {
 | 
			
		||||
        if (n <= 0 || typeof n !== typeof 1) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // from http://tools.ietf.org/html/rfc1951#page-11
 | 
			
		||||
        // "Any bits of input up to the next byte boundary are ignored."
 | 
			
		||||
        while (this.bitPtr !== 0) {
 | 
			
		||||
            this.readBits(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var movePointers = movePointers || false;
 | 
			
		||||
        var bytePtr = this.bytePtr;
 | 
			
		||||
        // bitPtr = this.bitPtr;
 | 
			
		||||
 | 
			
		||||
        var result = this.bytes.subarray(bytePtr, bytePtr + n);
 | 
			
		||||
 | 
			
		||||
        if (movePointers) {
 | 
			
		||||
            this.bytePtr += n;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @return {Uint8Array} The subarray.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.BitStream.prototype.readBytes = function(n) {
 | 
			
		||||
        return this.peekBytes(n, true);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			@ -1,124 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
 * bytestream.js
 | 
			
		||||
 *
 | 
			
		||||
 * Provides a writer for bytes.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the MIT License
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright(c) 2011 Google Inc.
 | 
			
		||||
 * Copyright(c) 2011 antimatter15
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* global bitjs, Uint8Array */
 | 
			
		||||
 | 
			
		||||
var bitjs = bitjs || {};
 | 
			
		||||
bitjs.io = bitjs.io || {};
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store.
 | 
			
		||||
     * @param {number} numBytes The number of bytes to allocate.
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteBuffer = function(numBytes) {
 | 
			
		||||
        if (typeof numBytes !== typeof 1 || numBytes <= 0) {
 | 
			
		||||
            throw "Error! ByteBuffer initialized with '" + numBytes + "'";
 | 
			
		||||
        }
 | 
			
		||||
        this.data = new Uint8Array(numBytes);
 | 
			
		||||
        this.ptr = 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {number} b The byte to insert.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteBuffer.prototype.insertByte = function(b) {
 | 
			
		||||
        // TODO: throw if byte is invalid?
 | 
			
		||||
        this.data[this.ptr++] = b;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Array.<number>|Uint8Array|Int8Array} bytes The bytes to insert.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) {
 | 
			
		||||
        // TODO: throw if bytes is invalid?
 | 
			
		||||
        this.data.set(bytes, this.ptr);
 | 
			
		||||
        this.ptr += bytes.length;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes an unsigned number into the next n bytes.  If the number is too large
 | 
			
		||||
     * to fit into n bytes or is negative, an error is thrown.
 | 
			
		||||
     * @param {number} num The unsigned number to write.
 | 
			
		||||
     * @param {number} numBytes The number of bytes to write the number into.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteBuffer.prototype.writeNumber = function(num, numBytes) {
 | 
			
		||||
        if (numBytes < 1) {
 | 
			
		||||
            throw "Trying to write into too few bytes: " + numBytes;
 | 
			
		||||
        }
 | 
			
		||||
        if (num < 0) {
 | 
			
		||||
            throw "Trying to write a negative number (" + num +
 | 
			
		||||
                ") as an unsigned number to an ArrayBuffer";
 | 
			
		||||
        }
 | 
			
		||||
        if (num > (Math.pow(2, numBytes * 8) - 1)) {
 | 
			
		||||
            throw "Trying to write " + num + " into only " + numBytes + " bytes";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Roll 8-bits at a time into an array of bytes.
 | 
			
		||||
        var bytes = [];
 | 
			
		||||
        while (numBytes-- > 0) {
 | 
			
		||||
            var eightBits = num & 255;
 | 
			
		||||
            bytes.push(eightBits);
 | 
			
		||||
            num >>= 8;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.insertBytes(bytes);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a signed number into the next n bytes.  If the number is too large
 | 
			
		||||
     * to fit into n bytes, an error is thrown.
 | 
			
		||||
     * @param {number} num The signed number to write.
 | 
			
		||||
     * @param {number} numBytes The number of bytes to write the number into.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteBuffer.prototype.writeSignedNumber = function(num, numBytes) {
 | 
			
		||||
        if (numBytes < 1) {
 | 
			
		||||
            throw "Trying to write into too few bytes: " + numBytes;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var HALF = Math.pow(2, (numBytes * 8) - 1);
 | 
			
		||||
        if (num >= HALF || num < -HALF) {
 | 
			
		||||
            throw "Trying to write " + num + " into only " + numBytes + " bytes";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Roll 8-bits at a time into an array of bytes.
 | 
			
		||||
        var bytes = [];
 | 
			
		||||
        while (numBytes-- > 0) {
 | 
			
		||||
            var eightBits = num & 255;
 | 
			
		||||
            bytes.push(eightBits);
 | 
			
		||||
            num >>= 8;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.insertBytes(bytes);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {string} str The ASCII string to write.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteBuffer.prototype.writeASCIIString = function(str) {
 | 
			
		||||
        for (var i = 0; i < str.length; ++i) {
 | 
			
		||||
            var curByte = str.charCodeAt(i);
 | 
			
		||||
            if (curByte < 0 || curByte > 255) {
 | 
			
		||||
                throw "Trying to write a non-ASCII string!";
 | 
			
		||||
            }
 | 
			
		||||
            this.insertByte(curByte);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			@ -1,195 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
 * bytestream.js
 | 
			
		||||
 *
 | 
			
		||||
 * Provides readers for byte streams.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the MIT License
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright(c) 2011 Google Inc.
 | 
			
		||||
 * Copyright(c) 2011 antimatter15
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* global bitjs, Uint8Array */
 | 
			
		||||
 | 
			
		||||
var bitjs = bitjs || {};
 | 
			
		||||
bitjs.io = bitjs.io || {};
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This object allows you to peek and consume bytes as numbers and strings
 | 
			
		||||
     * out of an ArrayBuffer.  In this buffer, everything must be byte-aligned.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {ArrayBuffer} ab The ArrayBuffer object.
 | 
			
		||||
     * @param {number=} optOffset The offset into the ArrayBuffer
 | 
			
		||||
     * @param {number=} optLength The length of this BitStream
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream = function(ab, optOffset, optLength) {
 | 
			
		||||
        var offset = optOffset || 0;
 | 
			
		||||
        var length = optLength || ab.byteLength;
 | 
			
		||||
        this.bytes = new Uint8Array(ab, offset, length);
 | 
			
		||||
        this.ptr = 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Peeks at the next n bytes as an unsigned number but does not advance the
 | 
			
		||||
     * pointer
 | 
			
		||||
     * TODO: This apparently cannot read more than 4 bytes as a number?
 | 
			
		||||
     * @param {number} n The number of bytes to peek at.
 | 
			
		||||
     * @return {number} The n bytes interpreted as an unsigned number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.peekNumber = function(n) {
 | 
			
		||||
        // TODO: return error if n would go past the end of the stream?
 | 
			
		||||
        if (n <= 0 || typeof n !== typeof 1) {
 | 
			
		||||
            return -1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var result = 0;
 | 
			
		||||
        // read from last byte to first byte and roll them in
 | 
			
		||||
        var curByte = this.ptr + n - 1;
 | 
			
		||||
        while (curByte >= this.ptr) {
 | 
			
		||||
            result <<= 8;
 | 
			
		||||
            result |= this.bytes[curByte];
 | 
			
		||||
            --curByte;
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the next n bytes as an unsigned number (or -1 on error)
 | 
			
		||||
     * and advances the stream pointer n bytes.
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @return {number} The n bytes interpreted as an unsigned number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.readNumber = function(n) {
 | 
			
		||||
        var num = this.peekNumber(n);
 | 
			
		||||
        this.ptr += n;
 | 
			
		||||
        return num;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the next n bytes as a signed number but does not advance the
 | 
			
		||||
     * pointer.
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @return {number} The bytes interpreted as a signed number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.peekSignedNumber = function(n) {
 | 
			
		||||
        var num = this.peekNumber(n);
 | 
			
		||||
        var HALF = Math.pow(2, (n * 8) - 1);
 | 
			
		||||
        var FULL = HALF * 2;
 | 
			
		||||
 | 
			
		||||
        if (num >= HALF) num -= FULL;
 | 
			
		||||
 | 
			
		||||
        return num;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the next n bytes as a signed number and advances the stream pointer.
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @return {number} The bytes interpreted as a signed number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.readSignedNumber = function(n) {
 | 
			
		||||
        var num = this.peekSignedNumber(n);
 | 
			
		||||
        this.ptr += n;
 | 
			
		||||
        return num;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * ToDo: Returns the next n bytes as a signed number and advances the stream pointer.
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @return {number} The bytes interpreted as a signed number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.movePointer = function(n) {
 | 
			
		||||
        this.ptr += n;
 | 
			
		||||
        // end of buffer reached
 | 
			
		||||
        if ((this.bytes.byteLength - this.ptr) < 0 ) {
 | 
			
		||||
            this.ptr =  this.bytes.byteLength;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * ToDo: Returns the next n bytes as a signed number and advances the stream pointer.
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @return {number} The bytes interpreted as a signed number.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.moveTo = function(n) {
 | 
			
		||||
        if ( n < 0 ) {
 | 
			
		||||
            n = 0;
 | 
			
		||||
        }
 | 
			
		||||
        this.ptr = n;
 | 
			
		||||
        // end of buffer reached
 | 
			
		||||
        if ((this.bytes.byteLength - this.ptr) < 0 ) {
 | 
			
		||||
            this.ptr =  this.bytes.byteLength;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This returns n bytes as a sub-array, advancing the pointer if movePointers
 | 
			
		||||
     * is true.
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @param {boolean} movePointers Whether to move the pointers.
 | 
			
		||||
     * @return {Uint8Array} The subarray.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) {
 | 
			
		||||
        if (n <= 0 || typeof n !== typeof 1) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var result = this.bytes.subarray(this.ptr, this.ptr + n);
 | 
			
		||||
 | 
			
		||||
        if (movePointers) {
 | 
			
		||||
            this.ptr += n;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reads the next n bytes as a sub-array.
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @return {Uint8Array} The subarray.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.readBytes = function(n) {
 | 
			
		||||
        return this.peekBytes(n, true);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Peeks at the next n bytes as a string but does not advance the pointer.
 | 
			
		||||
     * @param {number} n The number of bytes to peek at.
 | 
			
		||||
     * @return {string} The next n bytes as a string.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.peekString = function(n) {
 | 
			
		||||
        if (n <= 0 || typeof n !== typeof 1) {
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var result = "";
 | 
			
		||||
        for (var p = this.ptr, end = this.ptr + n; p < end; ++p) {
 | 
			
		||||
            result += String.fromCharCode(this.bytes[p]);
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the next n bytes as an ASCII string and advances the stream pointer
 | 
			
		||||
     * n bytes.
 | 
			
		||||
     * @param {number} n The number of bytes to read.
 | 
			
		||||
     * @return {string} The next n bytes as a string.
 | 
			
		||||
     */
 | 
			
		||||
    bitjs.io.ByteStream.prototype.readString = function(n) {
 | 
			
		||||
        var strToReturn = this.peekString(n);
 | 
			
		||||
        this.ptr += n;
 | 
			
		||||
        return strToReturn;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +15,7 @@
 | 
			
		|||
  * Typed Arrays: http://www.khronos.org/registry/typedarray/specs/latest/#6
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
/* global screenfull, bitjs, Uint8Array, opera */
 | 
			
		||||
/* global screenfull, bitjs, Uint8Array, opera, loadArchiveFormats, archiveOpenFile */
 | 
			
		||||
/* exported init, event */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -104,9 +104,8 @@ kthoom.setSettings = function() {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
var createURLFromArray = function(array, mimeType) {
 | 
			
		||||
    var offset = array.byteOffset;
 | 
			
		||||
    var offset = 0; // array.byteOffset;
 | 
			
		||||
    var len = array.byteLength;
 | 
			
		||||
    // var url;
 | 
			
		||||
    var blob;
 | 
			
		||||
 | 
			
		||||
    if (mimeType === "image/xml+svg") {
 | 
			
		||||
| 
						 | 
				
			
			@ -166,93 +165,61 @@ kthoom.ImageFile = function(file) {
 | 
			
		|||
    }
 | 
			
		||||
    if ( this.mimeType !== undefined) {
 | 
			
		||||
        this.dataURI = createURLFromArray(file.fileData, this.mimeType);
 | 
			
		||||
        this.data = file;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function initProgressClick() {
 | 
			
		||||
    $("#progress").click(function(e) {
 | 
			
		||||
        var offset = $(this).offset();
 | 
			
		||||
        var x = e.pageX - offset.left;
 | 
			
		||||
        var rate = settings.direction === 0 ? x / $(this).width() : 1 - x / $(this).width();
 | 
			
		||||
        var page = Math.max(1, Math.ceil(rate * totalImages)) - 1;
 | 
			
		||||
        currentImage = page;
 | 
			
		||||
        currentImage = Math.max(1, Math.ceil(rate * totalImages)) - 1;
 | 
			
		||||
        updatePage();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loadFromArrayBuffer(ab) {
 | 
			
		||||
    var start = (new Date).getTime();
 | 
			
		||||
    var h = new Uint8Array(ab, 0, 10);
 | 
			
		||||
    var pathToBitJS = "../../static/js/archive/";
 | 
			
		||||
    var lastCompletion = 0;
 | 
			
		||||
    if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { //Rar!
 | 
			
		||||
        unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS);
 | 
			
		||||
    } else if (h[0] === 80 && h[1] === 75) { //PK (Zip)
 | 
			
		||||
        unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS);
 | 
			
		||||
    } else if (h[0] === 255 && h[1] === 216) { // JPEG
 | 
			
		||||
        // ToDo: check
 | 
			
		||||
        updateProgress(100);
 | 
			
		||||
        lastCompletion = 100;
 | 
			
		||||
        return;
 | 
			
		||||
    } else { // Try with tar
 | 
			
		||||
        unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
 | 
			
		||||
    }
 | 
			
		||||
    // Listen for UnarchiveEvents.
 | 
			
		||||
    if (unarchiver) {
 | 
			
		||||
        unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS,
 | 
			
		||||
            function(e) {
 | 
			
		||||
                var percentage = e.currentBytesUnarchived / e.totalUncompressedBytesInArchive;
 | 
			
		||||
                if (totalImages === 0) {
 | 
			
		||||
                    totalImages = e.totalFilesInArchive;
 | 
			
		||||
                }
 | 
			
		||||
                updateProgress(percentage * 100);
 | 
			
		||||
                lastCompletion = percentage * 100;
 | 
			
		||||
            });
 | 
			
		||||
        unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.INFO,
 | 
			
		||||
            function(e) {
 | 
			
		||||
                // console.log(e.msg);  // Enable debug output here
 | 
			
		||||
            });
 | 
			
		||||
        unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT,
 | 
			
		||||
            function(e) {
 | 
			
		||||
                // convert DecompressedFile into a bunch of ImageFiles
 | 
			
		||||
                if (e.unarchivedFile) {
 | 
			
		||||
                    var f = e.unarchivedFile;
 | 
			
		||||
                    // add any new pages based on the filename
 | 
			
		||||
                    if (imageFilenames.indexOf(f.filename) === -1) {
 | 
			
		||||
                        var test = new kthoom.ImageFile(f);
 | 
			
		||||
                        if ( test.mimeType !== undefined) {
 | 
			
		||||
                            imageFilenames.push(f.filename);
 | 
			
		||||
                            imageFiles.push(test);
 | 
			
		||||
                            // add thumbnails to the TOC list
 | 
			
		||||
                            $("#thumbnails").append(
 | 
			
		||||
                                "<li>" +
 | 
			
		||||
                                    "<a data-page='" + imageFiles.length + "'>" +
 | 
			
		||||
    loadArchiveFormats(['rar', 'zip', 'tar'], function() {
 | 
			
		||||
        // Open the file as an archive
 | 
			
		||||
        archiveOpenFile(ab, function (archive) {
 | 
			
		||||
            if (archive) {
 | 
			
		||||
                totalImages = archive.entries.length
 | 
			
		||||
                console.info('Uncompressing ' + archive.archive_type + ' ...');
 | 
			
		||||
                archive.entries.forEach(function(e, i) {
 | 
			
		||||
                    updateProgress( (i + 1)/ totalImages * 100);
 | 
			
		||||
                    if (e.is_file) {
 | 
			
		||||
                        e.readData(function(d) {
 | 
			
		||||
                            // add any new pages based on the filename
 | 
			
		||||
                            if (imageFilenames.indexOf(e.name) === -1) {
 | 
			
		||||
                                let data = {filename: e.name, fileData: d};
 | 
			
		||||
                                var test = new kthoom.ImageFile(data);
 | 
			
		||||
                                if (test.mimeType !== undefined) {
 | 
			
		||||
                                    imageFilenames.push(e.name);
 | 
			
		||||
                                    imageFiles.push(test);
 | 
			
		||||
                                    // add thumbnails to the TOC list
 | 
			
		||||
                                    $("#thumbnails").append(
 | 
			
		||||
                                        "<li>" +
 | 
			
		||||
                                        "<a data-page='" + imageFiles.length + "'>" +
 | 
			
		||||
                                        "<img src='" + imageFiles[imageFiles.length - 1].dataURI + "'/>" +
 | 
			
		||||
                                        "<span>" + imageFiles.length + "</span>" +
 | 
			
		||||
                                    "</a>" +
 | 
			
		||||
                                "</li>"
 | 
			
		||||
                            );
 | 
			
		||||
                            // display first page if we haven't yet
 | 
			
		||||
                            if (imageFiles.length === currentImage + 1) {
 | 
			
		||||
                                updatePage(lastCompletion);
 | 
			
		||||
                                        "</a>" +
 | 
			
		||||
                                        "</li>"
 | 
			
		||||
                                    );
 | 
			
		||||
                                    // display first page if we haven't yet
 | 
			
		||||
                                    if (imageFiles.length === currentImage + 1) {
 | 
			
		||||
                                        updatePage(lastCompletion);
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    totalImages--;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            totalImages--;
 | 
			
		||||
                        }
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.FINISH,
 | 
			
		||||
            function() {
 | 
			
		||||
                var diff = ((new Date).getTime() - start) / 1000;
 | 
			
		||||
                console.log("Unarchiving done in " + diff + "s");
 | 
			
		||||
            });
 | 
			
		||||
        unarchiver.start();
 | 
			
		||||
    } else {
 | 
			
		||||
        alert("Some error");
 | 
			
		||||
    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function scrollTocToActive() {
 | 
			
		||||
| 
						 | 
				
			
			@ -317,7 +284,6 @@ function updateProgress(loadPercentage) {
 | 
			
		|||
                .find(".load").text("");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set page progress bar
 | 
			
		||||
    $("#progress .bar-read").css({ width: totalImages === 0 ? 0 : Math.round((currentImage + 1) / totalImages * 100) + "%"});
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -553,10 +519,6 @@ function keyHandler(evt) {
 | 
			
		|||
            updateScale(false);
 | 
			
		||||
            break;
 | 
			
		||||
        case kthoom.Key.SPACE:
 | 
			
		||||
            var container = $("#mainContent");
 | 
			
		||||
            // var atTop = container.scrollTop() === 0;
 | 
			
		||||
            // var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
 | 
			
		||||
 | 
			
		||||
            if (evt.shiftKey) {
 | 
			
		||||
                evt.preventDefault();
 | 
			
		||||
                // If it's Shift + Space and the container is at the top of the page
 | 
			
		||||
| 
						 | 
				
			
			@ -573,33 +535,11 @@ function keyHandler(evt) {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*function ImageLoadCallback() {
 | 
			
		||||
    var jso = this.response;
 | 
			
		||||
    // Unable to decompress file, or no response from server
 | 
			
		||||
    if (jso === null) {
 | 
			
		||||
        setImage("error");
 | 
			
		||||
    } else {
 | 
			
		||||
        // IE 11 sometimes sees the response as a string
 | 
			
		||||
        if (typeof jso !== "object") {
 | 
			
		||||
            jso = JSON.parse(jso);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (jso.page !== jso.last) {
 | 
			
		||||
            this.open("GET", this.fileid + "/" + (jso.page + 1));
 | 
			
		||||
            this.addEventListener("load", ImageLoadCallback);
 | 
			
		||||
            this.send();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        loadFromArrayBuffer(jso);
 | 
			
		||||
    }
 | 
			
		||||
}*/
 | 
			
		||||
function init(filename) {
 | 
			
		||||
    var request = new XMLHttpRequest();
 | 
			
		||||
    request.open("GET", filename);
 | 
			
		||||
    request.responseType = "arraybuffer";
 | 
			
		||||
    request.setRequestHeader("X-Test", "test1");
 | 
			
		||||
    request.setRequestHeader("X-Test", "test2");
 | 
			
		||||
    request.addEventListener("load", function(event) {
 | 
			
		||||
    request.addEventListener("load", function() {
 | 
			
		||||
        if (request.status >= 200 && request.status < 300) {
 | 
			
		||||
            loadFromArrayBuffer(request.response);
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -580,12 +580,19 @@ function singleUserFormatter(value, row) {
 | 
			
		|||
    return '<a class="btn btn-default" onclick="storeLocation()" href="' + window.location.pathname + '/../../admin/user/' + row.id + '">' + this.buttontext + '</a>'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function checkboxFormatter(value, row, index){
 | 
			
		||||
function checkboxFormatter(value, row){
 | 
			
		||||
    if(value & this.column)
 | 
			
		||||
        return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" checked onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', ' + this.column + ')">';
 | 
			
		||||
    else
 | 
			
		||||
        return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', ' + this.column + ')">';
 | 
			
		||||
}
 | 
			
		||||
function singlecheckboxFormatter(value, row){
 | 
			
		||||
    if(value)
 | 
			
		||||
        return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" checked onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', 0)">';
 | 
			
		||||
    else
 | 
			
		||||
        return '<input type="checkbox" class="chk" data-pk="' + row.id + '" data-name="' + this.field + '" onchange="checkboxChange(this, ' + row.id + ', \'' + this.name + '\', 0)">';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Do some hiding disabling after user list is loaded */
 | 
			
		||||
function loadSuccess() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -124,15 +124,24 @@
 | 
			
		|||
        error: function(xhr) {
 | 
			
		||||
            this.$modalTitle.text(this.options.modalTitleFailed);
 | 
			
		||||
 | 
			
		||||
            this.setProgress(100);
 | 
			
		||||
            this.$modalBar.removeClass("progress-bar-success");
 | 
			
		||||
            this.$modalBar.addClass("progress-bar-danger");
 | 
			
		||||
            this.$modalFooter.show();
 | 
			
		||||
 | 
			
		||||
            var contentType = xhr.getResponseHeader("Content-Type");
 | 
			
		||||
            // Write the error response to the document.
 | 
			
		||||
            if (contentType || xhr.status === 422) {
 | 
			
		||||
            if (xhr.status === 502 || xhr.status === 0) {
 | 
			
		||||
                if (xhr.statusText) {
 | 
			
		||||
                    this.$modalBar.text(xhr.statusText + ": File size may be too big");
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.$modalBar.text("Error: File size may be too big");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            else if (contentType || xhr.status === 422) {
 | 
			
		||||
                var responseText = xhr.responseText;
 | 
			
		||||
                if (contentType.indexOf("text/plain") !== -1) {
 | 
			
		||||
                if (contentType.indexOf("text/plain") === -1) {
 | 
			
		||||
                    responseText = "<pre>" + responseText + "</pre>";
 | 
			
		||||
                    document.write(responseText);
 | 
			
		||||
                } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -246,6 +246,10 @@
 | 
			
		|||
 | 
			
		||||
          <input type="checkbox" id="show-comics" class="pill" data-control="comicvine" checked>
 | 
			
		||||
          <label for="show-comics">ComicVine <span class="glyphicon glyphicon-ok"></span></label>
 | 
			
		||||
 | 
			
		||||
          <input type="checkbox" id="show-googlescholar" class="pill" data-control="googlescholar" checked>
 | 
			
		||||
          <label for="show-googlescholar">Google Scholar <span class="glyphicon glyphicon-ok"></span></label>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div id="meta-info">
 | 
			
		||||
| 
						 | 
				
			
			@ -267,7 +271,7 @@
 | 
			
		|||
    <img class="pull-left img-responsive"
 | 
			
		||||
         data-toggle="modal"
 | 
			
		||||
         data-target="#metaModal"
 | 
			
		||||
         src="<%= cover %>"
 | 
			
		||||
         src="<%= cover || "{{ url_for('static', filename='img/academicpaper.svg') }}"  %>"
 | 
			
		||||
         alt="Cover"
 | 
			
		||||
    >
 | 
			
		||||
    <div class="media-body">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,7 @@
 | 
			
		|||
              <div class="btn-group" role="group">
 | 
			
		||||
              {% if reader_list|length > 1 %}
 | 
			
		||||
                <button id="read-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
 | 
			
		||||
                  <span class="glyphicon glyphicon-eye-open"></span> {{_('Read in Browser')}}
 | 
			
		||||
                  <span class="glyphicon glyphicon-book"></span> {{_('Read in Browser')}}
 | 
			
		||||
                  <span class="caret"></span>
 | 
			
		||||
                </button>
 | 
			
		||||
                    <ul class="dropdown-menu" aria-labelledby="read-in-browser">
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +66,7 @@
 | 
			
		|||
                    {%endfor%}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                {% else %}
 | 
			
		||||
                  <a target="_blank" href="{{url_for('web.read_book', book_id=entry.id, book_format=reader_list[0])}}" id="readbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-eye-open"></span> {{_('Read in Browser')}} - {{reader_list[0]}}</a>
 | 
			
		||||
                  <a target="_blank" href="{{url_for('web.read_book', book_id=entry.id, book_format=reader_list[0])}}" id="readbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-book"></span> {{_('Read in Browser')}} - {{reader_list[0]}}</a>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
              </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,8 +15,8 @@
 | 
			
		|||
  <script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
 | 
			
		||||
  <script src="{{ url_for('static', filename='js/libs/plugins.js') }}"></script>
 | 
			
		||||
  <script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
 | 
			
		||||
  <script src="{{ url_for('static', filename='js/compress/uncompress.js') }}"></script>
 | 
			
		||||
  <script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
 | 
			
		||||
  <script src="{{ url_for('static', filename='js/archive/archive.js') }}"></script>
 | 
			
		||||
  <script>
 | 
			
		||||
    var updateArrows = function() {
 | 
			
		||||
      if ($('input[name="direction"]:checked').val() === "0") {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,13 @@
 | 
			
		|||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {% if kobo_sync_enabled and sync_only_selected_shelves %}
 | 
			
		||||
      <div class="checkbox">
 | 
			
		||||
          <label> <input type="checkbox" name="kobo_sync" {% if shelf.kobo_sync == 1 %}checked{% endif %}>
 | 
			
		||||
              {{ _('Sync this shelf with Kobo device') }}
 | 
			
		||||
          </label>
 | 
			
		||||
      </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <button type="submit" class="btn btn-default" id="submit">{{_('Save')}}</button>
 | 
			
		||||
    {% if shelf.id != None %}
 | 
			
		||||
      <a href="{{ url_for('shelf.show_shelf', shelf_id=shelf.id) }}" class="btn btn-default">{{_('Cancel')}}</a>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,7 +66,6 @@
 | 
			
		|||
      <div class="btn btn-danger" id="config_delete_kobo_token" data-value="{{ content.id }}" data-remote="false" {% if not content.remote_auth_token.first() %} style="display: none;" {% endif %}>{{_('Delete')}}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col-sm-6">
 | 
			
		||||
        {% for element in sidebar %}
 | 
			
		||||
          {% if element['config_show'] %}
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +124,12 @@
 | 
			
		|||
        </div>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {% if kobo_support and not content.role_anonymous() %}
 | 
			
		||||
    <div class="form-group">
 | 
			
		||||
      <input type="checkbox" name="kobo_only_shelves_sync" id="kobo_only_shelves_sync" {% if content.kobo_only_shelves_sync %}checked{% endif %}>
 | 
			
		||||
      <label for="kobo_only_shelves_sync">{{_('Sync only books in selected shelves with Kobo')}}</label>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
      <div class="col-sm-12">
 | 
			
		||||
    <div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,21 @@
 | 
			
		|||
</th>
 | 
			
		||||
{%- endmacro %}
 | 
			
		||||
 | 
			
		||||
{% macro user_single_checkbox_row(parameter, show_text) -%}
 | 
			
		||||
<th data-name="{{parameter}}" data-field="{{parameter}}"
 | 
			
		||||
    data-formatter="singlecheckboxFormatter">
 | 
			
		||||
    <div class="form-check">
 | 
			
		||||
    <div>
 | 
			
		||||
        <input type="radio" class="check_head" data-set="false" data-val="0" name="{{parameter}}" id="false_{{parameter}}" data-name="{{parameter}}" disabled>{{_('Deny')}}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
        <input type="radio" class="check_head" data-set="true" data-val="1" name="{{parameter}}" data-name="{{parameter}}" disabled>{{_('Allow')}}
 | 
			
		||||
    </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{show_text}}
 | 
			
		||||
</th>
 | 
			
		||||
{%- endmacro %}
 | 
			
		||||
 | 
			
		||||
{% macro user_checkbox_row(parameter, array_field, show_text, element, value) -%}
 | 
			
		||||
<th data-name="{{array_field}}" data-field="{{parameter}}"
 | 
			
		||||
    data-visible="{{element.get(array_field)}}"
 | 
			
		||||
| 
						 | 
				
			
			@ -39,14 +54,10 @@
 | 
			
		|||
    data-formatter="checkboxFormatter">
 | 
			
		||||
    <div  class="form-check">
 | 
			
		||||
    <div>
 | 
			
		||||
 | 
			
		||||
        <input type="radio" class="check_head" data-set="false" data-val={{value.get(array_field)}} name="options_{{array_field}}" id="false_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Deny')}}
 | 
			
		||||
 | 
			
		||||
        <input type="radio" class="check_head" data-set="false" data-val="{{value.get(array_field)}}" name="options_{{array_field}}" id="false_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Deny')}}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
 | 
			
		||||
        <input type="radio" class="check_head" data-set="true" data-val={{value.get(array_field)}} name="options_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Allow')}}
 | 
			
		||||
 | 
			
		||||
        <input type="radio" class="check_head" data-set="true" data-val="{{value.get(array_field)}}" name="options_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Allow')}}
 | 
			
		||||
    </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{show_text}}
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +145,10 @@
 | 
			
		|||
            {{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}}
 | 
			
		||||
            {{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}}
 | 
			
		||||
            {{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}}
 | 
			
		||||
            {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}}
 | 
			
		||||
            {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelves'), visiblility, all_roles)}}
 | 
			
		||||
            {%  if kobo_support %}
 | 
			
		||||
            {{ user_single_checkbox_row("kobo_only_shelves_sync", _('Sync Selected Shelves with Kobo'))}}
 | 
			
		||||
            {%  endif %}
 | 
			
		||||
            {{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}}
 | 
			
		||||
            {{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}}
 | 
			
		||||
            {{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show read/unread selection'), visiblility, sidebar_settings)}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								cps/ub.py
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								cps/ub.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -188,7 +188,7 @@ class User(UserBase, Base):
 | 
			
		|||
    allowed_column_value = Column(String, default="")
 | 
			
		||||
    remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic')
 | 
			
		||||
    view_settings = Column(JSON, default={})
 | 
			
		||||
 | 
			
		||||
    kobo_only_shelves_sync = Column(Integer, default=1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if oauth_support:
 | 
			
		||||
| 
						 | 
				
			
			@ -229,6 +229,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
 | 
			
		|||
        self.denied_column_value = data.denied_column_value
 | 
			
		||||
        self.allowed_column_value = data.allowed_column_value
 | 
			
		||||
        self.view_settings = data.view_settings
 | 
			
		||||
        self.kobo_only_shelves_sync = data.kobo_only_shelves_sync
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def role_admin(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -270,6 +271,7 @@ class Shelf(Base):
 | 
			
		|||
    name = Column(String)
 | 
			
		||||
    is_public = Column(Integer, default=0)
 | 
			
		||||
    user_id = Column(Integer, ForeignKey('user.id'))
 | 
			
		||||
    kobo_sync = Column(Boolean, default=False)
 | 
			
		||||
    books = relationship("BookShelf", backref="ub_shelf", cascade="all, delete-orphan", lazy="dynamic")
 | 
			
		||||
    created = Column(DateTime, default=datetime.datetime.utcnow)
 | 
			
		||||
    last_modified = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
 | 
			
		||||
| 
						 | 
				
			
			@ -502,6 +504,7 @@ def migrate_shelfs(engine, session):
 | 
			
		|||
            conn.execute("ALTER TABLE shelf ADD column 'created' DATETIME")
 | 
			
		||||
            conn.execute("ALTER TABLE shelf ADD column 'last_modified' DATETIME")
 | 
			
		||||
            conn.execute("ALTER TABLE book_shelf_link ADD column 'date_added' DATETIME")
 | 
			
		||||
            conn.execute("ALTER TABLE shelf ADD column 'kobo_sync' BOOLEAN DEFAULT false")
 | 
			
		||||
        for shelf in session.query(Shelf).all():
 | 
			
		||||
            shelf.uuid = str(uuid.uuid4())
 | 
			
		||||
            shelf.created = datetime.datetime.now()
 | 
			
		||||
| 
						 | 
				
			
			@ -509,6 +512,15 @@ def migrate_shelfs(engine, session):
 | 
			
		|||
        for book_shelf in session.query(BookShelf).all():
 | 
			
		||||
            book_shelf.date_added = datetime.datetime.now()
 | 
			
		||||
        session.commit()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        session.query(exists().where(Shelf.kobo_sync)).scalar()
 | 
			
		||||
    except exc.OperationalError:
 | 
			
		||||
        with engine.connect() as conn:
 | 
			
		||||
 | 
			
		||||
            conn.execute("ALTER TABLE shelf ADD column 'kobo_sync' BOOLEAN DEFAULT false")
 | 
			
		||||
        session.commit()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        session.query(exists().where(BookShelf.order)).scalar()
 | 
			
		||||
    except exc.OperationalError:  # Database is not compatible, some columns are missing
 | 
			
		||||
| 
						 | 
				
			
			@ -592,6 +604,13 @@ def migrate_Database(session):
 | 
			
		|||
        with engine.connect() as conn:
 | 
			
		||||
            conn.execute("ALTER TABLE user ADD column `view_settings` VARCHAR(10) DEFAULT '{}'")
 | 
			
		||||
        session.commit()
 | 
			
		||||
    try:
 | 
			
		||||
        session.query(exists().where(User.kobo_only_shelves_sync)).scalar()
 | 
			
		||||
    except exc.OperationalError:
 | 
			
		||||
        with engine.connect() as conn:
 | 
			
		||||
            conn.execute("ALTER TABLE user ADD column `kobo_only_shelves_sync` SMALLINT DEFAULT 0")
 | 
			
		||||
        session.commit()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        # check if name is in User table instead of nickname
 | 
			
		||||
        session.query(exists().where(User.name)).scalar()
 | 
			
		||||
| 
						 | 
				
			
			@ -611,15 +630,16 @@ def migrate_Database(session):
 | 
			
		|||
                     "allowed_tags VARCHAR,"
 | 
			
		||||
                     "denied_column_value VARCHAR,"
 | 
			
		||||
                     "allowed_column_value VARCHAR,"
 | 
			
		||||
                     "view_settings JSON,"         
 | 
			
		||||
                     "view_settings JSON,"
 | 
			
		||||
                     "kobo_only_shelves_sync SMALLINT,"                              
 | 
			
		||||
                     "UNIQUE (name),"
 | 
			
		||||
                     "UNIQUE (email))"))
 | 
			
		||||
            conn.execute(text("INSERT INTO user_id(id, name, email, role, password, kindle_mail,locale,"
 | 
			
		||||
                     "sidebar_view, default_language, denied_tags, allowed_tags, denied_column_value, "
 | 
			
		||||
                     "allowed_column_value, view_settings)"
 | 
			
		||||
                     "allowed_column_value, view_settings, kobo_only_shelves_sync)"
 | 
			
		||||
                     "SELECT id, nickname, email, role, password, kindle_mail, locale,"
 | 
			
		||||
                     "sidebar_view, default_language, denied_tags, allowed_tags, denied_column_value, "
 | 
			
		||||
                     "allowed_column_value, view_settings FROM user"))
 | 
			
		||||
                     "allowed_column_value, view_settings, kobo_only_shelves_sync FROM user"))
 | 
			
		||||
            # delete old user table and rename new user_id table to user:
 | 
			
		||||
            conn.execute(text("DROP TABLE user"))
 | 
			
		||||
            conn.execute(text("ALTER TABLE user_id RENAME TO user"))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1593,6 +1593,8 @@ def change_profile(kobo_support, local_oauth_check, oauth_status, translations,
 | 
			
		|||
            current_user.default_language = to_save["default_language"]
 | 
			
		||||
        if to_save.get("locale"):
 | 
			
		||||
            current_user.locale = to_save["locale"]
 | 
			
		||||
        current_user.kobo_only_shelves_sync = int(to_save.get("kobo_only_shelves_sync") == "on") or 0
 | 
			
		||||
 | 
			
		||||
    except Exception as ex:
 | 
			
		||||
        flash(str(ex), category="error")
 | 
			
		||||
        return render_title_template("user_edit.html", content=current_user,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,7 @@ SQLAlchemy-Utils>=0.33.5,<0.38.0
 | 
			
		|||
# extracting metadata
 | 
			
		||||
lxml>=3.8.0,<4.7.0
 | 
			
		||||
rarfile>=2.7
 | 
			
		||||
scholarly>=1.2.0, <1.3
 | 
			
		||||
 | 
			
		||||
# other
 | 
			
		||||
natsort>=2.2.0,<7.2.0
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user