diff --git a/app/config.py b/app/config.py index 23dacdf..c20bdca 100644 --- a/app/config.py +++ b/app/config.py @@ -97,6 +97,7 @@ class Config(object): PLAY_SETTLE = 500000 PLAYLIST_ICON_CURRENT = ":/icons/green-circle.png" PLAYLIST_ICON_NEXT = ":/icons/yellow-circle.png" + PLAYLIST_ICON_TEMPLATE = ":/icons/redstar.png" PREVIEW_ADVANCE_MS = 5000 PREVIEW_BACK_MS = 5000 PREVIEW_END_BUFFER_MS = 1000 diff --git a/app/dbtables.py b/app/dbtables.py index 9781f38..6d4919b 100644 --- a/app/dbtables.py +++ b/app/dbtables.py @@ -80,6 +80,9 @@ class PlaylistsTable(Model): cascade="all, delete-orphan", order_by="PlaylistRowsTable.row_number", ) + favourite: Mapped[bool] = mapped_column( + Boolean, nullable=False, index=False, default=False + ) def __repr__(self) -> str: return ( @@ -101,9 +104,7 @@ class PlaylistRowsTable(Model): ) playlist: Mapped[PlaylistsTable] = relationship(back_populates="rows") - track_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("tracks.id", ondelete="CASCADE") - ) + track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id", ondelete="CASCADE")) track: Mapped["TracksTable"] = relationship( "TracksTable", back_populates="playlistrows", diff --git a/app/menu.yaml b/app/menu.yaml new file mode 100644 index 0000000..239d5ce --- /dev/null +++ b/app/menu.yaml @@ -0,0 +1,102 @@ +menus: + - title: "&File" + actions: + - text: "Save as Template" + handler: "save_as_template" + - text: "Manage Templates" + handler: "manage_templates" + - separator: true + - separator: true + - text: "Exit" + handler: "close" + + - title: "&Playlist" + actions: + - text: "Open Playlist" + handler: "open_existing_playlist" + shortcut: "Ctrl+O" + - text: "New Playlist" + handler: "new_playlist_dynamic_submenu" + submenu: true + - text: "Close Playlist" + handler: "close_playlist_tab" + - text: "Rename Playlist" + handler: "rename_playlist" + - text: "Delete Playlist" + handler: "delete_playlist" + - separator: true + - text: "Insert Track" + handler: "insert_track" + shortcut: "Ctrl+T" + - text: "Select Track from Query" + handler: "query_dynamic_submenu" + submenu: true + - text: "Insert Section Header" + handler: "insert_header" + shortcut: "Ctrl+H" + - text: "Import Files" + handler: "import_files_wrapper" + shortcut: "Ctrl+Shift+I" + - separator: true + - text: "Mark for Moving" + handler: "mark_rows_for_moving" + shortcut: "Ctrl+C" + - text: "Paste" + handler: "paste_rows" + shortcut: "Ctrl+V" + - separator: true + - text: "Export Playlist" + handler: "export_playlist_tab" + - text: "Download CSV of Played Tracks" + handler: "download_played_tracks" + - separator: true + - text: "Select Duplicate Rows" + handler: "select_duplicate_rows" + - text: "Move Selected" + handler: "move_selected" + - text: "Move Unplayed" + handler: "move_unplayed" + - separator: true + - text: "Clear Selection" + handler: "clear_selection" + shortcut: "Esc" + store_reference: true # So we can enable/disable later + + - title: "&Music" + actions: + - text: "Set Next" + handler: "set_selected_track_next" + shortcut: "Ctrl+N" + - text: "Play Next" + handler: "play_next" + shortcut: "Return" + - text: "Fade" + handler: "fade" + shortcut: "Ctrl+Z" + - text: "Stop" + handler: "stop" + shortcut: "Ctrl+Alt+S" + - text: "Resume" + handler: "resume" + shortcut: "Ctrl+R" + - text: "Skip to Next" + handler: "play_next" + shortcut: "Ctrl+Alt+Return" + - separator: true + - text: "Search" + handler: "search_playlist" + shortcut: "/" + - text: "Search Title in Wikipedia" + handler: "lookup_row_in_wikipedia" + shortcut: "Ctrl+W" + - text: "Search Title in Songfacts" + handler: "lookup_row_in_songfacts" + shortcut: "Ctrl+S" + + - title: "Help" + actions: + - text: "About" + handler: "about" + - text: "Debug" + handler: "debug" + diff --git a/app/models.py b/app/models.py index 1a12a83..535bf53 100644 --- a/app/models.py +++ b/app/models.py @@ -192,6 +192,138 @@ class Playdates(dbtables.PlaydatesTable): ).all() +class Playlists(dbtables.PlaylistsTable): + def __init__(self, session: Session, name: str, template_id: int) -> None: + """Create playlist with passed name""" + + self.name = name + self.last_used = dt.datetime.now() + session.add(self) + session.commit() + + # If a template is specified, copy from it + if template_id: + PlaylistRows.copy_playlist(session, template_id, self.id) + + @staticmethod + def clear_tabs(session: Session, playlist_ids: list[int]) -> None: + """ + Make all tab records NULL + """ + + session.execute( + update(Playlists).where((Playlists.id.in_(playlist_ids))).values(tab=None) + ) + + def close(self, session: Session) -> None: + """Mark playlist as unloaded""" + + self.open = False + session.commit() + + def delete(self, session: Session) -> None: + """ + Delete playlist + """ + + session.execute(delete(Playlists).where(Playlists.id == self.id)) + session.commit() + + @classmethod + def get_all(cls, session: Session) -> Sequence["Playlists"]: + """Returns a list of all playlists ordered by last use""" + + return session.scalars( + select(cls) + .filter(cls.is_template.is_(False)) + .order_by(cls.last_used.desc()) + ).all() + + @classmethod + def get_all_templates(cls, session: Session) -> Sequence["Playlists"]: + """Returns a list of all templates ordered by name""" + + return session.scalars( + select(cls).where(cls.is_template.is_(True)).order_by(cls.name) + ).all() + + @classmethod + def get_favourite_templates(cls, session: Session) -> Sequence["Playlists"]: + """Returns a list of favourite templates ordered by name""" + + return session.scalars( + select(cls) + .where( + cls.is_template.is_(True), + cls.favourite.is_(True) + ) + .order_by(cls.name) + ).all() + + @classmethod + def get_closed(cls, session: Session) -> Sequence["Playlists"]: + """Returns a list of all closed playlists ordered by last use""" + + return session.scalars( + select(cls) + .filter( + cls.open.is_(False), + cls.is_template.is_(False), + ) + .order_by(cls.last_used.desc()) + ).all() + + @classmethod + def get_open(cls, session: Session) -> Sequence[Optional["Playlists"]]: + """ + Return a list of loaded playlists ordered by tab. + """ + + return session.scalars( + select(cls).where(cls.open.is_(True)).order_by(cls.tab) + ).all() + + def mark_open(self) -> None: + """Mark playlist as loaded and used now""" + + self.open = True + self.last_used = dt.datetime.now() + + @staticmethod + def name_is_available(session: Session, name: str) -> bool: + """ + Return True if no playlist of this name exists else false. + """ + + return ( + session.execute(select(Playlists).where(Playlists.name == name)).first() + is None + ) + + def rename(self, session: Session, new_name: str) -> None: + """ + Rename playlist + """ + + self.name = new_name + session.commit() + + @staticmethod + def save_as_template( + session: Session, playlist_id: int, template_name: str + ) -> None: + """Save passed playlist as new template""" + + template = Playlists(session, template_name, template_id=0) + if not template or not template.id: + return + + template.is_template = True + session.commit() + + PlaylistRows.copy_playlist(session, playlist_id, template.id) + + class PlaylistRows(dbtables.PlaylistRowsTable): def __init__( self, @@ -476,161 +608,8 @@ class PlaylistRows(dbtables.PlaylistRowsTable): session.connection().execute(stmt, sqla_map) -class Playlists(dbtables.PlaylistsTable): - def __init__(self, session: Session, name: str): - self.name = name - self.last_used = dt.datetime.now() - session.add(self) - session.commit() - - @staticmethod - def clear_tabs(session: Session, playlist_ids: list[int]) -> None: - """ - Make all tab records NULL - """ - - session.execute( - update(Playlists).where((Playlists.id.in_(playlist_ids))).values(tab=None) - ) - - def close(self, session: Session) -> None: - """Mark playlist as unloaded""" - - self.open = False - session.commit() - - @classmethod - def create_playlist_from_template( - cls, session: Session, template: "Playlists", playlist_name: str - ) -> Optional["Playlists"]: - """Create a new playlist from template""" - - # Sanity check - if not template.id: - return None - - playlist = cls(session, playlist_name) - - # Sanity / mypy checks - if not playlist or not playlist.id: - return None - - PlaylistRows.copy_playlist(session, template.id, playlist.id) - - return playlist - - def delete(self, session: Session) -> None: - """ - Delete playlist - """ - - session.execute(delete(Playlists).where(Playlists.id == self.id)) - session.commit() - - @classmethod - def get_all(cls, session: Session) -> Sequence["Playlists"]: - """Returns a list of all playlists ordered by last use""" - - return session.scalars( - select(cls) - .filter(cls.is_template.is_(False)) - .order_by(cls.last_used.desc()) - ).all() - - @classmethod - def get_all_templates(cls, session: Session) -> Sequence["Playlists"]: - """Returns a list of all templates ordered by name""" - - return session.scalars( - select(cls).where(cls.is_template.is_(True)).order_by(cls.name) - ).all() - - @classmethod - def get_closed(cls, session: Session) -> Sequence["Playlists"]: - """Returns a list of all closed playlists ordered by last use""" - - return session.scalars( - select(cls) - .filter(cls.open.is_(False), cls.is_template.is_(False)) - .order_by(cls.last_used.desc()) - ).all() - - @classmethod - def get_open(cls, session: Session) -> Sequence[Optional["Playlists"]]: - """ - Return a list of loaded playlists ordered by tab. - """ - - return session.scalars( - select(cls) - .where( - cls.open.is_(True), - ) - .order_by(cls.tab) - ).all() - - def mark_open(self) -> None: - """Mark playlist as loaded and used now""" - - self.open = True - self.last_used = dt.datetime.now() - - @staticmethod - def name_is_available(session: Session, name: str) -> bool: - """ - Return True if no playlist of this name exists else false. - """ - - return ( - session.execute(select(Playlists).where(Playlists.name == name)).first() - is None - ) - - def rename(self, session: Session, new_name: str) -> None: - """ - Rename playlist - """ - - self.name = new_name - session.commit() - - @staticmethod - def save_as_template( - session: Session, playlist_id: int, template_name: str - ) -> None: - """Save passed playlist as new template""" - - template = Playlists(session, template_name) - if not template or not template.id: - return - - template.is_template = True - session.commit() - - PlaylistRows.copy_playlist(session, playlist_id, template.id) - - -class Queries(dbtables.QueriesTable): - def __init__( - self, session: Session, name: str, query: str, description: str = "" - ) -> None: - self.query = query - self.name = name - self.description = description - session.add(self) - session.commit() - - @classmethod - def get_all(cls, session: Session) -> Sequence[Queries]: - """ - Return a list of all queries - """ - - return session.scalars(select(cls)).unique().all() - - class Settings(dbtables.SettingsTable): - def __init__(self, session: Session, name: str): + def __init__(self, session: Session, name: str) -> None: self.name = name session.add(self) session.commit() @@ -658,7 +637,7 @@ class Tracks(dbtables.TracksTable): fade_at: int, silence_at: int, bitrate: int, - ): + ) -> None: self.path = path self.title = title self.artist = artist diff --git a/app/musicmuster.py b/app/musicmuster.py index 3d10000..173da9c 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -174,7 +174,9 @@ class ItemlistItem: class ItemlistManager(QDialog): - def __init__(self, items: list[ItemlistItem], callbacks: ItemlistManagerCallbacks) -> None: + def __init__( + self, items: list[ItemlistItem], callbacks: ItemlistManagerCallbacks + ) -> None: super().__init__() self.setWindowTitle("Item Manager") self.setMinimumSize(600, 400) @@ -189,7 +191,7 @@ class ItemlistManager(QDialog): if not hh: raise ApplicationError("ItemlistManager failed to create horizontalHeader") hh.setStretchLastSection(True) - self.table.setColumnWidth(0, 200) + self.table.setColumnWidth(0, 288) self.table.setColumnWidth(1, 300) self.populate_table() @@ -246,19 +248,25 @@ class ItemlistManager(QDialog): self.table.setCellWidget(row, 1, widget) - def rename_item(self, item_id: int) -> None: - print(f"Rename item {item_id}") - - def edit_item(self, item_id: int) -> None: - print(f"Edit item {item_id}") - self.callbacks.edit(item_id) - def delete_item(self, item_id: int) -> None: - print(f"Delete item {item_id}") self.callbacks.delete(item_id) + def edit_item(self, item_id: int) -> None: + self.callbacks.edit(item_id) + + def rename_item(self, item_id: int) -> None: + new_name = self.callbacks.rename(item_id) + if not new_name: + return + # Rename item in list + for row in range(self.table.rowCount()): + item = self.table.item(row, 0) + if item and self.items[row].id == item_id: + item.setText(new_name) + self.items[row].name = new_name + break + def toggle_favourite(self, item_id: int, checked: bool) -> None: - print(f"Toggle favourite for item {item_id}: {checked}") self.callbacks.favourite(item_id, checked) for row in range(self.table.rowCount()): @@ -273,20 +281,16 @@ class ItemlistManager(QDialog): break def new_item(self) -> None: - print("New item") - - # test_items = [ - # {"id": 1, "text": "Item 1", "favourite": False}, - # {"id": 2, "text": "Item 2", "favourite": True}, - # {"id": 3, "text": "Item 3", "favourite": False} - # ] + self.callbacks.new_item() @dataclass class ItemlistManagerCallbacks: - edit: Callable[[int], None] delete: Callable[[int], None] + edit: Callable[[int], None] favourite: Callable[[int, bool], None] + new_item: Callable[[], None] + rename: Callable[[int], Optional[str]] class PreviewManager: @@ -451,16 +455,21 @@ class TemplateSelectorDialog(QDialog): Class to manage user selection of template """ - def __init__(self, templates: list[tuple[str, int]]) -> None: + def __init__( + self, templates: list[tuple[str, int]], template_prompt: Optional[str] + ) -> None: super().__init__() self.templates = templates + self.template_prompt = template_prompt self.selected_id = None self.init_ui() def init_ui(self): # Create label - label = QLabel("Select template:") + if not self.template_prompt: + self.template_prompt = "Select template:" + label = QLabel(self.template_prompt) # Create combo box self.combo_box = QComboBox() @@ -592,6 +601,61 @@ class Window(QMainWindow): self.load_last_playlists() self.stop_autoplay = False + # # # # # # # # # # Overrides # # # # # # # # # # + + def closeEvent(self, event: Optional[QCloseEvent]) -> None: + """Handle attempt to close main window""" + + if not event: + return + + # Don't allow window to close when a track is playing + if track_sequence.current and track_sequence.current.is_playing(): + event.ignore() + helpers.show_warning( + self, "Track playing", "Can't close application while track is playing" + ) + else: + with db.Session() as session: + # Save tab number of open playlists + open_playlist_ids: dict[int, int] = {} + for idx in range(self.playlist_section.tabPlaylist.count()): + open_playlist_ids[ + self.playlist_section.tabPlaylist.widget(idx).playlist_id + ] = idx + Playlists.clear_tabs(session, list(open_playlist_ids.keys())) + for playlist_id, idx in open_playlist_ids.items(): + playlist = session.get(Playlists, playlist_id) + if playlist: + log.debug(f"Set {playlist=} tab to {idx=}") + playlist.tab = idx + + # Save window attributes + attributes_to_save = dict( + mainwindow_height=self.height(), + mainwindow_width=self.width(), + mainwindow_x=self.x(), + mainwindow_y=self.y(), + active_tab=self.playlist_section.tabPlaylist.currentIndex(), + ) + for name, value in attributes_to_save.items(): + record = Settings.get_setting(session, name) + record.f_int = value + + session.commit() + + event.accept() + + # # # # # # # # # # Internal utility functions # # # # # # # # # # + + def active_base_model(self) -> PlaylistModel: + return self.current.base_model + + def active_tab(self) -> PlaylistTab: + return self.playlist_section.tabPlaylist.currentWidget() + + # # # # # # # # # # Menu functions # # # # # # # # # # + def create_action( self, text: str, handler: Callable, shortcut: Optional[str] = None ) -> QAction: @@ -610,7 +674,7 @@ class Window(QMainWindow): menu_bar = self.menuBar() # Load menu structure from YAML file - with open("menu.yaml", "r") as file: + with open("app/menu.yaml", "r") as file: menu_data = yaml.safe_load(file) self.menu_actions = {} # Store reference for enabling/disabling actions @@ -661,26 +725,231 @@ class Window(QMainWindow): items = getattr(self, f"get_{key}_items")() for item in items: action = QAction(item["text"], self) - action.triggered.connect( - lambda _, i=item["handler"]: getattr(self, i)() - ) + + # Extract handler and arguments + handler = getattr(self, item["handler"], None) + args = item.get("args", ()) + + if handler: + # Use a lambda to pass arguments to the function + action.triggered.connect(lambda _, h=handler, a=args: h(*a)) + submenu.addAction(action) break - def get_new_playlist_dynamic_submenu_items(self): - """Returns dynamically generated menu items for Submenu 1.""" - return [ - {"text": "Option A", "handler": "option_a_handler"}, - {"text": "Option B", "handler": "option_b_handler"}, - ] + def get_new_playlist_dynamic_submenu_items( + self, + ) -> list[dict[str, str | tuple[Session, int]]]: + """ + Return dynamically generated menu items, in this case + templates marked as favourite from which to generate a + new playlist. + + The handler is to call create_playlist with a session + and template_id. + """ + + with db.Session() as session: + submenu_items: list[dict[str, str | tuple[Session, int]]] = [ + {"text": "Show all", + "handler": "create_playlist_from_template", + "args": (session, 0) + } + ] + templates = Playlists.get_favourite_templates(session) + for template in templates: + submenu_items.append( + { + "text": template.name, + "handler": "create_playlist_from_template", + "args": ( + session, + template.id, + ), + } + ) + + return submenu_items def get_query_dynamic_submenu_items(self): """Returns dynamically generated menu items for Submenu 2.""" return [ - {"text": "Action X", "handler": "action_x_handler"}, + {"text": "Action Xargs", "handler": "kae", "args": (21,)}, {"text": "Action Y", "handler": "action_y_handler"}, ] + # # # # # # # # # # Playlist management functions # # # # # # # # # # + + def _create_playlist( + self, session: Session, name: str, template_id: int + ) -> Playlists: + """ + Create a playlist in the database, populate it from the template + if template_id > 0, and return the Playlists object. + """ + + log.debug(f" _create_playlist({name=}, {template_id=})") + + return Playlists(session, name, template_id) + + def _open_playlist(self, playlist: Playlists, is_template: bool = False) -> int: + """ + With passed playlist: + - create models + - create tab + - switch to tab + - mark playist as open + return: tab index + """ + + log.debug(f" _open_playlist({playlist=}, {is_template=})") + + # Create base model and proxy model + base_model = PlaylistModel(playlist.id, is_template) + proxy_model = PlaylistProxyModel() + proxy_model.setSourceModel(base_model) + + # Create tab + playlist_tab = PlaylistTab(musicmuster=self, model=proxy_model) + idx = self.playlist_section.tabPlaylist.addTab(playlist_tab, playlist.name) + + # Mark playlist as open + playlist.mark_open() + + # Switch to new tab + self.playlist_section.tabPlaylist.setCurrentIndex(idx) + self.update_playlist_icons() + + return idx + + def create_playlist_from_template(self, session: Session, template_id: int) -> None: + """ + Prompt for new playlist name and create from passed template_id + """ + + if template_id == 0: + # Show all templates + selected_template_id = self.solicit_template_to_use(session) + if selected_template_id is None: + return + else: + template_id = selected_template_id + + playlist_name = self.solicit_playlist_name(session) + if not playlist_name: + return + + playlist = self._create_playlist(session, playlist_name, template_id) + self._open_playlist(playlist) + session.commit() + + def delete_playlist(self) -> None: + """ + Delete current playlist + """ + + with db.Session() as session: + playlist_id = self.current.playlist_id + playlist = session.get(Playlists, playlist_id) + if playlist: + if helpers.ask_yes_no( + "Delete playlist", + f"Delete playlist '{playlist.name}': " "Are you sure?", + ): + if self.close_playlist_tab(): + playlist.delete(session) + session.commit() + else: + log.error("Failed to retrieve playlist") + + def open_existing_playlist(self) -> None: + """Open existing playlist""" + + with db.Session() as session: + playlists = Playlists.get_closed(session) + dlg = SelectPlaylistDialog(self, playlists=playlists, session=session) + dlg.exec() + playlist = dlg.playlist + if playlist: + self._open_playlist(playlist) + session.commit() + + def save_as_template(self) -> None: + """Save current playlist as template""" + + with db.Session() as session: + template_names = [a.name for a in Playlists.get_all_templates(session)] + + while True: + # Get name for new template + dlg = QInputDialog(self) + dlg.setInputMode(QInputDialog.InputMode.TextInput) + dlg.setLabelText("Template name:") + dlg.resize(500, 100) + ok = dlg.exec() + if not ok: + return + + template_name = dlg.textValue() + if template_name not in template_names: + break + helpers.show_warning( + self, "Duplicate template", "Template name already in use" + ) + Playlists.save_as_template(session, self.current.playlist_id, template_name) + session.commit() + helpers.show_OK("Template", "Template saved", self) + + def solicit_playlist_name( + self, session: Session, default: str = "", prompt: str = "Playlist name:" + ) -> Optional[str]: + """Get name of new playlist from user""" + + dlg = QInputDialog(self) + dlg.setInputMode(QInputDialog.InputMode.TextInput) + dlg.setLabelText(prompt) + while True: + if default: + dlg.setTextValue(default) + dlg.resize(500, 100) + ok = dlg.exec() + if ok: + proposed_name = dlg.textValue() + if Playlists.name_is_available(session, proposed_name): + return proposed_name + else: + helpers.show_warning( + self, + "Name in use", + f"There's already a playlist called '{proposed_name}'", + ) + continue + else: + return None + + def solicit_template_to_use( + self, session: Session, template_prompt: Optional[str] = None + ) -> Optional[int]: + """ + Have user select a template. Return the template.id, or None if they cancel. + template_id of zero means don't use a template. + """ + + template_name_id_list: list[tuple[str, int]] = [] + template_name_id_list.append((Config.NO_TEMPLATE_NAME, 0)) + + with db.Session() as session: + for template in Playlists.get_all_templates(session): + template_name_id_list.append((template.name, template.id)) + + dlg = TemplateSelectorDialog(template_name_id_list, template_prompt) + if not dlg.exec() or dlg.selected_id is None: + return None # User cancelled + + return dlg.selected_id + + # # # # # # # # # # Miscellaneous functions # # # # # # # # # # + def select_duplicate_rows(self) -> None: """Call playlist to select duplicate rows""" @@ -707,12 +976,6 @@ class Window(QMainWindow): QMessageBox.StandardButton.Ok, ) - def active_base_model(self) -> PlaylistModel: - return self.current.base_model - - def active_tab(self) -> PlaylistTab: - return self.playlist_section.tabPlaylist.currentWidget() - def clear_next(self) -> None: """ Clear next track @@ -731,49 +994,6 @@ class Window(QMainWindow): # Clear the search bar self.search_playlist_clear() - def closeEvent(self, event: Optional[QCloseEvent]) -> None: - """Handle attempt to close main window""" - - if not event: - return - - # Don't allow window to close when a track is playing - if track_sequence.current and track_sequence.current.is_playing(): - event.ignore() - helpers.show_warning( - self, "Track playing", "Can't close application while track is playing" - ) - else: - with db.Session() as session: - # Save tab number of open playlists - open_playlist_ids: dict[int, int] = {} - for idx in range(self.playlist_section.tabPlaylist.count()): - open_playlist_ids[ - self.playlist_section.tabPlaylist.widget(idx).playlist_id - ] = idx - Playlists.clear_tabs(session, list(open_playlist_ids.keys())) - for playlist_id, idx in open_playlist_ids.items(): - playlist = session.get(Playlists, playlist_id) - if playlist: - log.debug(f"Set {playlist=} tab to {idx=}") - playlist.tab = idx - - # Save window attributes - attributes_to_save = dict( - mainwindow_height=self.height(), - mainwindow_width=self.width(), - mainwindow_x=self.x(), - mainwindow_y=self.y(), - active_tab=self.playlist_section.tabPlaylist.currentIndex(), - ) - for name, value in attributes_to_save.items(): - record = Settings.get_setting(session, name) - record.f_int = value - - session.commit() - - event.accept() - def close_playlist_tab(self) -> bool: """ Close active playlist tab, called by menu item @@ -860,43 +1080,6 @@ class Window(QMainWindow): self.signals.search_songfacts_signal.connect(self.open_songfacts_browser) self.signals.search_wikipedia_signal.connect(self.open_wikipedia_browser) - def create_playlist( - self, session: Session, playlist_name: str - ) -> Optional[Playlists]: - """Create new playlist""" - - log.debug(f"create_playlist({playlist_name=}") - - playlist = Playlists(session, playlist_name) - - if playlist: - return playlist - else: - log.error(f"Failed to create playlist, {playlist_name=}") - - return None - - def create_playlist_tab(self, playlist: Playlists) -> int: - """ - Take the passed playlist, create a playlist tab and - add tab to display. Return index number of tab. - """ - - log.debug(f"create_playlist_tab({playlist=})") - - # Create model and proxy model - base_model = PlaylistModel(playlist.id) - proxy_model = PlaylistProxyModel() - proxy_model.setSourceModel(base_model) - - # Create tab - playlist_tab = PlaylistTab(musicmuster=self, model=proxy_model) - idx = self.playlist_section.tabPlaylist.addTab(playlist_tab, playlist.name) - - log.debug(f"create_playlist_tab() returned: {idx=}") - - return idx - def current_row_or_end(self) -> int: """ If a row or rows are selected, return the row number of the first @@ -915,25 +1098,6 @@ class Window(QMainWindow): ipdb.set_trace() - def delete_playlist(self) -> None: - """ - Delete current playlist - """ - - with db.Session() as session: - playlist_id = self.current.playlist_id - playlist = session.get(Playlists, playlist_id) - if playlist: - if helpers.ask_yes_no( - "Delete playlist", - f"Delete playlist '{playlist.name}': " "Are you sure?", - ): - if self.close_playlist_tab(): - playlist.delete(session) - session.commit() - else: - log.error("Failed to retrieve playlist") - def download_played_tracks(self) -> None: """Download a CSV of played tracks""" @@ -1064,6 +1228,18 @@ class Window(QMainWindow): if track_sequence.current: track_sequence.current.fade() + def get_tab_index_for_playlist(self, playlist_id: int) -> Optional[int]: + """ + Return the tab index for the passed playlist_id if it is displayed, + else return None. + """ + + for idx in range(self.playlist_section.tabPlaylist.count()): + if self.playlist_section.tabPlaylist.widget(idx).playlist_id == playlist_id: + return idx + + return None + def hide_played(self): """Toggle hide played tracks""" @@ -1129,7 +1305,7 @@ class Window(QMainWindow): if playlist: log.debug(f"load_last_playlists() loaded {playlist=}") # Create tab - playlist_ids.append(self.create_playlist_tab(playlist)) + playlist_ids.append(self._open_playlist(playlist)) # Set active tab record = Settings.get_setting(session, "active_tab") @@ -1170,60 +1346,119 @@ class Window(QMainWindow): Delete / edit templates """ - def edit(template_id: int) -> None: - """Edit template""" - - print(f"manage_templates.edit({template_id=}") - + # Define callbacks to handle management options def delete(template_id: int) -> None: """delete template""" - print(f"manage_templates.delete({template_id=}") + template = session.get(Playlists, template_id) + if not template: + raise ApplicationError( + f"manage_templeate.delete({template_id=}) can't load template" + ) + if helpers.ask_yes_no( + "Delete template", + f"Delete template '{template.name}': " "Are you sure?", + ): + # If template is currently open, re-check + open_idx = self.get_tab_index_for_playlist(template_id) + if open_idx: + if not helpers.ask_yes_no( + "Delete open template", + f"Template '{template.name}' is currently open. Really delete?", + ): + return + else: + self.playlist_section.tabPlaylist.removeTab(open_idx) + + log.info(f"manage_templates: delete {template=}") + template.delete(session) + session.commit() + + def edit(template_id: int) -> None: + """Edit template""" + + template = session.get(Playlists, template_id) + if not template: + raise ApplicationError( + f"manage_templeate.edit({template_id=}) can't load template" + ) + # Simply load the template as a playlist. Any changes + # made will persist + self._open_playlist(template, is_template=True) def favourite(template_id: int, favourite: bool) -> None: - """favourite template""" + """Mark template as (not) favourite""" - print(f"manage_templates.favourite({template_id=}") + template = session.get(Playlists, template_id) + template.favourite = favourite + session.commit() - callbacks = ItemlistManagerCallbacks(edit, delete, favourite) + def new_item() -> None: + """Create new template""" + + # Get base template + template_id = self.solicit_template_to_use( + session, template_prompt="New template based upon:" + ) + if template_id is None: + return + + # Get new template name + name = self.solicit_playlist_name( + session, default="", prompt="New template name:" + ) + if not name: + return + + # Create playlist for template and mark is as a template + template = self._create_playlist(session, name, template_id) + template.is_template = True + session.commit() + + # Open it for editing + self._open_playlist(template, is_template=True) + + def rename(template_id: int) -> Optional[str]: + """rename template""" + + template = session.get(Playlists, template_id) + if not template: + raise ApplicationError( + f"manage_templeate.delete({template_id=}) can't load template" + ) + new_name = self.solicit_playlist_name(session, template.name) + if new_name: + template.rename(session, new_name) + idx = self.tabBar.currentIndex() + self.tabBar.setTabText(idx, new_name) + session.commit() + return new_name + + return None + + # Call listitem management dialog to manage templates + callbacks = ItemlistManagerCallbacks( + delete=delete, + edit=edit, + favourite=favourite, + new_item=new_item, + rename=rename, + ) # Build a list of templates template_list: list[ItemlistItem] = [] with db.Session() as session: for template in Playlists.get_all_templates(session): - # TODO: need to add in favourites - template_list.append(ItemlistItem(name=template.name, id=template.id)) - - # # Get user's selection - # dlg = EditDeleteDialog(template_list) - # if not dlg.exec(): - # return # User cancelled - - # action, template_id = dlg.selection - - # playlist = session.get(Playlists, template_id) - # if not playlist: - # log.error(f"Error opening {template_id=}") - - # if action == "Edit": - # # Simply load the template as a playlist. Any changes - # # made will persist - # idx = self.create_playlist_tab(playlist) - # self.playlist_section.tabPlaylist.setCurrentIndex(idx) - - # elif action == "Delete": - # if helpers.ask_yes_no( - # "Delete template", - # f"Delete template '{playlist.name}': " "Are you sure?", - # ): - # if self.close_playlist_tab(): - # playlist.delete(session) - # session.commit() - # else: - # raise ApplicationError( - # f"Unrecognised action from EditDeleteDialog: {action=}" - # ) + template_list.append( + ItemlistItem( + name=template.name, id=template.id, favourite=template.favourite + ) + ) + # We need to retain a reference to the dialog box to stop it + # going out of scope and being garbage-collected. + self.dlg = ItemlistManager(template_list, callbacks) + self.dlg.show() def mark_rows_for_moving(self) -> None: """ @@ -1308,63 +1543,6 @@ class Window(QMainWindow): self.move_playlist_rows(unplayed_rows) self.disable_selection_timing = False - def new_playlist(self) -> None: - """ - Create new playlist, optionally from template - """ - - # Build a list of (template-name, playlist-id) tuples starting - # with the "no template" entry - template_list: list[tuple[str, int]] = [] - template_list.append((Config.NO_TEMPLATE_NAME, 0)) - - with db.Session() as session: - for template in Playlists.get_all_templates(session): - template_list.append((template.name, template.id)) - - dlg = TemplateSelectorDialog(template_list) - if not dlg.exec(): - return # User cancelled - template_id = dlg.selected_id - - # Get a name for this new playlist - playlist_name = self.solicit_playlist_name(session) - if not playlist_name: - return - - # If template_id == 0, user doesn't want a template - if template_id == 0: - playlist = self.create_playlist(session, playlist_name) - else: - playlist = Playlists.create_playlist_from_template( - session, template, playlist_name - ) - - if playlist: - playlist.mark_open() - # Need to ensure that the new playlist is committed to - # the database before it is opened by the model. - session.commit() - idx = self.create_playlist_tab(playlist) - self.playlist_section.tabPlaylist.setCurrentIndex(idx) - else: - log.error("Playlist failed to create") - - def open_playlist(self) -> None: - """Open existing playlist""" - - with db.Session() as session: - playlists = Playlists.get_closed(session) - dlg = SelectPlaylistDialog(self, playlists=playlists, session=session) - dlg.exec() - playlist = dlg.playlist - if playlist: - idx = self.create_playlist_tab(playlist) - playlist.mark_open() - session.commit() - - self.playlist_section.tabPlaylist.setCurrentIndex(idx) - def open_songfacts_browser(self, title: str) -> None: """Search Songfacts for title""" @@ -1688,32 +1866,6 @@ class Window(QMainWindow): ) track_sequence.current.start_time -= dt.timedelta(milliseconds=elapsed_ms) - def save_as_template(self) -> None: - """Save current playlist as template""" - - with db.Session() as session: - template_names = [a.name for a in Playlists.get_all_templates(session)] - - while True: - # Get name for new template - dlg = QInputDialog(self) - dlg.setInputMode(QInputDialog.InputMode.TextInput) - dlg.setLabelText("Template name:") - dlg.resize(500, 100) - ok = dlg.exec() - if not ok: - return - - template_name = dlg.textValue() - if template_name not in template_names: - break - helpers.show_warning( - self, "Duplicate template", "Template name already in use" - ) - Playlists.save_as_template(session, self.current.playlist_id, template_name) - session.commit() - helpers.show_OK("Template", "Template saved", self) - def search_playlist(self) -> None: """Show text box to search playlist""" @@ -1830,43 +1982,16 @@ class Window(QMainWindow): # Switch to correct tab if playlist_id != self.current.playlist_id: - for idx in range(self.playlist_section.tabPlaylist.count()): - if ( - self.playlist_section.tabPlaylist.widget(idx).playlist_id - == playlist_id - ): - self.playlist_section.tabPlaylist.setCurrentIndex(idx) - break + open_idx = self.get_tab_index_for_playlist(playlist_id) + if open_idx: + self.playlist_section.tabPlaylist.setCurrentIndex(open_idx) + else: + raise ApplicationError( + f"show_track() can't find current playlist tab {playlist_id=}" + ) self.active_tab().scroll_to_top(playlist_track.row_number) - def solicit_playlist_name( - self, session: Session, default: str = "" - ) -> Optional[str]: - """Get name of new playlist from user""" - - dlg = QInputDialog(self) - dlg.setInputMode(QInputDialog.InputMode.TextInput) - dlg.setLabelText("Playlist name:") - while True: - if default: - dlg.setTextValue(default) - dlg.resize(500, 100) - ok = dlg.exec() - if ok: - proposed_name = dlg.textValue() - if Playlists.name_is_available(session, proposed_name): - return proposed_name - else: - helpers.show_warning( - self, - "Name in use", - f"There's already a playlist called '{proposed_name}'", - ) - continue - else: - return None - def stop(self) -> None: """Stop playing immediately""" @@ -2074,6 +2199,10 @@ class Window(QMainWindow): self.playlist_section.tabPlaylist.setTabIcon( idx, QIcon(Config.PLAYLIST_ICON_CURRENT) ) + elif self.playlist_section.tabPlaylist.widget(idx).model().sourceModel().is_template: + self.playlist_section.tabPlaylist.setTabIcon( + idx, QIcon(Config.PLAYLIST_ICON_TEMPLATE) + ) else: self.playlist_section.tabPlaylist.setTabIcon(idx, QIcon()) diff --git a/app/playlistmodel.py b/app/playlistmodel.py index 5db28ae..4762033 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -26,6 +26,7 @@ from PyQt6.QtGui import ( ) # Third party imports +from sqlalchemy.orm.session import Session import obswebsocket # type: ignore # import snoop # type: ignore @@ -74,12 +75,14 @@ class PlaylistModel(QAbstractTableModel): def __init__( self, playlist_id: int, + is_template: bool, *args: Optional[QObject], **kwargs: Optional[QObject], ) -> None: log.debug("PlaylistModel.__init__()") self.playlist_id = playlist_id + self.is_template = is_template super().__init__(*args, **kwargs) self.playlist_rows: dict[int, RowAndTrack] = {} @@ -498,7 +501,7 @@ class PlaylistModel(QAbstractTableModel): """ if not index.isValid(): - return Qt.ItemFlag.NoItemFlags + return Qt.ItemFlag.ItemIsDropEnabled default = ( Qt.ItemFlag.ItemIsEnabled @@ -772,7 +775,7 @@ class PlaylistModel(QAbstractTableModel): return None - def load_data(self, session: db.session) -> None: + def load_data(self, session: Session) -> None: """ Same as refresh data, but only used when creating playslit. Distinguishes profile time between initial load and other @@ -1061,7 +1064,7 @@ class PlaylistModel(QAbstractTableModel): # Update display self.invalidate_row(track_sequence.previous.row_number) - def refresh_data(self, session: db.session) -> None: + def refresh_data(self, session: Session) -> None: """ Populate self.playlist_rows with playlist data diff --git a/app/playlists.py b/app/playlists.py index 192f0ae..c1f63a1 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -343,7 +343,7 @@ class PlaylistTab(QTableView): Override closeEditor to enable play controls and update display. """ - self.musicmuster.action_Clear_selection.setEnabled(True) + self.musicmuster.enable_escape(True) super(PlaylistTab, self).closeEditor(editor, hint) diff --git a/app/ui/icons.qrc b/app/ui/icons.qrc index 6e5d633..9d51913 100644 --- a/app/ui/icons.qrc +++ b/app/ui/icons.qrc @@ -1,6 +1,7 @@ yellow-circle.png + redstar.png green-circle.png star.png star_empty.png diff --git a/app/ui/icons_rc.py b/app/ui/icons_rc.py index d9681af..91978ae 100644 --- a/app/ui/icons_rc.py +++ b/app/ui/icons_rc.py @@ -1645,6 +1645,1351 @@ Ou\xb6\x11\xb8\x8dU\x8d`\x86\xac%p\xfd\xedU\ \xa1A <\xfa\x84\x86\xbd\xf0x\x1b\x1a\xe8CG\x98\ \xb7\xa1\xed\x0e\xf8\xd1\xb9\xb9Ttkv\x00\x00\x00\x00\ IEND\xaeB`\x82\ +\x00\x00S\xe1\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x02\x00\x00\x00\x02\x00\x08\x06\x00\x00\x00\xf4x\xd4\xfa\ +\x00\x00\x00\xc4zTXtRaw prof\ +ile type exif\x00\x00x\ +\xdamP[\x0e\x03!\x08\xfc\xe7\x14=\x02\x02\xbax\ +\x1c\xb7k\x93\xde\xa0\xc7/\x0a6\xbbm'\xe1!\x93\ +\x0c#\xd0_\xcf\x07\xdc\x06(\x09H\xde\xb4\xd4R\xd0\ + U*5k\x14\x1dm\xe6\x842\xf3\x02\xc5\xf42\ +\x87\x0fA6b\xab\xec\x84\x16\xafi\xcd\x97P\xd4\xd4\ +\xac\xcb'!\xbd\x07\xb1_\x89*\xb1^\xbf\x84b\x11\ +\x0fG\xc3\xc2\x11B5\x84\x98\x9cH!\xd0\xfc[X\ +\xaan\xe7/\xec\x1d\xafP\x0f\x18i\x0f\xd5\xe9\x08\x7f\ +\xdf\xb2\xd9\xf5\x8el{\x98\xa8sb\xb4\xcc,n\x80\ +Gd\xe06\x9bfa\xa6\xac\xafs\x82\x96\x99S8\ +\xb1\x83\xfc\xbb\xd3\x02\xbc\x0179Y\xc2clt8\ +\x00\x00\x01\x84iCCPICC prof\ +ile\x00\x00x\x9c}\x91=H\xc3@\x1c\xc5_\ +S\xa5E*\x0e\x16\x14q\xc8P\x9d\xecbE\x04\x97\ +Z\x85\x22T\x08\xb5B\xab\x0e&\xd7Oh\xd2\x90\xa4\ +\xb88\x0a\xae\x05\x07?\x16\xab\x0e.\xce\xba:\xb8\x0a\ +\x82\xe0\x07\x88\xbb\xe0\xa4\xe8\x22%\xfe/)\xb4\x88\xf1\ +\xe0\xb8\x1f\xef\xee=\xee\xde\x01B\xb3\xcaT\xb3'\x0e\ +\xa8\x9ae\xa4\x93\x091\x9b[\x15\x03\xaf\x100\x84 \ +f\x11\x93\x99\xa9\xcfIR\x0a\x9e\xe3\xeb\x1e>\xbe\xde\ +Ey\x96\xf7\xb9?G\x7f\xbe`2\xc0'\x12\xc7\x99\ +nX\xc4\x1b\xc4\xd3\x9b\x96\xcey\x9f8\xcc\xcar\x9e\ +\xf8\x9cx\xc2\xa0\x0b\x12?r]q\xf9\x8ds\xc9a\ +\x81g\x86\x8dLz\x9e8L,\x96\xbaX\xe9bV\ +6T\xe2)\xe2H^\xd5(_\xc8\xba\x9c\xe7\xbc\xc5\ +Y\xad\xd6Y\xfb\x9e\xfc\x85\xa1\x82\xb6\xb2\xccu\x9a\xa3\ +Hb\x11K\x90 BA\x1d\x15Ta!J\xabF\ +\x8a\x894\xed'<\xfc#\x8e_\x22\x97B\xae\x0a\x18\ +9\x16P\x83\x0a\xd9\xf1\x83\xff\xc1\xefn\xcdbl\xd2\ +M\x0a%\x80\xde\x17\xdb\xfe\x18\x03\x02\xbb@\xaba\xdb\ +\xdf\xc7\xb6\xdd:\x01\xfc\xcf\xc0\x95\xd6\xf1\xd7\x9a\xc0\xcc\ +'\xe9\x8d\x8e\x169\x02\x06\xb6\x81\x8b\xeb\x8e\xa6\xec\x01\ +\x97;\xc0\xf0\x93.\x1b\xb2#\xf9i\x0a\xc5\x22\xf0~\ +F\xdf\x94\x03\x06o\x81\xbe5\xb7\xb7\xf6>N\x1f\x80\ +\x0cu\x95\xba\x01\x0e\x0e\x81\xf1\x12e\xaf{\xbc;\xd8\ +\xdd\xdb\xbfg\xda\xfd\xfd\x00\x05Dr\xe1xK\xa9Z\ +\x00\x00\x0dxiTXtXML:com.\ +adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a <\ +rdf:Seq>\x0a \x0a \ +\x0a \x0a \ + \x0a \x0a\ +\x0a \ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ +\x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a\ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \x0a \ + \ + \ + \ + \ + \ + \ +\x0a \ + \x0a\ +\xf72JK\x00\x00\x00\x06bKGD\x00\xff\x00\xff\ +\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00\ +\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x00\x07t\ +IME\x07\xe9\x02\x17\x09\x00\x1f\xc8H\xea\xd6\x00\x00\ + \x00IDATx\xda\xed\xddw\x98\x15\xd5\xf9\xc0\ +\xf1\xef\xee\x02\x0bR\x96bA\x10\x94*EDc\xc1\ +\xaeQ\xb0\xc4.\x8aF\xa3\xd8\x15{\x17QL\xd4\xc4\ +^b4\x891\x9a\xa2\xc6\xfc\xa21Q\xa3Fc\xef\ +\xd8\x0bX\x00\x05\x04\xc1\x88\x0a\x0cM\x96\xb6\xbf?\xe6\ +nD@\x04\xb6\x9c3w\xbe\x9f\xe7\xb9O\x12#\xdc\ +\xf7\x9esf\xce;g\xce\xbc\x03\x92$I\x92$I\ +\x92$I\x92$I\x92$I\x92$I\x92$I\x92\ +$I\x92$I\x92$I\x92$\xa9\x0e\x95\xd8\x04R\ +\xd1\x1f\xe3=\x80-\x81\xbe@\x17\xa0\x13\xd0\x14hQ\ +\xf8wf\x02\xb3\x81\x09\xc0\xc7\xc0\xdb\xc0\xab\xc0h\xa0\ +\xca&\x94$)\x1bJ\x81\x01\xc0\xef\x80\xc9\x85I|\ +u>\x93\x81[\x0a\x7fW\xa9\xcd*IR\x9cZ\x01\ +\x17\x16\xae\xe4\xabj\xf93\x1e\x18\x06\xb4\xb4\x99%I\ +\x8aCs\xe02 \xa9\x83\x89\x7f\xe9\xcf\x0c\xe0\x12\xa0\ +\x99\xcd.IR\x18%\xc0\xe1\xc0\x94z\x98\xf8\x97w\ +{\xe00\xbb@\x92\xa4\xfa\xb56\xf0@\x80\x89\x7f\xe9\ +\xcf\xa3@;\xbbC\x92\xa4\xba\xd7\x1f\x98\x1a\xc1\xe4_\ +\xfd\x99Z\x88I\x92$\xd5\x91s\x80\x85\x11M\xfe\xd5\ +\x9f\x05\xc0\xd9v\x8f$I\xb5\xab\x04\xb8>\xc2\x89\x7f\ +\xe9\xcf\xaf\xf1\x91AI\x92jE\x03\xe0\xff20\xf9\ +W\x7f\xee.\xc4,I\x92VS)pW\x86&\xff\ +\xea\xcf\x9d\xae\x04H\x92\xb4\xfan\xcd\xe0\xe4_\xfd\xb9\ +\xc5\xee\x93$i\xd5\x0d\xcd\xf0\xe4_\xfd9\xd7n\x94\ +$i\xe5\xed\x0b,*\x82\x04`\x11\xb0\x8f\xdd)I\ +\xd2\xf7\xebBZr\xb7\xaaH>\xd3\x81\xcev\xab$\ +I\xdf\xad\x1cx\xa3\x88&\xff\xea\xcfk\x85\xdf&I\ +\x92\x96\xe3\xea\x22\x9c\xfc\xab?\x97\xdb\xbd\x92$-\xab\ +\x1fqV\xf9\xab\xcdj\x81[\xd8\xcd\x92$}\xa3!\ +0\xaa\x88'\xff\xea\xcf\xc8\xc2o\x95$I\xa4u\xf4\ +\xabr\xf29\xc3\xee\x96$\x09\xd6\xa1\xb8v\xfd\x7f\xdf\ +'\x01\xd6\xb5\xdb\xa5\xb0\xcal\x02)\xb8\xeb\x81ms\ +\xf4{\xcb\x815\x80\x87\xedzIR^m\x00T\xe6\ +\xe8\xea\xbf\xfa3\x9f\xb4\xde\x81$I\xb9tg\x0e'\ +\xff%_\x18$IR\xeet%}4.\xaf\x09\xc0\ +B`C\x87\x81$)o\xee\xc8\xf1\xe4_\xfd\xf9\xb3\ +\xc3@\x92\x94']r~\xf5\xbf\xe4*@w\x87\x83\ +T\xffJm\x02)\x88\x0b\x81\x066\x03e\xc0\xf96\ +\x83$)\x0f\xd6'\x9f;\xffW\xf4D\x80o\x0b\x94\ +\x5c\x01\x90\x8a\xdeE@#\x9b\xe1\x7f\x1a\x02Cm\x06\ +IR\xb1_\xfd\xcf\x8f\xe1\xca{\x8f\xed\xa9\xda}\xbb\ +\xa8V\x01\xd6wxH\x92\x8a\xd5-1L\xb8\xbbo\ +G\xd5\xa2\x8e\x15U\x8b:VT\xed\xbaM4I\xc0\ +-\x0e\x0f\xa9\xfe\x94\xd8\x04R\xbd\xe9\x00\x8c%-\x85\ +\x1b\xcc\x86\x1b\xc0\xfb\x8b+\xbe\xf5\xcfz\x96&\x8c\x99\ +\x10\xbc}*\x81n\xc0$\x87\x8aT\xf7\xdc\x03 \xd5\ +\x9f\xa1\xa1'\xff\x06e\xf0\xe7\xcb\x97\xfd\xe7w_\x0d\ +\x0d\xc3?\x93P\x8eO\x04H\x92\x8aL{\xe0k\x02\ +/\xb3_p\xdc7K\xffK\x7f\xce;:\x8a\xdb\x00\ +_\x17\xdaJR\x1d\xf3\x16\x80T?n\x04N\x0b\x19\ +\xc0Z\xad\xe0\xbf\xcd+V\xf8\xef\xb4\x9b\x93\xf0\xf9W\ +Q\xb4\xd5\x19\x0e\x19\xa9ny\x0b@\xaa{m\x81\xe3\ +B\x07\xf1\xb3\x93\xbf\xff\xdf\xb9\xe8\x84(\xda\xeb\x04\xa0\ +\x9d\xc3Fr\x05@\xca\xba\xeb\x813C\x06\xd0q]\ +\x18\xdf\xb0b\xa5\xfe\xdd.\x8b\x12&L\x8e\xa2\xcd\xce\ +v\xe8H\xae\x00HY\xb5f\x0cW\xff\xc3V!\x82\ +\xf3\x8f\x89\xa2\xddN\x02\xd6u\xf8H\xae\x00HYu\ +\x0dpN\xc8\x00:\xb4\x85\x09\x8d*V\xe9\xcft^\ +\x98\xf0\xc9\x94(\xda\xee<\x87\x90\xe4\x0a\x80\x945m\ +\x80\x13C\x071\xf4\xd8U\xff3\xe7\x1c\x19\xcd*\xc0\ +\xda\x0e#\xc9\x15\x00)k\xae p\x8d\xfbu\xd7\x82\ +O\x9bT\xac\xd6\x9f\xed8/a\xf2\xd4(\xdap\x98\ +CIr\x05@\xca\xd2\xd5\xff\xc9\xa1\x83\xb8\xe0\xd8\xd5\ +\xff\xb3\xe7\x1e\x1dE;\x9e\x0a\xac\xe5p\x92\x5c\x01\x90\ +\xb2\xe2\xe7\xc0\x85!\x03h\xbb&L^\xa3\xa2F\x7f\ +G\x87y\x09S\xa6F\xd1\x96\xc3\x1dR\x92+\x00R\ +\xec*b\xb8\xfa?\xaf\x16\xae\xe0\xcf\x1e\x1cE{\x9e\ +\x06\xb4rXI\xb5\xab\xcc&\x90j\xdd0`\xb7\x90\ +\x01\xac\xd3\x06\xee\x1dSQ\xe3\xbfg\xab\xf7\x1asK\ +Y%s\xbe\x0e\xda\x9e\xe5\xc0<\xe0\x19\x87\x96\xe4\x0a\ +\x80\x14\xf3\xd5\xff\xa9\xa1\x838\xe7\xa88\xff\xae\x1a8\ +\x03h\xe9\xf0\x92j\x8f{\x00\xa4\xdau1pI\xc8\ +\x00\xda\xb4\x84\xa9-*j\xf5\xef\x5cwv\xc2\xd4i\ +Q\xb4\xede\x0e1\xc9\x15\x00)6-\x80\xd3C\x07\ +qn\x1d\x5c\xb1\x9fqx\x14\xed{\x96\xab\x00\x92+\ +\x00R\x8c.$\xdd\xb1^TW\xff\xd5\xda\xceJ\xf8\ +bz\x14m|\xb9CMr\x05@\x8aE\xd3\x18\xae\ +\xfe\xcf<\xa2\xee\xfe\xee\xd3~\x12\xcd*@s\x87\x9b\ +\xe4\x0a\x80\x14\x8b\xf3\x81+C\x06\xd0\xb29|\xd5\xaa\ +\xa2N\xbfc\xcd\x19\x09\xd3g\x06o\xeb\xa1\xc0U\x0e\ +9\xc9\x15\x00)\x86\xab\xff\xb3\x8a\xf9\xea\xbf\xda\xe9q\ +\xac\x02\x9c\x034s\xd8I\xae\x00H1LH\xd7\x84\ +\x0c\xa0\xa2\x19Lk]Q/\xdf\xd5fz\xc2\x8cY\ +Q\xb4\xf9u\x0e=\xc9\x15\x00)\x94\xc6\xc0\x99\xa1\x83\ +8\xbd\x1ew\xe9\x9fzX\x14\xed~.\xb0\x86\xc3O\ +r\x05@\x0a\xe5L\xe0\xfa\x90\x01\xb4h\x06\xd3\xeb\xe9\ +\xea?\xb2U\x803\x81_:\x04%W\x00\xa4\x10W\ +\xffg\x87\x0e\xe2\xd4C\xeb\xff;O:$\x8a\xf6?\ +\x1fh\xe20\x94\x5c\x01\x90\xea}\xee\x05~\x152\x80\ +\xa6M`\xe6Z\x15A\xbe\xbb\xe5W\x09\xb3\xe6\x04\xef\ +\x83\xd3\x80\x9b\x1c\x8a\x92+\x00R})\x07\xce\x0b\x9e\ +\x81\x04\xbc\x1f?\xe4\xe0(\xfa\xe1\x02\xd2\x95\x18I\xae\ +\x00H\xf5\xe2$\xe0\xd7y\xbd\xfa\xafV\xf1e\xc2\xec\ +\xb9Q\xf4\xc5o\x1d\x92\x92+\x00R]kH\xba\x0b\ +=\xa8\x18\xae\xc0O\x18\x14\xcd*@#\x87\xa5\xe4\x0a\ +\x80T\xe7\xf3\x1epK\xc8\x00\x1a\x97\xc3\x9cu*\xa2\ +h\x8c\xe6S\x13\xe6\xce\x8b\xa2OnuhJ\xae\x00\ +Huy\xf5\x7f\xbeW\xff\xdf8\xfe\xa0(\xc2\xb8\xd0\ +U\x00\xc9\x04@\xaaK\x83\x81N\xa1\xaf\xfe\xaf\x9c\x17\ +\xcf\xfbp\xae{\xb6\x82&\xe5\xc1\xc3\xe8\x08\x1c\xee\xf0\ +\x94L\x00\xa4\xbaPF\x04;\xff\x8f?\x10\x1a\x8c\x88\ +\xeb\xd0=f`\x14a\x0c\x03\x1a8L%\x13\x00\xa9\ +\xb6\x1d\x0et\x0b\x19@y#\xb8j\x8d\xf8\xde\x83s\ +\xe3\x0b\x154\x0e\xbf\x0a\xd0\x19\xf8\x89\xc3T2\x01\x90\ +j\xfb\xea\xff\x82\xe0W\xda\x07@\xa3\xc7\xca\xa2l\xa0\ +\xa3\xf6\x8b\x22\x8c\x8b\x5c\x05\x90L\x00\xa4\xdat(\xd0\ +=d\x00\x0d\x1b\xc0\xe5}\xe3\xadys\xedzMi\ +\xd40x\x18]\x80C\x1c\xae\x92\x09\x80T[W\xff\ +\xc3B\x07q\xf4\x01\xd0\xfc\xb7\xe5\xd16R\xe3{\x1b\ +pd\x1c\xab\x00\x17\x17\xfaL\x92\x09\x80T#\x07\x03\ +=B_\xfd_\xb6s\xfcO\xb9]\xd7c\x8d\x18V\ +\x01\xba\x01\x83\x1c\xb6\x92\x09\x80T\xd3c$\xf8\xbd\xff\ +\xc1\xfbB\x9b\xcb\xe3\x7f\xf1\xdd\x1a\x7fn\xc8\xe1{G\ +\x11\xcap\xcfo\x92\x09\x80T\x13\x07\x02\x1b\x85\x0c\xa0\ +\xac\x14\x86\x0e\xceN\xd1\xce\x9f\xef\xd3\x80\x06\xe1\x17\xe0\ +{\x16\xfaN\xd2w\xb0\x14\xb0\xb4\xe2\xe3\xe3m`\xe3\ +\x90A\x1c\xb9\x1f\xdc\xfefE\xa6\x1a\xee\xe8M\x13\xfe\ +\xfc@\xf00\xde+\xf4\xddb\x87\xb2\xe4\x0a\x80\xb4*\ +\x0e\x08=\xf9\x97\x95\xc2\x05\xc7f\xaf\xe1~~pY\ +\x0c\xab\x00\xbd\x81\xfd\x1c\xc6\x92+\x00\xd2\xaa\x1e\x1b\xaf\ +\x01\x9b\x85\x0c\xe2\xf0\xbd\xe1O\xefTd\xb2\x01\x07o\ +\x9cp\xd7C\xc1\xc3x\x07\xd8\x14\xa8rHK\xae\x00\ +H+c\xdf\xd0\x93\x7fY)\x0c;>\xbb\x0d\xf8\xb3\ +\xe3K(\x0b\x7f\x86\xe9\x0b\xec\xe3p\x96\x5c\x01\x90V\ +\xd6\xab\xc0\x16!\x038tO\xb8sdE\xa6\x1b\xf1\ +'\x1b%\xfc\xf5\x91\xe0a\xbcUH\xe6\x5c\x05\x90\x5c\ +\x01\x90Vh\xaf\xd0\x93\x7fiF\xef\xfd/m\xf8\x89\ +\xe9o\x09lS`O\x87\xb5\xe4\x0a\x80\xf4}^\x01\ +\xb6\x0c\x19\xc0\xa0\xdd\xe0\xaf\x1fT\x14Ec\x1e\xd23\ +\xe1\xde\xc7\x82\x87\xf1F!\xa9s\x15@r\x05@Z\ +\xae=BO\xfe%%p\xe1\x09\xc5\xd3\xa0?\x1d\x12\ +\xc5*\xc0f\xc0n\x0eo\xc9\x15\x00\xe9\xbb<\x0fl\ +\x172\x80\x81\x03\xe0\x9e\xd1\x15E\xd5\xa8\x07uO\xf8\ +\xc7\x13\xc1\xc3\x18\x01l\xed\x10\x97\x5c\x01\x90\x96\xb6k\ +\xe8\xc9\xbf\xa4\x04.8\xae\xf8\x1av\xf8\x89\xe9o\x0b\ +l+`\x80\xc3\x5cr\x05@Z\xdas\xc0\xf6!\x03\ +\xd8o\x17\xb8olEQ6\xee\x01]\x13\x1ex*\ +x\x18/\x03\xdb8\xd4%W\x00\xa4j;\x87\x9e\xfc\ +\x01\x86\x1dW\xbc\x0d\xfc\xd3!Q\xac\x02l]\xe8k\ +\xc9\x15\x00\x9b@\x02\xe0\x19`\xc7\x90\x01\xec\xb5#<\ +0\xbe\xa2\xa8\x1by\x9f\x0d\x12\x1e~.x\x18/\x12\ +\xf8V\x8f\xe4\x0a\x80\x14\x87mCO\xfe\x90\xde'/\ +v\x97\x9c\x12\xc5*@\x14\xfd-\xb9\x02 \x85\xf7$\ +\x81\x97\x85\xf7\xd8\x1e\x1e\xfa\xa4\x22\x17\x8d\xbdg\xc7\x84\ +G_\x88\xa2\xcf\xfb;\xf4\xe5\x0a\x80\x94_Q\xdc\x13\ +.\xe6{\xffK\xfb\xe9\x90(\xc2\xd8\x85\x08\xf6|H\ +\xae\x00H\xe1\xbd\x89`\x99\xf7\xe2\x13\xed\x88\xa5ER\x09\ +\xf1\x00`c{C\xae\x00H\xc5\xe7o\xc0\xa0\x90\x01\ +\xfc\xa0\x17\xbc6\xdb\xab\xff\xe5\xd9\xba\x22\xe1\xd5\x91\xc1\ +\xc3\xb8\x078\xd8\xde\x90+\x00R\xf1\xe8I\x04\xcb\xbb\ +\x97x\xef\xff;E\xb2\x17\xe0@\xd2}\x22\x92+\x00\ +R\x91\xb8\x1b\xf8q\xc8\x006\xed\x09\xaf\xcf\xf1\xea\x7f\ +E\xfa5Ox\xfd\xbd(\xc6\xcaa\xf6\x86\x5c\x01\x90\ +\xb2\xaf\x1b\x81\x97\xfe!\x9a\xb7\xe0E\xed\xc2\xe3\xa3\x08\ +\xe3`\xa0\x87\xbd!W\x00\xa4\xec\xbb\x038|X\xe5\ +\xd5\x7fM\xf4*M\x18=!x\x18\xbf\x03L\xe5\xe4\ +\x0a\x80\x94\x01\x1d\x08\xbc\xf1\xcf\xab\xff\xdaqa\x1c\xd5\ +\x01\x8f\x06\xd6\xb77\xe4\x0a\x80\x14\xbf_\x03A\x1f\xbc\ +\xeb\xd2\x01\xc6\x94x\xf5_\x1bz\x96&\x8c\x09\xbf\x0a\ +\xf0\x1b\xc0B\xcer\x05@\x8a\xd8\xbaD\xf0\xe8V$\ +u\xed\x8b\xc2\xd0c\xa2\x08\xe3\x18`={C\xae\x00\ +H\xf1\xfa\x15pj\xc8\x00:\xaf\x07cK\xbd\xfa\xaf\ +M=J\x12\xc6~\x12\xc5\xd8:\xdd\xde\x90+\x00R\ +|\xda\x02\xc1\xf7\x8eG\xf2\x0c{Q9\xef\xe8(\xc2\ +8\x1ehoo\xc8\x15\x00)>\xd7\x03g\x86\x0c\xa0\ +\xe3\xba0\xbe\xa1W\xffu\xa1\xeb\xa2\x84\xf1\x93\x83\x87\ +q\x03p\x96\xbd!W\x00\xa4x\xacC\xe0\xc7\xfe\x00\ +\x86Y2\xa6\xce\x9c\x1f\xc7^\x80!@;{C\xae\ +\x00H\xf1\xb8\x068'd\x00\x1d\xda\xc2\x84F^\xfd\ +\xd7\xa5\xce\x0b\x13>\x99\x12<\x8ck\x81s\xed\x0d\xb9\ +\x02 \x85\xd7\x86\x08\x0a\xb5D\xf2\x16\xbb\xa2vn\x1c\ +\xaf\xe6\x19\x02\xacmo\xc8\x15\x00)\xbc+\x81\xf3C\ +\x06\xb0\xeeZ\xf0i\x13\xaf\xfe\xeb\xc3\xfa\x95\x09\x9f~\ +\x1e<\x8c\xab\x80\xa1\xf6\x86\x5c\x01\x90\xc2^\xfd\x9f\x14\ +:\x88\x0b\xbc\xfa\xaf\xbfU\x808\x9e\x088\x05X\xcb\ +\xde\x90+\x00R8?\x07.\x0c\x19@\xdb5a\xf2\ +\x1a^\xfd\xd7\xa7\x0e\xf3\x12\xa6L\x0d\x1e\xc6/\x80\x8b\ +\xec\x0d\xb9\x02 \xd5\xbf\x96DP\x9e5\x92g\xd4s\ +\xe5\x9c#\xa3\x08\xe3T\xa0\xb5\xbd\xa1\xac*\xb3\x09\x94\ +a\x17\x00\xbb\x85\x0c`\x9d6p\xef\x18\xaf\xfe\xeb\xdb\ +V\xa3\x1a\xf3\xfbF\x95\xcc\x9e\x1b4\x8cr\xa0\x12x\ +\xc6\x1e\x91+\x00R\xfd\xa9 p\xc9_\x80s\x8e\xb2\ +#B9\xf3\x88(\xc28\x1dheo\xc8\x15\x00\xa9\ +\xfe\x0c\x05\xf6\x08\x19@\x9b\x96\xf0\x8f\x8f\xbc\xfa\x0fe\ +\x9b\xf7\x1bsKY%s\xbe\x0e\x1aFc`.\xf0\ +\xac=\x22W\x00\xa4\xba\xd7\x028-t\x10\xe7z\xf5\ +\xef*@\xea,\xd2\xfd(R\xa6\xf8\x14\x80\xb2\xe8B\ +\xd2\xdd\xffA\xaf\xfe\xa7\xb6\xf0\xea?\x06mg%|\ +1=x\x18\x17\x91>\x15 \xb9\x02 \xd5\x91\xa6D\ +\xf0J\xd6H\xae<\x05\x9c~x\x14a\x9c\x094\xb7\ +7\xe4\x0a\x80Tw\x86\x02W\x84\x0c\xa0es\xf8\xaa\ +\x95W\xff1Y+I\x98\x96\x04\x0f\xe3\x02\xd2\xaa\x94\ +\x92+\x00R\x1d\x5c\xfd\x9f\x19:\x08\xaf\xfe#\x5c\x05\ +\xf8I\x14a\x9c\x034\xb37\xe4\x0a\x80T\xfb\xce\x05\ +\xae\x0e\x19@E3\x98\xd6\xda\xab\xff\x18\xad9#a\ +\xfa\xcc(\xc6\xe8\xb5\xf6\x86\x5c\x01\x90jO\xe3\x18\xae\ +\xfe#\xb9\xdf\xac\xe58\xf5\xb0(\xc28\xdfU\x00\xb9\ +\x02 \xd5\xae3\x81\xebC\x06\xd0\xa2\x19L\xf7\xea?\ +jm\xa6'\xcc\x98\x15<\x8c\xb3\x80\x1b\xec\x0d\xb9\x02\ + \xd5\xce\xd5\xff9\xc1\xaf0\x0f\xb5#bw\xf2\x8f\ +\xa3\x08\xe3<\xa0\x89\xbd!W\x00\xa4\x9a;\x0d\xb81\ +d\x00M\x9b\xc0\xcc\xb5\xbc\xfa\xcf\x82\x96_%\xcc\x9a\ +\x13<\x8c\xd3\x81_\xd9\x1br\x05@Z}\xe5\xa4\x1b\ +\xab\xc2^\xfd\x1ffGd\xc5I\x87D\x11\xc6PW\ +\x01\xe4\x0a\x80T3'\x037{\xf5\xafUQ\xf1e\ +\x12\xfaM\x81\x00\xa7\x00\xbf\xb67\xe4\x0a\x80\xb4\xea\x1a\ +\xc6p\xf5?\xe4`;\x22kN\x1c\x14E\x18\xe7\x93\ +\xae`I\xae\x00H\xab\xe8\x04\xe0\x96\x90\x014.\x87\ +9\xebx\xf5\x9fE\xcd\xa7&\xcc\x9d\x17>\x17\x01~\ +go\xc8\x15\x00i\xd5\xae\xfe\xcf\xf7\xea_\xab\x9d=\ +\xc6\xb1\x0a0\x0chdo\xc8\x04@Zy\x83\x81N\ +\xa1\xaf\xfe\xaf\x9c\xe7\xfb]\xb2\xea\xdag*h\x12~\ +\x01\xbe#`\xf1h\x99\x00H+\xa9\x8c\xf4Y\xea\xa0\ +\x8e?\x10\x1a\x8c\xf0\x10\xc9\xb2c\x0f\x8c\x22\x8c\x8b\x5c\ +\x05\x90\x09\x80\xb4r\x8e\x00\xba\x85\x0c\xa0\xbc\x11\x5c\xb5\ +\x86\x15]\xb3\xee\x97\xcfG\xb1\x0a\xb0>\xe0\x83\xa4\x8a\ +\x8e\x9b\x00\x15r\xec\xadW\x98\xe8\x97\xfct\x07:\x87\ +\xbeb:\xe9\x10\xb8\xe9%7\xff\x15\x83S\xb7I\xf8\ +\xcd\xff\x05\x0fc>0\x0e\x18\x03\x8c]\xea\xf3)P\ +eO\xc9\x04@\xc5\xa6UaB\xef\x0c\xf4\x06z\x15\ +\xfe\xfb\x86D\xfa\xd2\x94\x86\x0d\xe0\x8b\xcb\x1a\xd3\xfc\xb7\ +>\xc1U\x0c\xe6\x1d\xb4\x90\xd67\xcd\xa1r~\xb4!\ +\xce/$\x01\xe3\x80\xf7\x81\xf7\x0a\xff}\x1c0\xde\xe4\ +@&\x00\xca\xca$\xbf\xe4d\xdf\x07h\x91\xb5\x1fs\ +\xc2 \xf8\xcd\x08\xaf\xfe\x8b\xc9I[%\xfc\xee\x9eL\ +\x86^\x09L^NbP\xfd\x91L\x00T\xe7Z\x02\ +]\x963\xd1o\x04\xb4-\x96\x1f\xd9\xb0\x01L\xbe\xb9\ +\x11m.\xb7\x8ak1\x99;x\x01m\xae\x9c\xcb\xfc\ +\x05E\xf5\xb3f\x00\x1f/')\x18\x05\xfc\xd7^\x97\ +\x09\x80VE9\xd0\x9eo/\xd5W\x7f:\xe5a\xbc\ +\x1c;\x10~\xf7\x9aW\xff\xc5\xe8\x84-\x12n\xbb/\ +7?w\xfaRIA\xf5\x0a\xc2X`\xa6\xa3A&\ +\x00\xf9\xd4\x88t\xf3]\xe7\xe5L\xf4\x1b\x90\xe3'C\ +\xcaJa\xf4\xfd%t:\xa5\x85\xa3\xa4\x08\xcd\x1aR\ +\xc9Z\xc3\xe7\xb1`a\xee\x9bb\xfar\x12\x83q\xc0\ +h`\xb6#\xc5\x04@\xd9\xd6\x10\xe8\xc0\xb2\xf7\xe4{\ +\x91>\x92Tf\x13-\xeb\xc8\xfd\xe0\xf67\xbd\xfa/\ +f\xc7\xfc \xe1O\xf7\xdb\x0e\xdf\x93\x1c,o\xbf\xc1\ +\x07\xc0\x5c\x9b\xc7\x04@\xf1h\xc7\xb2K\xf5\xbdI\x1f\ +\xa5k`\xf3\xac\xda\xd5\xff\xfb\x0fB\xd7\x93L\x00\x8a\ +\xd9\x94+g\xd3\xe9\xf0E,\x5cd[\xac\x86\xcf\x96\ +\x93\x18\xbc_X9p]\xc5\x04@u\xa0\x15\xcb\xbf\ +'\xdf\x0b\xdf7^k\x0e\xdf\x1b\xfe\xf4\x8e\x93\x7f\x1e\ +\x1c\xd97\xe1\xce\x7f\xd9\x0e\xb5h\x010\x89\xe5\xef9\ +\xf8\x040\xdd2\x01\xd0\xf7L\xf2\xcb{V\xbe\x07\xd0\ +\xd4\xe6\xa9\xfb\xab\xffQ\x0f@\xf7\x93M\x00\xf2`\xfc\ +\xcd3\xe9\xb1\x7f\x95\xab\x00\xf5c\xc9\x1a\x07K\xef9\ +\x98\x00,\xb6\x89L\x00\xf26\xc9/9\xd1w\x07|\ +\xe3L@\x87\xee\x09w\x8et\xf2\xcf\x93\xc3\xfb$\xdc\ +\xfd\xb0\xed\x10\xd8\x8aj\x1cX\x00\xc9\x04 \xf3\x93|\ +\xf5D\xbf\x11\xe0\x0c\x13\xa1\xd2Rx\xe7>\xe8u\xba\ +\xdd\x93'\xa3oN\xd8h?X\xec\xf5g\xac\xe6\x15\ +\x12\x81\xa5\x13\x83\xf7H\xf7\x22\xc8\x04 \x88r\xa0+\ +\xcb\xde\x93\xaf\xfe(C\x06\xed\x06\x7f\xfd\xc0\xc9?\x8f\ +~\xdc3\xe1\x9e\xc7l\x87\x0cZ\xba\xc6A\xf5\xad\x85\ +w\xb1\xc6\x81\x09@-X\xd1\xb3\xf2\x9dl\xb7\x22\x19\ +\xfc%\xf0\xf6}\xb0\xd1\x19&\x00y\xf4\xe1M\x09}\ +\xf6w\x15\xa0\x88\x93\x03k\x1c\x98\x00|\xa7\xa5\x9f\x95\ +_r\xa2\xdf\x00_\x95\x5c\xf4\x06\x0e\x80{F;\xf9\ +\xe7\xd9\xa0\x0d\x13\xee{\xdcv\xc8Yr\xb0\xf4\x9e\x83\ +\xdc\xd68(\xf6\x04\xa0\x01\xd0\x91\xe5\xdf\x97\xdf\x10\x0b\ +\xe2\xe4\xfa\xea\xff\xb5\xbf\xc1\xa6\xe7\x98\x00\xe4\xd9\xfb7\ +&\xf4\x1d\xe8*\x80\x96[\xe3\xa0z\xcf\xc1<\x13\x80\ +\xb8-\xaf N\xf5D\xdf\xd8\xb1\xad\xa5\xed\xb7\x0b\xdc\ +7\xd6\xc9_0\xb0[\xc2\xfdO\xda\x0eZ\xae\x85\xc0\ +D\x96\xbf\xe7\xe0C2^\xe3 K\x09@+\x96\x7f\ +O\xdeg\xe5\xb5\xca^\xfd?\xd8\xec<\x13\x00\xc1;\ +\xd7%l6\x08\xaa|\xe8L\xabf\xe9\x02H\x99\xab\ +q\x10[\x02\xf0]\x05q|V^\xb5f\xaf\x1d\xe1\ +\x81\xf1N\xfe\xfa\xc6\xbe\x9d\x12\x1ez\xd6vP\xadY\ +\xb2\x00\xd2\xd2{\x0e\xa2\xa9q\x10*\x01h\x09\xf4\x03\ +\xfa\x16&\xf9\xea\xfa\xf5\xbe\x86Mu\xee\x95\xbf\xc2\xe6\ +\xe7\x9b\x00\xe8\x1bo\x5c\x9d\xb0\xe5!\xb6\x83\xea\xc5L\ +`L!)x\x8f\xf4\xf1\xc5W\x80\x19\xc5\x9a\x00T\ +\x00\xbb\x00\xbb\x01\xdb\x01=\xf1Q:\x05\xb0\xf7Np\ +\xff8'\x7f-k\xbf\xce\x09\xffz\xc6vP\x10\x8b\ +I\xf7\x14<\x0f<\x06\xa9\xe0\x94C\ +m\x07E\xab;\xf0w\xe09\xa0O}&\x00\x8d\x80\ +\x9f\x01\xaf\x93\xee\xe8\x97\xa2\xd3y=\xf8\xf7-\xf0\xcb\ +\xe7\x9d\xfc\xb5zn|\xa1\x82G~\x0b\x9d\xda\xdb\x16\ +\x8a\xd6\xf6\x85\xb9\xf8\xa7\xa4\xef\xb7Y%\xabz\x0b`\ +}\xd2{\x0f[\xda\xee\x8a\xd1\xe6\xbda\xc8!p\xe4\ +\xef\x9d\xf8U{\xfexl\xc2-\x7f\x83\xd7\xdf\xb3-\ +\x14\xadW\x80A\xa4\xa5\x8bk=\x01\xd8\x15\xf8+\xe9\ +\xe3}RP\xa5\xa5\xd0\xa1-t[\x1fztJ'\ +\xfe\x1f\xb5iD\x9b\xcb\x9b\xd88\xaa3S/\x9d\xc3\ +c\x93\x17\xf2\xfa{\xf0\xe1x\xf8h\x22L\xfc\xcc\x97\ +\x09)\x1a_\x91>z\xffDm&\x00G\x00\xb7\xad\ +\xce\x12\x83T\x13\xadZ\xa4\xcb\xf9\xbd\xba@\xef\xae\xe9\ +\x7f\xef\xdc\xc1\xb7\xf8)\xbe\xc4`\xd4\x82\x85\x8c\xfb\x14\ +\xde\xfb\x08\xde\x1f\x07\xe3&\xc1\x84)&\x07\xaaw\x0b\ +I\x1f\xc5\xbf\xbd6\x12\x80\x93\x81\x9b\xb0r\x9f\xea\xc8\ +\xda\xad\xd3+\xf9\xee\x1b@\xb7\x8e\xe9\x7f\xef\xda\x116\ +>\xcbI^\xd97\xf2\x86\x84\xb1\x9f\x90~&\xa6\xff\ +9f\x02L\x9df\xdb\xa8\xceT\x01C\x80\xdf\xd5$\ +\x018\x16\xb8\xd5\xc9_5\xd5\xba\x22\x9d\xd8\xbbu,\ +L\xf4\x85I\xfe\x07\xe7:\xc9+\xbf\xde\xbc&\xe1\xa3\ +%\x92\x82\xea\x04aZb\xdb\xa8\xc6\x16\x03\xc7\x00\x7f\ +Z\x9d\x04`\x00\xf0\x08\x96\xf0\xd5J*o\x04]:\ +,\xb1T_\xf8l\xd4\xb0\x01k_\xec\x1b\x9b\xa5U\ +\xf1\xd6\xb5\x09\xe3&\xc1\xb8O\xf9\xdf\xad\x85Qc!\ +\x99m\xdbh\xa5-\x04v'}\xb7\xc0J'\x00]\ +\x81WI_\xcf+\xfd\xcf\x1a\x8d\x0bW\xf2\x85+\xf8\ +\xea\xab\xfam\x87{%/\xd5\x97\x17/K\xfe\xb7Z\ +P\xbd\x820\xf6\x13\x98;\xcf\xb6\xd12\xa6\x91>\xb9\ +\xf7\xf1\xca$\x00\x0d\x80\x17H_\xd7\xab\x1cj\xd8 \ +\xdda\xdf\xb9\xc3\x12\x1b\xf0\xbax%/ee\xe5\xe0\ +\xbd\x8f\xe0\xfd\x8f\x0b\xab\x07\x93\xd2'\x16\xe6|m\xdb\ +\xe4\xd8\xeb\xc06\xc0\x82\xefK\x00~JZ\xe8GE\ +>\xc9o\xd0\xbe\xb0\xf9n\xfdo\xae\xea7\x9bSN\ +\xcb\xeb\x1b\xdb@R\x91\x99q\xd6<\xdehZ\xf9\xbf\ +\xd5\x821\x85\xff\x9c0\xd97 \xe6\xc4E\xc0/V\ +\x94\x00t\x06\xde\x03\x9c\x01\x8a\xc4\xbak-{O\xde\ +\xc7\xe8$-i\xc9\xc7\x18\x97\xdcs0z<,\xf2\ +1\xc6b\xf15\xd0\x0b\x98\xf0]\x09\xc0\xdf\x81\x81\xb6\ +Sv\x94\x94\xc0z\xeb|s\x05\xdf\xadczo~\ +\xc3N\xd0\xfdd'yI53\xe6\xd7I\xfa\x84\xc2\ +\x12\x8f1\x8e\xfd\x04>\xfd\x1c\xaa\xaal\x9f\x8c\xb9\x87\ +\xf4\xcd\xbd\xcb$\x00\x1b\x03o\xe3#\x7fQ\xaa.\x88\ +\xd3\xb9\xc37\xf7\xe4\xbd\x92\x97\x14\xc3\xca\xc1{\x1f\x7f\ +{\xcf\xc1\xf8\xc9&\x07\x91\xaa*\xcc\xf5\xa3\x96N\x00\ +\xfe\x02\xf8\xfe\xab\x80\xd6l\xf5M!\x9c\xee\x1b\x14v\ +\xd9w\x84M\x9c\xe4%e\xcc\xdb\xd7\xa6O*|4\ +\x91o\xad |9\xdd\xb6\x09\xec\x0e`\xf0\x92\x09@\ +k`\x0aPn\xdb\xd4\xad\x96\xcd\xd3g\xe5\xabw\xd8\ +W\x7fv\xbe\xccI^R>,]\xe3`\xdc\xa7i\ +\x8d\x83\xff~i\xdb\xd4\x83\xf9@;\xe0\xab\xea\x04\xe0\ +d\xe0f\xdb\xa5v\x947\x82\xf6k/U\xbf\xde\x82\ +8\x92\xb4J\xc9A\xf5\xe3\x8cc'\xc2L\x0b \xd5\ +\xa6\x93\x80\xdfV'\x00O\x03;\xd9&+\xafq\xf9\ +7K\xf4\xdd\x96x\x8cn\xbb?\xb5\xa0d\x9c\xdb(\ +$\xa96Uu\xae\xe2\x85#g~\xf3^\x85\xea\x22\ +H\x13a^\xa5\xed\xb3\x8a\x9e\x06v.\x01\x9a\x03_\ +\x02\x8dl\x93o[\xba NuQ\x9c-\x9ay%\ +/I1y\xe9\xe7\xc97\x1b\x11\x0b\x9b\x11?\x18g\ +u\xc4\xef0\x1fhS\x02\xfc\x08x8\xaf\xad\xd0\xa0\ +\x0c\xd6o\xf7\xcd\x15|uQ\x9c\xde\xcd\xcbh7\xb4\ +\x99\xc3D\x922l\xea\xa5sx\xa7r\xe1\xb7\x8a\x1f\ +\x8d\xfd\x04>\x99\x02\x0b\x17\xe5\xbaiv/\x01.\x06\ +.)\xf6_\xda\xaa\xc5\xb2\xf7\xe4}\x8cN\x92\xf2\x9d\ +\x1c,Y\x00\xa9z\xcf\xc1'SrQ\x00iX\x09\ +ET\xfc\xa7\xfd\xda\xcb\x7fQM\xaf\xd3\x9d\xe4%I\ ++\xef\xfd\x1b\x97\xff\xc2\xa5\xc9S\x8b\xe6'\xdeSB\ +\xfa\x92\x80\xcd\xb2t%_}\xf5\xfe\xbf\x17\xd5t\xf5\ +J^\x92T\x7f+\x07\xaf\xcd^\xb8\xcc\x9e\x83\x8c\x15\ +@\x1aQ\x02L&}&0\x1a\xcd\x9bB\xcf\xce\xcb\ +\xbe\xa8\xe6\x07\xe7:\xc9K\x92\xe2\xf5\xe65\xc92/\ +\x5c\xfa`\x1c\xcc\x9a\x13]\xa8\x93J\x809\xc0\x1a!\ +\xa3\xe8\xb8.\xec\xb9\x03\xec\xb4\x05l\xd6\xbd\x84N\xa7\ +\xb4p\x14I\x92\x8a\xc6\xf8\x9bg\xf2\xc6\x98*\x9ey\ +\x0d\x1ez\x16&\xfd7xH\xb3JH\x1f\x07hX\ +\xdf\xdf\x5cV\x0a\x07\xef\x01\xa7\x1e\x0a[^\xe0\x95\xbd\ +$)?^\xb9\x22\xe1\xa6\xbf\xc0=\x8f\x06\xdbpX\ +Y\x02,\x00\x1a\xd4\xf77\xff\xedZ8\xf0WN\xfc\ +\x92\xa4\xfc\xba\xf7\xb4\x84C\xce\x09\xf2\xd5\x0bJ\x81\x99\ +!\xbe\xf9?/\xd9\xf1\x92\xa4|{\xf4\x85`_\x9d\ +\x94\x023B|\xf3\x1f\xefOk>K\x92\x94Go\ +\x5c\x9dp\xc7\x83\xc1\xbe~z\x09\xf0\x0c\xb0c\x88o\ +\xdfp\x03x\x7f\xb1\xb7\x01$I\xf9\xd3\xa7aZ\xbe\ +8\x90'K\x81\xb1\xa1\xbe}\xf4\x04\x18\xbc\xb1\xab\x00\ +\x92\xa4|9\xbcO\xd0\xc9\x1f\xe0\xe3R\xe0\xbd\x90\x11\ +\xdc\xf5\x10\x5c\xb2\xa7I\x80$)\x1f.\xde=\xe1\xee\ +\xf0o\xe0y\xaf\x14\x18\x11:\x8aK\x7f\x0b\xd7\x1dd\ +\x12 I*n\xbf>,\xe1\x17\xb7F\x11\xca\xcb%\ +@90\x1dh\x122\x92\x92\x12\xb8i\x18\x0c\xb9\xd3\ +=\x01\x92\xa4\xe2\xf3\xdb\xc3\x13N\xbd<\x8ar\xc1s\ +\x81V\xa5@%\xf0t\xe8h\xaa\xaa\xe0\xd4\xcb\xe1\x97\ +\x07\xbb\x12 I*.\xb7\x1c\x11\xcd\xe4\x0f\xf0\x040\ +\xbf\xac\xf0?Z\x00{\xc5\x10\xd5\x7f^\x82\x06\x87W\ +\xb2\xc3\xe8\xc6\x8e\x18IR\xe6]wP\xc2\x99WE\ +\x15\xd2\xf5\xc0\x1b%\x85\xff\xb16\xf0)\x01J\x02\x7f\ +\x97\xf3\x8f\x81\xcb\x1f\xf7v\x80$)\xbb\xae\x19\x980\ +\xf4\x86\xa8BZ\x00\xb4\x07\xbe\xa8^\x01\x98C\xfaJ\ +\xe0\x1e\xb1D\xf8\xe2[0o`%\xfd\xc7\xb9\x12 \ +I\xca\x9eK\xf6L\x18~Sta=\x00\xfc\x11\xa0\ +l\x89\x7f8\x1b84\xa6(_|\x0b\xe6\xee_\xc9\ +\x80\xf1&\x01\x92\xa4\xec8\x7f\xe7\x84+n\x8b2\xb4\ +\xb3\x81\x8f\x00J\x96\xf8\x87%\xc0(\xa0Wl\xd1\x9e\ +8\x08~=\xc2\xdb\x01\x92\xa4\xf8\x9d\xbdc\xc2/\xef\ +\x8c2\xb4Q\xc0\xc6@\xd5\xd2+\x00\x90>\x11\xb0O\ +l\x11\xbf\xfe\x1eL\xdd\xad\x92=?u%@\x92\x14\ +\xaf\x93\xb7J\xb8\xf9\xaf\xd1\x86w>\xf0\xf6\x92W\xfd\ +Kj\x00\x8c$\xa2\xbd\x00K:l/\xb8\xe3]W\ +\x02$I\xf19n\xb3\x84?\xfc3\xda\xf0\xc6\x00\xbd\ +\x81\x85\xd5\xff`\xe9\x15\x80\xc5\xc0\x97\xc0\x811F?\ +r\x0c\x8c\xd9\xa6\x92\x81_\xb8\x12 I\x8a\xc7\xd1\x9b\ +&\xfc\xe9\xfe\xa8C<\x9e\xa5J\xff\x97,\xe7_*\ +\x01\x9e\x07\xb6\x8d\xf5W\x1c\xbc;\xdc\xfd\xbe+\x01\x92\ +\xa4\xf0\x0e\xed\x95\xf0\xb7G\xa3\x0e\xf1Y\xe0\x87\x14\xee\ +\xfd\xaf(\x01\xa0\xb0L\xf0\x16\x11\xd5\x05X\xda^;\ +\xc2\x03\xe3M\x02$I\xe1\x1c\xd4=\xe1\x1fOD\x1d\ +\xe2|`S\xe0\xfd\xa5\xff\x8f\xb2\xef\xf8\x03_\x00\x8d\ +\x81\xedc\xfdEc>\x8177\xa9\xe4\xc73\xbc\x1d\ + I\xaa\x7f\xfbvJx\xe0\xe9\xe8\xc3\xbc\x02\xb8g\ +y\xffG\xc9\x0a\xfePC\xe0E`\x8b\x98\x7f\xd9n\ +\xdb\xc2#\x93\x5c\x09\x90$\xd5\x9f\xfd:'\xfc\xeb\x99\ +\xe8\xc3|\x13\xd8\xba\xb0\x0a\xb0J\x09\x00@O\xe0u\ +`\x8d\x98\x7f\xe1.[\xc1\x7f\xa6\x98\x04H\x92\xea\xde\ +\xae\xed\x12\x9e\x1c\x11}\x98\xd5\x15~G\x7f\xd7\xbfP\ +\xfa=\x7f\xc1\x07\xc0q\xb1\xff\xca'G\xc0\x8ek\xfa\ +\x16AIR\xdd\x1a\xb0n&&\x7f\x80\x93V4\xf9\ +\xafL\x02\x00p7\xf0\xab\xd8\x7f\xe9\x0boB\xbf\xe6\ +&\x01\x92\xa4\xba\xb1]\xab\x84\xa7^\xc9D\xa8\xd7\x03\ +w|\xdf\xbfT\xb2\x92\x7fYC\xe0!`\xd7\xd8\x7f\ +\xf5\x16\x1b\xc1\x88\x99\xde\x0e\x90$\xd5\x9e\xadZ$\xbc\ +6*\x13\xa1>\x0a\xec\xcd\x12\x05\x7fj\x9a\x00\x004\ +'}\x96p\xd3\xd8\x7f\xfd&=\xe0\x8d\xb9&\x01\x92\ +\xa4\x5cM\xfeo\x02;\x92\xbe\xdc\xef{\x95\xac\xe2_\ +\xde\x0ex\x0e\xe8\x12{+\xf4\xe9\x06oW\x9a\x04H\ +\x92jpAY\x9e0rl&B\xfd\x08\xd8\x01\xf8\ +le\xff@\xe9*~\xc1\x14\xd2jB\xe3co\x89\ +\x91c\xa1Wi\xc2\xc2\xad\x16;\x82%I\xab\xaco\ +v&\xff\x89\xc0\x80U\x99\xfcW'\x01\x00\x98\x04\xec\ +R\xf8\xc2\xa8\x8d\x9e\x00}^\x9d\xc5\xfc\xdd\x169\x92\ +%I+e\xfen\x8b\xe8Y\x9a0*;\x93\xffN\ +\xc0\x84U\xfd\x83\xa5\xab\xf9\x85\xe3I\xab\x04~\x1c{\ +\xcb\x8c\x99\x00=\x1e\x9e\xcd\xd4K\xe78\xaa%I+\ +4w\xf0\x02\xfa<6\x9b1\x132\x11\xee'\xd4`\ +U\xbe\xb4\x06_<\xb1\xf0\xc5\xd1\xe7H\x9fL\x81~\ +\x17,d\xd2\xf5\xb3\x1c\xdd\x92\xa4\xe5\x9aq\xd6<\xfa\ +\xfeq.\x1fM\xccD\xb8\xe3\x0bs\xf0\xb8\xd5\xfd\x0b\ +Jj!\x88u\x80'\x80\x8dbo\xad\xb6k\xc2\x93\ +\xb7C\x8fS\xdd\x1c(I\xfa\xc6G\xbfI\xd8\xe5\x18\ +\xf8\xf4\xf3L\x84;\x9a\xf4V\xfc\xe4\x9a\xfc%\xa5\xb5\ +\x10\xc8\xe7\x85@F\xc6\xdeb\xff\xfd\x12v>\x1a\xde\ +\xbf\xd1\x82A\x92\xa4\xd4\x98_'\xfc\xf0\xe8\xccL\xfe\ +\x1f\x14\xae\xfc'\xd7\xf4/*\xad\xa5\x80\xa6\x92>{\ +\xf8Z\xec-\xf7\xf9W\xb0\xcb10\xea\x97&\x01\x92\ +\x94w\xa3oN\xd8\xf9h\x9825\x13\xe1\xbe]\x98\ +k?\xab\x8d\xbf\xac\xb4\x16\x03\x9bNZ)0\xfaB\ +\x89S\xa7\xc1NG\xc2\x1bW\x9b\x04HR^\xbd{\ +}\xc2\x0e\x83\xe1\xb3/2\x11\xee\x9b@\x7f\xa0\xd6\xa2\ +-\xad\xe5\x00g\x00\xbb\x01/\xc7\xde\x92\xd3g\xc2\xae\ +\xc7\xc1\xabW\x98\x04HR\xde\xbcumB\xffc\xe1\ +\xcb\xe9\x99\x08\xf7u\xd2\xe7\xfc\xbf\xaa\xcd\xbf\xb4\xb4\x0e\ +\x02M\x0a\x81>\x1d{\x8b\xce\x98\x05\xbb\x9f\x00#.\ +7\x09\x90\xa4\xbcx\xf3\x9a\x84\x01\xc7\xc2W32\x11\ +\xee\x0b\xa4\xfb\xec\xa6\xd5\xf6_\x5cZG\x01\xcf\x01\xf6\ +\x22}: j\xc9\xect%\xe0\x99\x9f\x9a\x04HR\ +\xb1{\xe9\xe7\xe9=\xff\xe933\x11\xees\xc0\x8f\x80\ +:\x89\xb6\xb4\x0e\x03\x9f[H\x02\x1e\x8a\xbd\x85\xe7|\ +\x0d{\x9f\x0cO\x0e7\x09\x90\xa4b\xf5\xfc\xa5\x09{\ +\x9c\x08\xb3\xb2Q\x17\xee\x19`O\xa0\xce\x0a\xd8\x94\xd6\ +\xf1\x0f\xa8\x04\x06\x02\x0f\xc4\xde\xd2s\xe7\xa5I\xc0\xc3\ +\xe7\x99\x04HR\xb1y\xfc\xc2\x84=N\x80\xd9s3\ +\x11\xee\xbf\x81=X\xc9\xb7\xfa\xc5\x9a\x00\x00\xcc\x07\x06\ +\x01\xff\x88\xbd\xc5+\xe7\xc3\x81g\xc2\x83g\x9b\x04H\ +R\xb1xth\xc2~\xa7\xc1\xd7\x95\x99\x08\xf7a`\ +\x7f`^]\x7fQi=\xfd\xa0\xea$\xe0\xae\xd8[\ +~\xfe\x02\x18t6\xfc\xf3L\x93\x00I\xca\xbaG\xce\ +O8\xe0\x0c\x98\x97\x8d\xc9\xff\xde\xc2\xe4_/\xd1\x96\ +\xd5\xe3\x0f\xab\x02\x1e\x04\xd6\x076\x89\xb9\x07\x16/\x86\ +\xfb\x1e\x87.C+\xe9\xf3Zc\x8f I\xca\xa0\xbf\ +\x9f\x96p\xf09\xe9\x85]\x06\xfc\x1f\xf0\x13`a}\ +}aY=\xff\xc0\xea$\xa0#\xb0i\xcc=QU\ +\x05\x0f>\x05\x1b\x9c[I\xdf7L\x02$)K\xee\ +95\xe1'Caa6\xde\x06\x7f7pD}N\ +\xfe!\x12\x80%\x93\x806\xc0\x96\xb1'\x01\x0f<\x0d\ +k\x9fZ\xc9\xe6\xef\x98\x04HR\x16\xfc\xf1\xd8\x84\xa3\ +.\x82E\x8b3\x11\xee\xef\x81c\x80zOU\xca\x02\ +\xfe\xe8G\x81\x96\xc0V\xb1\xf7\xce\xbf\x9f\x876'U\ +\xb2\xe5H\x93\x00I\x8a\xd9\xedG'\x9cpIz+\ +7\x03~\x07\x0c\x01\x82D[\x16\xf8\xc7?\x0a4\x02\ +\xb6\x8f\xbd\x97\x1e}\x11Z\x9eXI\xbfQ&\x01\x92\ +\x14\xa3[\x8fL\x18ri\xbaz\x9b\x01\xd7\x03\xa7\x93\ +\xae\x8a\x07Q\x16A#\x9c\xb9}\xc2\x8dwe\x22\xd4\ +*\xe04\xe0\xa6\x18\x83+\x8b\xb8\xe1\xde\x04>\x02\xf6\ +\xa5\xfe\xdeY\xb0Z\xdex\x0f\xa6\xf4\xafd\xef)&\ +\x01\x92T\x97N\xdb6\xe1\xe6\xbb3\x11\xeab\xe08\ +\xd2g\xfd\xa3T\x16y\x03\x8e\x04\xc6\x02\xfb\xc5\x9e\x04\ +\xbc\xf9\x01|\xb4]%\x07L5\x09\x90\xa4\xbap\xec\ +f\x09\xb7\xde\x9b\x89P\x17\x91V\xf7\xfbS\xccA\x96\ +e\xa0!G\x01\x1f\x16\x92\x80\xa8\xe3\x1d9\x16Fo\ +]\xc9\xc0/L\x02$\xa96\x1d\xb5I\xc2\x9f\x1f\xc8\ +\xcc\xe4\x7f$pg\xec\x81\x96d\xa8\xff\xf7\x02\xfe\x0e\ +\x94\xc7\x1e\xe8\x81\xbb\xc2\xdf>\xac\xf0\x88\x95\xa4Zp\ +\xc4\xc6\x09\x7fy(\x13\xa1\xce\x07~\x0c\xfc#\x0b\xc1\ +\x96dl\x1c\xecQh\xd8\xe8/\xb1\xf7\xdc\x01\x1e\x9c\ +`\x12 I5\xba\xa0\xea\x96\xf0\xcf'33\xf9\x1f\ +\x0c\xdc\x9f\x95\xb6-\xc9\xe0x\xd8\x0d\xf8'\xd0$\xf6\ +@w\xdf\x0e\x1e\x9eh\x12 I\xabc\xff.\x09\x0f\ +>\x9d\x89P+\x81\x83\x80\x7fe\xa9}K2:.\ +v\x00\x1e\x02\x9a\xc7\x1e\xe8\x8e\x9b\xc3SSM\x02$\ +i\x95.\xa0\xda'<\xfer&B\x9dK\xfa\xb4\xda\ +\x13Yk\xe3\x92\x0c\x8f\x8f\xed\x80\x87\x81\x16\xb1\x07\xba\ +\xfdf\xf0\xcc\x17&\x01\x92\xb42vm\x97\xf0\xe4\x88\ +L\x84:\x07\xd8\x87\xf4\xa5v\x99S\x92\xf1q\xb29\ +\xf0\x18\xd0:\xf6@\xb7\xdd\x14\x9e\xfb\xca$@\x92V\ +\xa4\x7f\xdb\x84\xa7_\xcdD\xa8\x09\xe9\xbe\xb4\x97\xb3\xda\ +\xd6%E0^~\x00\xfc\x07h\x13{\xa0\x9b\xf5\x82\ +Wg\x9b\x04H\xd2\xf2l\xd7*\xe1\xe5w2\x11\xea\ +\x0c`w\xe0\x95,\xb7wI\x91\x8c\x9bM\x80\xc7\x81\ +5c\x0ft\xd3\x9e\xf0\xfa\x1c\x93\x00IZ\xd2\xd6\x15\ +\x09\xaf\x8e\xccD\xa8\xd3I7\xa3\xbf\x96\xf56/)\ +\xa2\xf1\xd3\x13x\x12X7\xf6@\xfbn\x08o~m\ +\x12 I\x00\xfd\x9a'\xbc\xfe^&B\x9dJ\xfa\xb6\ +\xdaw\x8b\xa1\xddK\x8bh\x0c}\x00\xfc\x10\x98\x1c{\ +\xa0\xef\x8c\x86\xdee\x09U\x9d\xab<\xf2%\xe5\xda&\ +\xe5\x99\x99\xfc?\x07v)\x96\xc9\xbf\xd8\x12\x00\x80\xd1\ +\xc0\xf6\xc0\x84\xd8\x03\xfdp<\xf4\x9e0\x93\x85[-\ +\xf6\x0c )\x976n\x940rl&B\xfd\x0c\xd8\ +\x99\xb44}\xd1(-\xc215\x1e\xd8\x09\x18\x17}\ +\xb62\x016|q\x16_\x0d\xfb\xda3\x81\xa4\xdc\x98\ +w\xd0B6$\xe1\xbd\x8f2\x11\xee\xc4\xc2\x85\xe5\xfb\ +\xc5\xd6\x0f\xa5E:\xbe>!\xbd\x1d\x10\xfd\xf0\x9a0\ +\x19\xb6\xb8t>S\xae\x9c\xedYAR\xd1\x9b5\xa4\ +\x92\x8d\xef\x99\xc3G\x133\x11\xee\x84\xc2\x5c\xf2q1\ +\xf6EI\x91\x8f\xb5\xb6\xa4\x1b\x03{\xc5\x1eh\x87\xb6\ +\xf0\xf4\xad%t:\xa5\x85g\x08IEi\xc6Y\xf3\ +\xd8\xfc\x9aJ\xc6O\xceD\xb8cH\xef\xf9\x7fZ\xac\ +\xfdQ\x92\x831\xb7\x0e\xe9#\x82}\xa2\xcfV\xd6\x84\ +'n\x83\x9e\xa7\xf9\x84\x80\xa4\xe22\xf67\x09\xbb\x1c\ +\x0d\x93\xa7f\x22\xdc\x0f\x0b\x93\xff\x94b\xee\x93\x92\x9c\ +\x8c\xbd\xb5H\xeb4o\x1c{\xa0k\xb7\x86'n\x87\ +\xde\xa7\x9b\x04H*\x0ec~\x9d\xb0\xcb10%\x1b\ +\x93\xff\x07\x85\xc9\xff\xb3b\xef\x97\xd2\x9c\x8c\xbf/H\ +7\x06F_`r\xea4\xd8q0\xbcyM\xe2Y\ +CR\xe6\x8d\xbc!a\x87#23\xf9\xbfE\xfa\xb2\ +\xb9\xcf\xf2\xd07\xa59\x1a\x87\xd5\xd5\x9b\xa2\x7f\xc5\xc4\ +\xf4\x990\xe0Xx\xfd*\x93\x00I\xd9\xf5\xceu\x09\ +\xfd\x8f\x85/\xa6g\x22\xdc7H\x8b\xfc|\x99\x97\xfe\ +)\xcd\xd9x\x9cQ\xe8\xe0g\xa2\x0ft\x16\xecz\x1c\ +\xbcr\x85I\x80\xa4\xecy\xeb\xdat\xf2\xff2\x1b\x93\ +\xffK\xa4\xcf\xf9\x7f\x95\xa7>*\xcd\xe1\xb8\x9c\x0d\xec\ +E\x06^\xdf\x98\xccNW\x02\x9e\xfd\x99I\x80\xa4\xec\ +x\xf9\x17\x09;\x1f\x0d\xd3\xb2q\xeaz\x9e\xf4\xc5>\ +3\xf3\xd6O\xa59\x1d\x9fs\x80\xbdI\x9f\x0e\x88;\ +\xd0\xafa\xaf\x93\xe0\xe9\x8bM\x02$\xc5\xef\xc5\xcb\x12\ +\xf68\x11ff\xa3\xb4\xc9\xb3\xc0\x8f\x80Yy\xec\xab\ +\xd2\x1c\x8f\xd3\xb9\x85$\xe0\xc1\xe8\x03\x9d\x07\xfb\x9c\x02\ +O\x5cd\x12 )^\xcf]\x92\xf0\xa3!0kN\ +&\xc2}\x14\xd8\x83tU8\x97Js>^+\x81\ +\x83\x80\xfb\xb3\x90\x04\xec}2\x03k\x9dR\xc9\x16\xef\x9a\x04H\xaa\x1f\x7f8\ +&\xe1\xe8\xe1\xe9-\xc9\x0c\xb8\x158\x91\xf4V\xafL\ +\x00V\xca\xa3@\x05\xb0U\xf4\xd9\xca\x0b\xd0\xfa\xa4J\ +\xb6\x1ci\x12 \xa9n\xfd\xfe\xc8\x84!\x97\xc1\xe2l\ +L\xa7\xb7\x00CHWwe\x02\xb0J\x1e\x03\x1a\x90\ +\xbe\x1c\x22\xee@_\x84\x8a\x13*\xd9j\x94I\x80\xa4\ +:\x9aM\x8fH8\xf9\xe7\xe9\xeac\x06\x5c\x0b\x9cn\ +\xaf\x99\x00\xd4\xc4\xd3\xa4\xafM\xde)\x0bI@\x83\xc3\ ++\xd9a\xb4I\x80\xa4\xdau\xddA\x09g^\x95\x99\ +p\xaf\x02\xce\xb7\xd7L\x00j\xc33\xa4\xcf\x8c\xf6\x8f\ +>[y\x15*\x0f\xacd\x97q&\x01\x92j\xc75\ +\x03\x13\xce\xbf>S\x93\xffP{\xcd\x04\xa06\xbdH\ +\xfa\x0e\x81]\xa3\x0f\xf4-\x93\x00I\xb5\xe3\xd2\xbd\x12\ +\x86\xdf\x94\x99p/.|d\x02P\xeb^\x02\xa6\x92\ +\xbe<\xa2$\xf6$`\xce\xfe\x95\x0c\x18o\x12 i\ +5g\xd3\xdd\x13.\xbb%\x13\xa1V\x01g\x17\xae\xfe\ +e\x02Pg^\x07>\x03\xf6\x8c=\x09x\xf9m\x98\ +\xba[%{~j\x12 i\xd5\x9c\xbdc\xc25\x7f\ +\xcc\xcc\xe4\x7f\x06p\xa3\xbdf\x02P\x1f\xde\x00&\x03\ +{\xc5\x9e\x04\xbc\xfe\x1e|6\xa0\x92\xbd&\x9b\x04H\ +Z9gl\x9f\xf0\xab\xbb23\xf9\x9f\x0a\xdcl\xaf\ +\x99\x00\xd4\xa7\xb7\x80\xb1\xc0\xbeD\xfeR\xa57\xdf\x87\ +Ow\xaed\x9f\xcfL\x02$\xad\xd8\xa9\xdb$\xfc\xfa\ +\xaf\x99\x08u\x11p\x0c\xf0{{\xcd\x04 \x84Q\xc0\ +h`\xbf\xd8\xdb\xf2\xad\x0fa\xcc6\x95\x0c\xfc\xc2$\ +@\xd2\xf2\x1d\xbdi\xc2m\xf7e\x22\xd4E\xc0\xd1\xc0\ +\x9f\xed\xb5\xd5Wb\x13\xd4\x8a\xbd\x81{\x81\xf2\xd8\x03\ +\x1d\xb4\x1b\xfc\xf5\x83\x0a{L\xd2\xb7\x0c\xde8\xe1\xae\ +\x872\x11\xeaB\xe0H\xe0/\xf6\x9a\x09@,\xf6\x04\ +\xfe\x0eD\x7f\x89\xbd\xd7\x8e\xf0\xc0x\x93\x00I\xa9\x83\ +\xba'\xfc\xe3\x89L\x84:\x1f8\x04\xf8\xa7\xbdf\x02\ +\x10\x9b\xdd\x81\x7f\x00Mb\x0f\xf4G;\xc0\xbf&\x98\ +\x04Hy7\xb0[\xc2\xfdOf\x22\xd4J`\x10\xe9\ +\x1b[e\x02\x10\xa5\x1d\x81\x87\x80f\xb1\x07\xba\xdb\xb6\ +\xf0\xc8$\x93\x00)\xaf\xf6\xeb\x9c\xf0\xafg2\x11\xea\ +\x5c\xd2\xbdV\x8f\xdbk&\x00\xb1\xdb\x1ex\x18h\x1e\ +}\xb6\xb29<5\xd5$@\xca\x9b\xdd\xdb'<\xfe\ +rf&\xff}\x80'\xed5\x13\x80\xac\xd8\x16x\x04\ +h\x11{\xa0\xdb\xfd\x00\x9e\xfd\xd2$@\xca\x8b\x01\xeb\ +&<\xf5J&B\x9dC\xba\xc9\xfai{\xcd\x04 \ +k6\x03\xfe\x03\xb4\x8e=\xd0\xcd{\xc3+\xb3L\x02\ +\xa4b\xb7}\xeb\x84\x97\xde\xceD\xa8\x09\xe9\xbe\xaa\x11\ +\xf6\x9a\x09@VmZH\x02\xd6\x8c>[\xe9\x05\xaf\ +\xce6\x09\x90\x8a\xd5\xb6\xad\x12F\xbc\x93\x89Pg\x00\ +\xbb\x01\xaf\xdak&\x00Y\xd7\x8b\xf4\xfeU\xdb\xd8\x03\ +\xdd\xa4\x07\xbc1\xd7$@*6[\xb5HxmT\ +&B\xfd\x02\x18\x00\xbcc\xaf\x99\x00\x14\x8b\x1e\x85$\ +\xa0]\xec\x81\xf6\xec\x0c\xa3\x16\x9a\x04H\xc5b\xd3\xc6\ +\x09\xef\x8e\xc9D\xa8S\x81\xfe\xc0H{\xad\xee\x95\xda\ +\x04\xf5\xe6C\xe0\x87\xc0\xa7\xb1\x07\xfa\xc18\xe8]\x96\ +P\xd5\xb9\xca^\x932\xaeoyf&\xff\xff\x02;\ +;\xf9\xbb\x02P\xcc\xba\x16V\x02:\xc6\x1eh\xf7\x0d\ +\xe0\x83\xc5\xae\x04HY\xd5\xb34a\xcc\x84L\x84:\ +\xa90\xf9\x7fd\xaf\xb9\x02P\xcc>\x22-\x164>\ +\xf6@\xc7L\x80\xcd\x9b&\xf6\x98\x94A\x9b\xad\x91\x99\ +\xc9\x7fB\xe1\x9c\xe8\xe4o\x02\x90\x0b\xd5\x03~l\xec\ +\x81\xbe\xf5\x01\x1c\xb5\x89I\x80\x94%\x87\xf7Ix\xfb\ +\xc3\xcc\x9c\x0bw\xce\xc2\x05\x91\x09\x80j\xd3$`'\ +\xd2\xbd\x01Q\xbb\xe3Ax\xe4|\x93\x00)\x0b\xfeu\ +N\xc2\xdd\x0fg\x22\xd4\x0fI\x0b\xa69\xf9\x07\xe2\x1e\ +\x80\xf0\xd6\x01\x9e\x006\x8a9\xc8^]`\xe4\x02\xf7\ +\x03H\xb1\xeb]\x96\xf0a\xfcS\xea{\xa4\xbb\xfd\xff\ +k\x8f\xb9\x02\x90g\x9f\x03;\x00\xaf\xc5\x1c\xe4\xfb\x1f\ +\xc3\xbf\x87\xba\x0a \xc5~\xf5\x9f\x81\xc9\xffm\xd2'\ +\xa2\x9c\xfcM\x00\x04L'-y\xf9z\xccA\xfe\xe1\ +\x1fv\x94\x14\xb3?=\x10}\x88o\x00\xbb\x90\x16\xfb\ +Q`\xde\x02\x88K\x05\xf0o`\xeb\x18\x83k\xde\x14\ +f\xb4\xf16\x80\x14\xabV\xd3\x12f\xce\x8ez\xf2\xdf\ +\x15\x98fO\xb9\x02\xa0e%\xa4\xf5\xaf_\x881\xb8\ +Ys\xe0\xdd\xeb\xbd\x0d \xc5\xe8\xcdk\xa2\x9e\xfc_\ + \xdd\xed\xef\xe4o\x02\xa0\x15\xcd\xb3\x85$\xe0\x89\x18\ +\x83\xfbx\x92\x1d$\xc5\xe8\xa3\x89\xd1\x86\xf6<\xf0#\ +`\xa6\xbdd\x02\xa0\xef7\x17\xd8\x97\xf4-\x82Q\xf9\ +\xfc+;G\x8a\xd1\x17q^[?N\xba\xbfi\x96\ +=d\x02\xa0UO\x02\xa2z\xa2w\xd1\x22;F\x8a\ +\xd1\xa2\xc5\xd1\x85\xf4\x08\xb0O\xe1\x5c&\x13\x00\xad\xa2\ +y\xc0\x01@4\xfb\xef+\x9a\xdb)R\x8c*\x9aE\ +\x15\xce\xc3\xc0\xc0\xc29L&\x00ZM\xf3\x81C\x80\ +{c\x08\xa6\xfd\xdav\x88\x14\xa3\x8e\xebF\x13\xca\xdf\ +\x81\xfd\x9d\xfcM\x00T;\x16\x00?\x06\xee\x08\x1d\xc8\ +\xf6\xffq\x09@\x8a\xd1\xb6\xafD\xb1\x04\xf0\xb7\xc2\xb9\ +j\x81=\x12?\xeb\x00dK\x19\xf0{\xe0\xa8\x10_\ +^\xd1\x0c\xa6\xb5\xb6\x0e\x80\x14\xab6\xd3\x13f\x84\xdb\ +nw70\x18XhO\xb8\x02\xa0\xda\xb7\x088\x16\ +x%\xc4\x97\xf7\xe9n\x07H1\xdb\xa8[\xb0\xaf~\ +\x058\xdc\xc9\xdf\x04@uk1\xd02\xc8\xc9\xa5\xab\ +\x8d/\xc5\xacw\xb8c\xb4\xa2pn\x92\x09\x80\xeaP\ +9\xd0%g'\x17I+s\x8cv\x09\xf6\xd5]\x81\ +\xc6\xf6\x80\x09\x80\xeaV/\xa0A\x90\x15\x80n6\xbe\ +\x14\xb3\x80\xc7h\x03`C{ [\xdc\x04\x98=?\ +\x01\xee\x0c\xf1\xc5\x8b:\xba\x01P\x8a]\xd9\xc4`\xef\ +\xeb\xf8\x09\xf0\x17{\xc0\x15\x00\xd5\x9d\xde!\xbe\xb4\xed\ +\x9a6\xbc\x94\x05\xeb\xb4\xc9\xd7\xb9I&\x00y\xb2Q\ +\x90/u\xf9_\xca\xc6\x09\x22\xdc\xb1\xba\x91\xado\x02\ +\xa0bL\x00\xdc\x00(e\xe3\x04\x11\xeeX5\x010\ +\x01P\x1dj\x06\xac\x1f\xe2\x8b}\x02@\xca\x86\x80\xc7\ +\xea\x06\x80\xa5BM\x00T\x87\x19v\x90\x8d\x9b\xae\x00\ +H\x199I\x84\xbb\x05PB\xfa\x94\x92L\x00TG\ +\x09@\xfd\x1f\xd5%\xb0\xe5\x05>\x01 eA\xbf\x0b\ +*(\x09\xf7|\x97\xb7\x01L\x00TG\x82\xec\xb2\x8d\ +\xe8-c\x92\xe2>f}\x12\xc0\x04@\xc5\x94]\xbb\ +\xfc/e\xecJ\xc1\x8d\x802\x010\x01\xa8\x95/\xf5\ +\x11@)['\x0a\x13\x00\x99\x00\x14\x955\x81\xb69\ +\xbb\x9a\x90\x94\xadcv\xdd\xc2\xb9J&\x00\xaa\xcdc\ +:\x87W\x13\x92\xb2w\xcc\xfa$\x80\x09\x80j\xfb\x98\ +\x0e\xf1\xa5e\xa5\xd0\xf7l\x9f\x00\x90\xb2d\x93s*\ +(\x0bwv\xf76\x80\x09\x80\x8aa\x05\xa0kG\x1b\ +^\xca\xa2.\xe1\x8e]\x9f\x040\x01P1d\xd5n\ +\x00\x942z\xc2p#\xa0L\x00\x5c\x010\x01\x90r\ +\x98\x00\x84;v\xfb\xd8\xfa&\x00\xaa=\xed\x81\xd6A\ +\xb2\x8e.6\xbe\x94\xc9+\x86p\xc7n+\xa0\x9d=\ +`\x02\xa0ZJ\xe6\x83\x9dD|\x02@r\x05 C\ +\xe7,\x99\x00\x98\x00\xd4\x82F\x0d\xa1\xc7\xa9>\x01 \ +eQ\x8fS+(od\x02 \x13\x80\xac\x0br\xff\ +\xbfG'\x1b^\xca\xb2\x0d7\xc8\xd79K&\x00\xae\ +\x00\xd4\xd6\x97\xba\x01P\xca\xf6\x95\x83O\x02\xc8\x04 \ +\xf3}\x14\xa4\xb2\x96\xf7\xff%\x13\x80\x1a\xac\x008\xbf\ +\x98\x00\xa8\x866\x00\x9a\x06I\xe1M\x00\xa4L\x0bx\ +\x0c7\x05\xd6\xb7\x07L\x00T\xc3c8\xd4\x17o\xd9\ +\xbc\x81\xad/e\xd8fk\x96\xe5\xf2\xdc%\x13\x00\x13\ +\x80\x9a\xa4\xefM`\xed\x8b\x9b\xda\xfaR\x86\xb5\x1b\xda\ +\x8c\xa6ML\x00d\x02\x90UAv\xd3z\xff_*\ +\x0e\xbd\xc2\x15\x04\xf2I\x00\x13\x00e1\x8b6\x01\x90\ +\x8a\xe4\x04\x12\xeei\x1eW\x00L\x00T\x03\x0d\x80\x0d\ +\x83$\x00\x96\x00\x96\x8aB\xc0c\xb9G\xe1\x1c&\x13\ +\x00\xad\x86\xee@y\xce\xae\x1a$\x15\xc7\xb1\x5c\x0ex\ +&1\x01\xd0\xea\x1e\xbb\xa1\xbe\xf8\x87O6\xb7\xf5\xa5\ +\x22\x10\xf8X\xf66\x80\x09\x80VS\x90M4\xadZ\ +@\x83\x11\x0e\x0d\xa9\x184\x18QJ\xebp\xaf\xf4p\ +#\xa0\x09\x80\xb2\x94=\xbb\xfc/\x15\xd9\x95\x84%\x81\ +e\x02`\x02\xb0R_\xea\x13\x00Rq%\x00\xe16\ +\x02\x9a\x00\x98\x00h54\x06\x82\x1c\xb6>\x02(\xb9\ +\x02PK\xba\x02M\xec\x01\x13\x00\xad\x9a\x9e@\x90:\ +\x9e\xde\x02\x90\x8aK\xc0c\xba\x8c@\x8f2\xeb\xfb\x95\ +\xd8\x04\xd1:\x1c\xb8#\xc4\x17/\xeaXa\xebKE\ +\xa6lb\x12\xf2\x5cv\x97=\xe0\x0a\x80V^\x90\xdd\ +\xb3\xeb\xaee\xc3K\xc5\xa8\xed\x9a\xf9:\x97\xc9\x04 \ +\xcb,\x01,\xa9\xf6N(\x96\x04\x96\x09\x80\x09\xc0\x0a\ +\xbf\xd4\x04@*\xce\x13\x8a\x8f\x02\xca\x04 \x13\x9a\x03\ +\x1d]\x01\x90T\x04\xc7\xf6\xfa@\x0b{\xc0\x04@+\ +\x9f1\x07\xd9\xa0\xe9\x0a\x80\xe4\x0a@-+\x01z\xd9\ +\x03&\x00Z\xf9\x04\xa0\xfe\x8f\xd2\x12\xd8\xf2\x02\x9f\x00\ +\x90\x8a\xd1\x96\x17TP\x12\xee\xb9/o\x03\x98\x00h\ +%\x05\xd95\xbb~;\x1b^*f\x01\x8fq\x9f\x04\ +0\x01P\xcc\x07\x8b\xcb\xffRqs#\xa0L\x002\ +p\x9c\x06\xf9R+\x00J\xc5}ea\x02 \x13\x80\ +\xa8\xad\x09\xb4\xcd\xd9\xc9ARq\x1f\xe3m\x01\xcb\x8c\ +\x99\x00(\xd6L\xd9[\x00R\x91\x9f\x5c\xc2\x1e\xe3>\ +\x09`\x02\xa0\x18\x13\x80\xb2R\xd8\xf8,\x9f\x00\x90\x8a\ +Y\xdf\xb3+hP\x16\xec\xeb\xbd\x0d`\x02\xa0\xef\x11\ +d\x03`\xd7\x8e6\xbc\x94\x07]:\xe4\xeb\xdc&\x13\ +\x00W\x00\xbe\xefK\xdd\x00(\xe5\xe3\x04\xe3;\x01d\ +\x02\x10\xad \xf7\xc9L\x00$\x13\x00\x13\x00\x13\x00\x85\ +\xb3\x1e\xd0:\xc4\x17\xf7\xeeb\xe3Ky\x10\xf0Xo\ +\x05\xb4\xb7\x07L\x00\x14Y\x86\xec\x0a\x80\xe4\x0a\x80\xab\ +\x00&\x00\x0a\x98\x9c\x87\xf8\xd2\xf2F\xb0\xe1)>\x01\ + \xe5\xc1\x86\xa7TP\xde\xc8\x04@&\x00&\x00@\ +\x8fN6\xbc\x94'\x01\x8fy\x9f\x040\x01PL\xd9\ +\xb1\xcb\xffR\xceN4n\x04\x94\x09@t}\x11\xe4\ +\x09\x007\x00J\xf9\x12\xf0\x98\xef\xed\xbcc\x02\xa0e\ +u\x02\x9a\xba\x02 \xa9\xceg\xe1p%\x81\xd7\x006\ +\xb0\x07L\x00\xb4\xd4<\x1c\xea\x8b\xb7h\xd6\xc0\xd6\x97\ +rd\xcb\xe6A\x8fyo\x03\x98\x00(\x86\x83\xa2i\ +\x13X\xfb\xe2\xa6\xb6\xbe\x94#k_\xdc\x94\xe6\xe1\x0e\ +{\x13\x00\x13\x00-%\xc8\xeeX_\x01,\xe5S\xcf\ +\xce\xf9:\xd7\xc9\x04\xc0\x15\x80\xa5\xbf\xd4\x04@\xca\xe7\ +\x09'\xdc\xb1\xef\x0a\x80\x09\x80\x96\xd0\x00\xe8\xee\x0a\x80\ +\xa4\x1c\x1c\xfb=\x80\x86\xf6\x80\x09\x80R\xdd\x81\xf2 \ +\xa9\xb8O\x00H\xf9\x5c\x01\x08w\xec7\x02<\xf3\x98\ +\x00\xa8\xfaX\x0c\xf5\xc5\xbb\xdc\xdd\xc2\xd6\x97r(\xf0\ +\xb1\xefm\x00\x13\x00\x15\x04\xd9\x14\xd3\xaa\x05\x94\x8c+\ +\xb1\xf5\xa5\x1c*\x19WB\x9b\x96\xf9:\xe7\xc9\x04\xc0\ +\x15\x80\x82>\xddmx)\xd7W\x1en\x044\x01P\ +>\x13\x00\x9f\x00\x90r~\xe21\x010\x01PP\x8d\ +\x81 \x95\xb9}\x02@r\x05 \x90.@\x13{\xc0\ +\x04 \xefz\x01e&\x00\x92r\xb4\x02PF\xfa8\ +\xa0\x02r\x07XxG\x00\x7f\x0e\xf1\xc5\x8b:V\xd8\ +\xfaR\xce\x95MLB\x9e\xfb\xee\xb4\x07\xc2\xf1-0\ +\xe1\x05\xd9\x0d\xbb\xeeZ6|\xde\x8c\xb8<\xe1\x91\xe7\ +\xe0\xad\x0fa\xec'\x90\xccJ\xffyEs\xe8\xb6>\ +l\xda\x03~\xb4\x03l5\xcc\xc40O\xda\xad\x0dS\ +\xa6\x06\xf9j\xf7\x01\xb8\x02\x90{\x0f\x01{\xd6\xf7\x97\ +\xf6\xdf\x1a\x1e\x9b\xec\x89>\x0fn>,\xe1\x86;`\ +\xc2\xe4\x95\xfb\xf7;\xb5\x873\x8e\x80S\xfe\xe2\xf8\xc8\ +\x83\xdd\xdb'<\xfer\x90\xaf~$\xc4\xb9O\xdfp\ +\x0f@x}\x82|\xa9u\xb8\x8a\xdemG%t\x98\ +\x97p\xfa\x15+?\xf9\x03\x8c\x9f\x0c\xa7_\x01mg\ +%\x5c30\xb1!\x8b\x5c\xc0\x8a\x80\xd6\x020\x01\xc8\ +\xb5\xe6@\x87 G\x9e\x1b\x00\x8b\xd6}\xa7'lH\ +\xc2\x09\x97\xd4li\xf7\x8b\xe90\xf4\x06\xe8\xb4 \xe1\ +\xb6\xa3L\x04\x8aU\xc0sAG\xc0e&\x13\x80\xfc\ +&\xdf\x04\xba\x0dc\x0d\x80\xe2\xf3\xd4\xf0\x84\xcd\xd6H\ +\x18t6|4\xb1\xf6\xfe\xde\x89\x9f\xc1\x09\x97\xc0F\ +\x0d\x12\xee;\xddD\xa0\xe8NB\xe1\xce\x05%\xa4O\ +A)`\x07(\x9c\xe3\x80[\xeb\xbd\xd3K`a\x07\ +\x13\xefb\xf1\xf2/\x12\x86\xdd\x08\xcf\xbd^?\xdf\xb7\ +e\x1f\xf8\xc5i\xb0\xf3e\x8e\xa1b\xd1`RBU\ +U\x90\xaf>\x1e\xf8\xbd=\xe0\x0a@\x1e\x05\xb9\x07\xb6\ +A;\x1b\xbe\x18\xbcqu\xc2\xbe\x9d\x12\xb6;\xbc\xfe\ +&\x7f\x80WG\xc2\x80\xe3`\x876\x09/^\xe6\x8a\ +@1\x08xNp\x1f\x80\x09@n\x05y\x0c\xc6\xfb\ +\xff\xd96\xfa\xe6\x84Cz&\xf4\xfb1<\xf4l\xb8\ +8^|\x0bv\x18\x0c\xbb\xb5Ox\xf7z\x13\x81L\ +\x9f\x88\xc2m\x04\xf4Q@\x13\x00\x13\x80\x9c\x1c\xec\xaa\ +\x81\xb9\x83\x170\xa4_B\x9f\xfd\xe0\xde\xc7\x08\xb5d\ +\xbb\x8c'^\x86\xcd\x06\xc1!=\x13&]?\xcb\x8e\ +2\x010\x010\x01\xd0\xf7X\x13X\xc7\x15\x00\xad\x8c\ +a\x03\x12\xd6\xbaz.\xb7\xde\x0b\x8b\x16\xc7\x17\xdf\xe2\ +\xc5iR\xd2\xed\x90\xc5\x0c\xe9\x97P\xd5\xb9\xcaN\xcb\ +\x90\xde]\x82}\xf5:\x80e\xc9L\x00r\xa7O\xa8\ +/\xf6\x09\x80\xec\xb8f`B\xcb\xaf\x12\xae\xba\x1d\xe6\ +U\xc6\x1f\xef\x82\x85p\xeb\xbd\xd0b\xc4L\xce\xdc\xde\ +\xdb\x02\xae\x00\xac\x5c\xfea\x0f\x84\xe1S\x00\xe1\x9c\x0a\ +\xfc\xaa\xbe\xbf\xb4A\x19T\xb6w\xf7v\x16&\xfe\xab\ +n\x87\xe93\xb3\xfd;\x9a7\x85\x93\x0e\x81\xcb\x1fw\ +\xcc\xc5\xae|r\xc2\xc2E\xc1\xce\x857\xdb\x03\xae\x00\ +\xe4I\x90\xac\xb7kG\x1b>f\xb7\x1d\x95\xb0\xde\xd7\ +\x09Co\xc8\xfe\xe4\x0f0k\x0e\x5cu;\xaccU\ +\xc1\xe8\x05<7\xb8\x02`\x02\x90;n\x00\xd4\xff\xdc\ +wzB\xf7\xaa\xb4z\xdfg_\x14\xdf\xef\xfb\xb2P\ +Up\x83\xf9V\x15\x8c\xf6\x84\xe4F@\x13\x00\x15w\ +\xd6\xeb\x06\xc0\xb8<5<\xe1\x07M\xd2\xea}\x1fO\ +*\xfe\xdf;\xe9\xbfiU\xc1\xdeeV\x15\x8c\xee\x84\ +\x14\xee\xdc\xd0\x07oG\x9b\x00\xe4\xc8z@\xcb \xa9\ +\xb6\x09@4\x13\xffV-\x12\x06\x1c\x07\xef\x8c\xce\xdf\ +\xef\xffp<\x0c:\x1b6)7\x11\x88f\x05 \xdc\ +\xb9\xa1\x02ho\x0f\x98\x00\xe4\xe6X\x0b\xf6\xc5\xde\x02\ +\x08\xea\xb5+\x13vm\x97N\xfc\xaf\x8d\xb2=F\x8e\ +M\x13\x81\xed['<\x7f\xa9\x89@\xd0\x93R\xd8s\ +\x83\xb7\x01L\x00L\x00\xeaRy#\xe8~\xb2\xbb\xb1\ +\x83\x5c\xf1\xde\x94V\xef\xdb\xfa0xr\x84\xed\xb1\xb4\ +\x97\xde\x86\x9d\x8eL\xab\x0a\xbes\x9d\x89@\x08\xddO\ +\xae\xa0I\xb9\x09\x80\x09\x80\xeaZ\x90\xfb\xff=:\xd9\ +\xf0\xf5m\xd6\x90J\x86\xf4K\xd8x\xff\xb8\xaa\xf7\xc5\ +\xea\x89\x97a\xf3\x83\xd3\xaa\x82\xe3o\x9ei\x83\xd4\xb3\ +\x0d\xc3\x9d#|\x12\xc0\x04\xc0\x15\x80:\xfdR\x97\xff\ +\xeb\xd5\xb0\x01\x09\xeb\xfct^\xb4\xd5\xfbbU]U\ +\xb0\xe7\x01U\x0c\xe9\x97\xb0p+\x1b\xaf\xde\xce\x11\xe1\ +\xf6\x01\xb8\x02`\x02\x90\x9b6\xef\x19$\xc5\xeeb\xe3\ +\xd7\xd7\xc4\xdf\xec\xf3\xb4\x90O\xe5|\xdbcuUW\ +\x15l\xfe\xcfY\x0c\xe9\xe7m\x81z9G\x84K\x00\ +z9\x1f\x99\x00\xe4Ag\xa0\xa9+\x00\xc5\xe7\x9a\x81\ +\x09m\xa6\xa7\x13\xff\xd7\x95\xb6Gm\x99\xbf M\x04\ +*\xbeL\x186\xc0D\xa0H\xcf\x11k\x14\xce\x8d2\ +\x01(\xeec,\xd4\x17o\xd5\xa0\x91\xad_\x07n;\ +*\xa1\xfd\xdc\xb4z\xdf\x0c_\x86Wgf\xcfM\xab\ +\x0a\xae=\xd3\xaa\x82ue\x9b\x05\xe5!\xbf\xde}\x00\ +&\x00&\x00u\xa1\xd9\x1a\xd0\xe6\xf2&\xb6~-\xba\ +\xef\xf4\x84n\x8b\xd3\xea}\xff\xfd\xd2\xf6\xa8/_\xcd\ +H\xab\x0a\xae_iU\xc1\xda\xd6\xf2\xfa\xc64o\x1a\ +\xec\xeb\xdd\x07`\x02P\xf4\xac\x00\x98q\x0f\x9f\x97\xb0\ +IyZ\xbdo\xdc\xa7\xb6G(\x9f~\x9eV\x15\xec\ +U\x9ap\xd7\x89&\x02\xb5\xa5W\xb8\xbdB\xae\x00\x98\ +\x00\x98\x00\xd4Ijm\x02PcO\x0dO\xd8\xb2Y\ +\xc2>\xa7\xa4\x05l\x14\x87\xd1\x13`\xf00\xe8kU\ +\xc1\xac\x9f+\x5c\x010\x01(j\x0d\x81\x0d]\x01\xc8\ +\x96W\xafH\xe8\xdf6\xad\xde\xf7\xc6\xfb\xb6G\xacF\ +\x15\xaa\x0an\xd7*\xe1\xb9KL\x042x\xae\xe8\x01\ +\xb8Q\xc9\x04\xa0hu\x0f5\xc0}\x02`\xd5}\xf0\ +\xabo\xaa\xf7=\xfd\xaa\xed\x91\x15/\xbf\x03?<*\ +\xad*\xf8\xf6\xb5&\x02\x19:W4\x04\xc3\x06$\xb4\xbec6\xbf\xfaK\ +ZxF\xc5\xa5\xba\xbc\xf0z\xa7\xce\xb7\xaa\xe0\xca\xcc\ +\xc2\xe1\x9e\x04p#\xa0\x09\x80\x09@\xad~\xa9\x1b\x00\ +\xbf\xd35\x03\x13ZO\xb3z_^TW\x15l\xf1\ +\x85U\x05Wx\xce\x08w'\xde\x04\xc0\x04\xa0(5\ +!P\xa9K\x9f\x00X\xd6mG%\xb4\x9b\x93V\xef\ +Kf\xe7\xb2\x09F\x02\x83\x80\x01\xc0ky\xfb\xf1s\ +\xbe\xb6\xaa`\xa4\xe7\x8c.\xa4e\x81e\x02PTz\ +\x01e\xae\x00\x84u\xd7\x89\x09]\x17\xa5\xd5\xfb>\xff\ +*\x97M\xf0aa\xe2\xef\x0b\xdc\x0b<\x01lYH\ +\x04\xde\xc9[cTW\x15\xec8/\xe1\xa6CM\x04\ +\x228g\x94\x92>\x0e\xa8z\xe0S\x00\xf5\xe7\x08\xe0\ +\xcf!\xbe\xd8'\x00\xd2\xb2\xbd\x17\xdd\x04c&\xe4\xb6\ +\x09&\x02\xbf\x00\xfe\x00,\x5c\xc1\xc9w pE\xe1\ +J,w6h\x0f\x17\x1c\x0b\xc7\xfe\xd1c&\xe0\x93\ +\x00\x83\x81;\x9c2\x5c\x01(&Av\xb7\xb6[;\ +\xdf\x8d\xfe\xd4\xf0\x84-\x9a\xa5e{s:\xf9\x7f\x09\ +\x0c%-@u\xeb\x0a&\x7f\x80\xc5\x85U\x81\x9e\xc0\ +\x09\xc0gyk\xac\x09\x93\xd3\xf2\xc2\x1b7\xb2\xaa`\ +\xc0s\x87O\x02\x98\x00\x14\x1d7\x00\xd6\xa3W\xaeH\ +\xd8e\x9d\xb4z\xdf\x9b\xf9\xac\xde7\x0b\xb8\xaap%\ +\x7f\x150o\x15\xfe\xec\x82B\xb2\xd0\xb5\x90\xef\x9dC\xd5W\xed\xdd\xea\xe0\xaa\xbd&\xab\x09\ +E\xe1\x89\x97a\xcbC`\xdfN\x09\xa3~\x99\x8fD\ + \xe0\xb9\xa3\x03\xd0\xd2i\xc3\x04\xa0\x98\xae\xfe\x83l\ +\xb8\xec]\xe4[\xb9\xe6\x1d\xb4\x90!\xfd\x126\xda\x17\ +\xee\xfcWZ\xf0%g\xaa\xef\xdb\xf7\x22\xbdo?\xa5\ +\x0e\xbf\xeb\x0b\xbe\xbd\x9f`Q\x9e\x1a\xba\xaa\x0a\x1ez\ +\x166=0\xad*8\xf5\xd29\xc5}\xd2\x0a\x97\x00\ +\x94\x14\xc6\xb3\xea\xa1\xa1U\xf7\x8e\x07~W\xef\x9d[\ +\x02\x0b;\x14\xefn\xe6a\x03\x12n\xbc\x0b\xe6\xe5\xb7\ +\x80\xcf\x13\xc09\x84{|\xaf'p\x09p`\x1e\xcf\ +%\x8d\x1a\xc2\x91\xfb\xc1o_)\xdec\xac\xe1\xa7I\ +\xa8\xa4\xfa\x84B\x92)W\x002/\xc8\xae\xd6\x0d\xda\ +\x15gc^30\xa1U\xa1z_N'\xff\x17\x81\ +\x1d\x08\xff\xec\xfe\x07\xa45\x05\xfa\x01\x0f\xe5\xad\x13\xf2\ +PU0\xe09\xc4'\x01L\x00\x8a\x86\x1b\x00k\xc1\ +mG%\xac;;\xad\xde73\x9f\xd5\xfb^\x05\xfa\ +\x03\xdb\x01\xcfG\x14\xd7k\xc0\xde\x85\xb8\x9e\xcb[\xa7\ +TW\x15\x5c+)\xbe\xaa\x82n\x044\x01\x90\x09@\ +\xf0\x89\xbf\xc3\xbc\xb4z\xdf\xd4i\xb9\x1c?\xd5W\xda\ +[\x01OF\xbe2\xb1cae\xe2\xed\xbcu\xd2\xb4\ +$\xad*\xd8vV\xf1$\x02\x01\xf7\x01l\xec\xb4a\ +\x02P\x0c\xd6\x04\x82\x94\xd4\xc8\xfa\x06\xc0\xfbNO\xe8\ +Q\x92N\xfcS\xa6\xe6r\xecL$\xbd\x17\xda\x87t\ +\xa3_UF\xe2~\x02\xd8\xac\x90\xb4|\x94\xb7N\xfb\ +bz\x9a\x08t^\x98p\xdbQ\xd9N\x04\x02>\x09\ +\x10\xec\xbci\x02\xa0\xda\xd4'X\xf6\x9e\xd1\x15\x80\xa7\ +\x86'l\xde4\xad\xde7\xf6\x93\x5c\x8e\x99\xea\xdd\xf6\ +\xdd\xc9\xeen\xfb\xfa|:!J\x9fLI\xab\x0a\xf6\ +i\x98\xdd\xaa\x82\x81\xcf!\xee\x03\xa8c>\x05P\xf7\ +N\x05~U\xdf_\xda\xa0\x0c*\xdbgkw\xf2\xcb\ +\xbfH\x18v#<\xf7zn\xc7\xca4\xe0&\xe0:\ +\x8a\xaf\xe8\xce\x1a\xc0q\xc0\xb0\xbc^\xd9\xf5\xdb\x18~\ +q\x1a\xfc\xf0\xd2l\x1d\x97\x8d\xa7$,X\x18\xe4\xab\ +O+\x1c\x0fr\x05 \xb3\x82d\xb1\xdd\xd6\xcfN\x03\ +\xbdqu\xc2\xbe\x9d\x12\xb6;<\xb7\x93\xff\x1c\xbe)\ +\xb4\xf33\x8a\xb3\xe2\xde\x5c\xe0\xc6\xc2o\xaci\x85\xc2\ +Lz\xe5]\xe8\x7f,\xec\xd0&\xe1\xa5\x9fggE\ +\xa0kGW\x00L\x00\xb4\xba\xdc\x00\xf8\x1d\xc6\xfc:\ +-\xdb\xdb\xef\xc7i\x81\x95\x1c\x9a\xcf\xb7\xab\xf7\xcd\xc8\ +\xc1o\x9eM\xce\xab\x0a\xbe\xf8\x16l\x7fDZ^x\ +\xe4\x0d\xf1'\x02>\x09`\x02\xa0\x8ce\xb11o\x00\ +\x9c;x\x01C\xfa%\xf4\xd9/-\xdb[U\x95\xbb\ +1\x91\xfb\xb7\xee\xf1\xcd[\x0a\xab\xf79,\xcc[\x03\ +<\xf12\xfc\xe0\xa0\xb4\xaa\xe0\x94+\xe3}\xae5\xe0\ +\xb9$X\x05U\x13\x00\xd5\x86`5\xadc}\x07\xc0\ +\xb0\x01\x09k]=\x97[\xef\x85\x85\x8br9&\x9e\ +\x006%\xdd!?\xceC\x84Id\xf3I\x87\xda\xc9\ +\x04\x17\xa7Ip\xe7#\x161\xa4_\x9c\xab\x01\x01W\ +\x00*\x80\xf5\x9e\x00\xb6 }F\xfe]\x0f\x8de|XH\ +\x8a\xfa\x16\x12\x81\x5cY\xb00\xad*\xd8|j|U\ +\x05\x03\x9fK\xbc\x0d`\x02`\x02\xb0*\xca\x1bA\xf7\ +\x93\xe3\xd8i|\xcd\xc0\x845g\xe4\xbaz\xdf\x08`\ +\x97\xc2\xc4\xff\xba\x87\xc4\xf7\x1aYH\x04\xb6\x05r\xb7\ +3d\xee\xbc\xb4\xaa`\xcb\xaf\xe2I\x04\xba\x9dTA\ +\x93r\x13\x00\x13\x00\xad\xaa \xf7\xff{v\x0e\xff\xc3\ +o;*a\xbd\xaf\xd3\x89\x7f\xfa\xcc\x5c\xf6\xfd{\x85\ +\x89l\x1b\xe0)\x0f\x85U\xf6\x12\xb0S!qz3\ +o?~\xd6\x9c4\x11X'\x92\xaa\x82=\xc2\x9dS\ +|\x12\xc0\x04\xc0\x15\x80U\xfa\xd2\x80\xf7\xff\xef;=\ +\xa1{UZ\xbd\xef\xb3/r\xd9\xe7\x9f\x90\xde\xd3\xae\ +^\xca\xae\xf20\xa8\x91'\x80\xcd\x0b\xc9\xd4\xd8\xbc\xfd\ +\xf8/\x0bU\x05;-\x08[U0\xe09\xc5\x15\x00\ +\x13\x80\xcc\xb6m\x8f )s\x80\x83\xf5\xa9\xe1\x09?\ +h\x92V\xef\xfbxR.\xfb{2p\x06\xb0!\xd9\ +\xad\xde\x17\xab\xaaB2\xd5\x1b\x18\x0cL\xc8[\x03L\ +\xfc,\xad*\xb8Q\x830U\x05\x03n*\xee\x05\x94\ +y\x08\x98\x00dM'\xa0i\x90\x94\xb9\x1e7\xed<\ +5\xcb\x7f#P\xe9\xd0\xaf3\x0b\x80;\x0a\x93\ +\xc2\xb9\x85\xb6\xcf\x95\x0f\xc6\xc1\xa0\xb3a\xab\x16\x09O\ +\x0d\xaf\xbfD\xa0O\xb8\x8d\x80M\x0a\xe7R\x99\x00d\ +J\x8fP_\xbc\xcd\x82\xba\xdf\xb1\xf3\xfaU\x09\xbb\xb6\ +K'\xfeWG\xe6\xb2\x7f\xab\xab\xf7u\x05\xae\x05\xbe\ +v\xc8\xd7\x9b\xaf\x0bm\xbe\x01i-\x81$o\x0d\xf0\ +\xda(\x18p\x5cZU\xf0\x85K\xeb\xfe\xe7o\x9b4\ +\x0e\xf9s\xbb;\xe4M\x00\xb2&\xc8\xa0m\xb6\x06\xb4\ +\xbc\xbe\xee\x0e\xd6\x0foJ\xab\xf7mu(<9\x22\ +\x97\xfdZ]\xbd\xaf+\xf9\xa9\xde\x17\xab\xa5\xab\x0a\xe6\ +.\x09{\xf1-\xd8\xf1\xc8\xb4\xaa\xe0\xbb\xd7\xd7]\x22\ +\xd0\xfc\xb7\xe5\xb4h\x16\xecgvs\xa8\x9b\x00dM\ +\x90\xbbfuu\xafn\xea\xa5s\x18\xbcqB\x9f\xfd\ +s[\xbdo!p[\xa1_O\x00\xfe\xeb\x10\x8f\xc6\ +W\x85dl\xc3B\x1f\xe5\xb2\xaa\xe0f\x83`\xf0\xc6\ +\x09S/\x9dS7\xe7\x96p\x15\x01M\x00L\x002\ +'\xc8+4\xeab\xb7\xee\xb0\x01\x09\x1b\x1c\xbf\x90\xbb\ +\x1eJ+\x97\xe5\xcc\x92\x1b\xd0\x8e#\xad\x5c\xa78M\ +*\xf4Q7\xd2U\x9a\x5c\x8d\xd6\xc5\x8b\xe1\xae\x87\xa0\ +\xe3\xb1\x0b\x19\xd2/\xa1\xaas\xedf\xe9\x017\x02Z\ +\x0d\xd0\x04 s\xd6\xc9\xfa\x0a\xc0\x05\xfd\x13\x9aOM\ +\xab\xf7U\xce\xcfe\x1f>\x02lF\xfa\x08\xda\x18\x87\ +tfL ]\xa5\xd9\xbc\xd0\x87\xb9R]U\xb0\xc5\ +\x88\x99\x5c\xd0\xbf\xf6n\x0b\x04L\x00\xd6uH\x9b\x00\ +dM\xdb\x10_\xda\xb9\x16r\xe5\xcb\xf7Mh=-\ +\xe1\xea?\xa4\x95\xc9r\xe8\x05`{`O\xe0-\x87\ +rf\xbdU\xe8\xc3\xed\x0b}\x9a+s\xe7\xc1\xd5\x7f\ +\x80\xd6\xd3\x12.\xdf\xb7\xe6\x89@\x97\x0e\xf9:\x97\x9a\ +\x00\xa8&\x82\xbc\x04\xa8S\x0d\x12\x80\xdb\x8eJh?\ +7a\xf8M\x90\xe4\xb3l\xef\xa8\xc2\xd5~.'\x8c\ +\x1c$t\x03\x807\xf2\xf6\xe3\x93\xd90\xfc\xa6\x9aW\ +\x15\x5c\xbf]\xb0\x9fP\xe1\x10\xae\x1b\xbej\xb1\xee\xda\ +u\x01\x01\x0aX,\xea\xb8\xea\xc7\xca\x9f\x8fK\xf8\xd9\ +o\xd2b#95\x06\xb8\x18\xb8\x07+\xf7\xe5\xe1\xd8\ +\x1c\x04\x5cJN\x1f/\xeb\xb8.\xfc\xec$\x18\xfc\xfb\ +U?W\x94M\x0c\xf2\xc4\xe5\x02\xa0\x91C\xd7\x15\x80\ +\xac('\x03\xd5\xab\xfeqFB\xef\xb2\x84\xa3\x87\xe7\ +v\xf2\xaf\xde4\xd6\x1b\xf8\x9b\x93\x7f.T\x15\xfa:\ +\xb7\x9b:'~\x06G\x0f\x87\xdee\x09\xff8#\x13\ +%\x14\x1a\x16>2\x01\xc8\xccI\xa6\xfe/mVr\ +=\xe7\xa9\xe1\x09\xfd\x9a'\x1ct\x16|8>\x97\xfd\ +\x93\xfb\xc7\xc6\xe4c\x9d\x1f\x8e\x87\x83\xce\x82\xbe\xe5+\ +_^\xb8\xd4\x19\xa3\xa8x\x0b\xa0n4 ]\xb6\xaa\ +\xf7\x04`a\x87\xef^\xd6{\xfc\xc2\x84\xcb\x7f\x0f\xcf\ +\xe5\xf7\xa5\xb43\x81\xeb\x80\x1b\x80Y\x0eS-\xa19\ +p&p6\xd0\x22\x8f\x0d\xb0\xc3\xe6p\xe1\xf1\xd0\xff\ +\xe7\xdf}\x0ei0)\x09U\x03\xa4\x01\xbe_C\x19\ +J\xac\xe6\x17V\x02\xea\xf53k\xf8\x1aU\x8b:V\ +|\xebs\xd7\x95Tm\xd2\xa3\xfec\x89\xe83\x17\xb8\ +\x06h\xe3\xd0\xd4\xf7hS\x18+s\xf3z\xbcl\xd2\ +\x83\xaa\xbf\x5c\xc52\xe7\x91Y\xc3\xd7\x08\x15\xd3|/\ +V]\x01\xc8\x9aI\x04(`q\xe7\x95\xe9\x8b;\xde\ +|\x1f\x1e\x7f\x19\xfe\xf5\x0c\xcc\x9e\x9b\xdb>X\x00\xfc\ +\x01\xb8\x8c\xf4m}\xd2\xcaj\x0f\x0c\x07\x8e&\xa7\xf7\ +\x9f\x9b\xad\x01{\xef\x04\x03\xb6\x86\x1f\xf4\x82Qc\xe1\ +'C\x83\x84\xf2)\xd0\xc1!\xa9,y=\xc7W\xdc\ +\xa1?\x8bIw\xf4[BT5\xb5>\xf0;\xd2=\ +\x03\x1e[a>\xaf:\x0c\xeb\x86[:\xea\x8e\xb5\xe2\ +\xc3x\x82o\xaa\xf7\x8d\xb59TC\x9f\x90n\x12\xdc\ +\x98\xb4$\xb4O\x8ax.5\x01\xd0\xf7\xfa\xdc&\xa8\ +W/\xecy\xed\x9a\x00\x00\x03rIDAT\x01;\ +\x91\x16{\xb1z\x9fj\xdb\xfb\x85\xa4rk\xe0)\x9b\ +\xc3s\xa9\x09\x80Vd\x9cMP/\xde\x00v\x07\xb6\ +\x05\x9e\xb59T\xc7^\x01v)\x8c\xb97l\x8ez\ +1\xde&P\xd6\xec\x8a\xf7\xee\xea\xf2\xf3\x01p\x10n\ +dUX\xfd\x81w<\x1e\xeb\xf4\xd3\xdfa\xa6\xaci\ +I\xba\x19\xcd\x03\xb8v?\x13\x81\xe3I\x9f\x0b\x96b\ +PZHF?\xf6\xf8\xac\x93\x0d\xbd-\x1db\xca\xa2\ +1\x1e\xc0\xb5\xf6\xf9\x028\x1fh\xec\xb0R\xa4\x1a\x15\ +\x92\xd3)\x1e\xaf\xb5\xf6\x19\xed\xb0RV\xfd\xd9\x03\xb8\ +\xc6\x9f\xe9\xc0\x85@3\x87\x932\xa2Ya\xccN\xf7\ +\xf8\xad\xf1\xe7O\x0e'e\xd5~\x1e\xc0\xab\xfd\x99\x03\ +\xdc\x08\xac\xed0RF\xb5\x06~FZ\x82\xdacz\ +\xf5>\xfb8\x8c\x94U\xe5@\xe2A\xbc\xcae?\x7f\ +\x07\xb4s\xf8\xa8H\xac\x05\x5c\x09\xcc\xf3\xf8^\xa5O\ +\x82\xb7\xfc\x94qwy \xaf\xd4g\x11i\xf5\xbe\xae\ +\x0e\x19\x15\xa9\x8eXUpU>w8d\x94u\xbb\ +{ \x7f\xef\xe7~`#\x87\x8arb\xa3\xc2\x98\xf7\ +\xd8_\xf1gW\x87\x8a\x8a\xc1\x9b\x1e\xcc\xcb\xfd\xbc\x00\ +\xec\xe0\xf0PNmIZ\xba\xdas\xc1\xb2\x9f\xb7\xb1\ +\xc6\x87\x8a\xc4a\x1e\xd0\xcb\xbc\xdcco\x87\x85\x04\xc0\ +v\xc0s\x9e\x17\xbe\xf59\xc4a\xa1b\xd1\x00\xf8\xc8\ +\x83\x9aQ\xc0\xfef\xf6\xd22J\x0a\xc7\xc6(\xcf\x13\ +\x8c\xc5B_*2y\xde\x0b\xf0\x09i\x81\x942\x87\ +\x81\xb4B\xd5U\x05\xc7\xe6\xf8|\xb1\x97\xc3@\xc5\xe8\ +\x9f9;\x90\xa7b\xf5>iu4,$\xcd\x93s\ +v\xce\xb8\xcf\xaeW\xb1\xea\x08L\xcb\xc1A<\x0d\x18\ +\x0a4\xb5\xcb\xa5\x1aiZ8\x96\xf2r\xde\xe8`\x97\ +\xab\x98\xedE\xf1\xbe$h\x0ei\xc1\x93Vv\xb3T\ +\xab\x9a\x93\xae\xa6\x15ka\xb1\xc5\xa4\x95S\xa5\xa2w\ +U\x91\x1d\xbc\x95\xc0\xaf\x80\xb6v\xadT\xa7\xda\x16\x8e\ +\xb5\xca\x22;\x87\x5cm\xd7*/J\x80\xdb\x8b\xe0\xa0\ +\xad\xae\xde\xd7\xc5.\x95\xeaU\x07\xd2\xaa\x82\x0b\x8a\xe0\ +\xaa\ +\x00\x00\x01\x80A\xd4\xb2`\ +\x00\x00\x01<\x00\x00\x00\x00\x00\x01\x00\x01\xae\xb0\ +\x00\x00\x01\x8bRz\xee0\ +\x00\x00\x01\xa2\x00\x00\x00\x00\x00\x01\x00\x02;O\ +\x00\x00\x01x\xe8\xe8Z\xc8\ +\x00\x00\x00\xb4\x00\x00\x00\x00\x00\x01\x00\x01\x13}\ +\x00\x00\x01xr\xe6\xdd\xe0\ +\x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\xb9\xed\ \x00\x00\x01x\xbb\xe5\x06 \ -\x00\x00\x00\xbe\x00\x00\x00\x00\x00\x01\x00\x01\x0b_\ +\x00\x00\x00\xda\x00\x00\x00\x00\x00\x01\x00\x01_D\ \x00\x00\x01y/9=X\ " diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui index 41ee1ac..53b2b13 100644 --- a/app/ui/main_window.ui +++ b/app/ui/main_window.ui @@ -1143,7 +1143,7 @@ padding-left: 8px; - Open &playlist... + O&pen... diff --git a/app/ui/main_window_playlist.ui b/app/ui/main_window_playlist.ui index 6bcee88..4b8fb1a 100644 --- a/app/ui/main_window_playlist.ui +++ b/app/ui/main_window_playlist.ui @@ -7,7 +7,7 @@ 0 0 1249 - 499 + 538 diff --git a/app/ui/main_window_ui.py b/app/ui/main_window_ui.py index b711264..064376f 100644 --- a/app/ui/main_window_ui.py +++ b/app/ui/main_window_ui.py @@ -764,7 +764,7 @@ class Ui_MainWindow(object): ) self.actionE_xit.setText(_translate("MainWindow", "E&xit")) self.actionTest.setText(_translate("MainWindow", "&Test")) - self.actionOpenPlaylist.setText(_translate("MainWindow", "Open &playlist...")) + self.actionOpenPlaylist.setText(_translate("MainWindow", "O&pen...")) self.actionNewPlaylist.setText(_translate("MainWindow", "&New...")) self.actionTestFunction.setText(_translate("MainWindow", "&Test function")) self.actionSkipToFade.setText( @@ -847,4 +847,4 @@ class Ui_MainWindow(object): from infotabs import InfoTabs -from pyqtgraph import PlotWidget +from pyqtgraph import PlotWidget # type: ignore diff --git a/migrations/env.py b/migrations/env.py deleted file mode 120000 index 200c230..0000000 --- a/migrations/env.py +++ /dev/null @@ -1 +0,0 @@ -env.py.DEBUG \ No newline at end of file diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..027fd36 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,27 @@ +from importlib import import_module +from alembic import context +from alchemical.alembic.env import run_migrations + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# import the application's Alchemical instance +try: + import_mod, db_name = config.get_main_option('alchemical_db', '').split( + ':') + db = getattr(import_module(import_mod), db_name) +except (ModuleNotFoundError, AttributeError): + raise ValueError( + 'Could not import the Alchemical database instance. ' + 'Ensure that the alchemical_db setting in alembic.ini is correct.' + ) + +# run the migration engine +# The dictionary provided as second argument includes options to pass to the +# Alembic context. For details on what other options are available, see +# https://alembic.sqlalchemy.org/en/latest/autogenerate.html +run_migrations(db, { + 'render_as_batch': True, + 'compare_type': True, +}) diff --git a/migrations/env.py.DEBUG b/migrations/env.py.DEBUG deleted file mode 100644 index c6420ae..0000000 --- a/migrations/env.py.DEBUG +++ /dev/null @@ -1,28 +0,0 @@ -from importlib import import_module -from alembic import context -from alchemical.alembic.env import run_migrations - -# Load Alembic configuration -config = context.config - -try: - # Import the Alchemical database instance as specified in alembic.ini - import_mod, db_name = config.get_main_option('alchemical_db', '').split(':') - db = getattr(import_module(import_mod), db_name) - print(f"Successfully loaded Alchemical database instance: {db}") - - # Use the metadata associated with the Alchemical instance - metadata = db.Model.metadata - print(f"Metadata tables detected: {metadata.tables.keys()}") # Debug output -except (ModuleNotFoundError, AttributeError) as e: - raise ValueError( - 'Could not import the Alchemical database instance or access metadata. ' - 'Ensure that the alchemical_db setting in alembic.ini is correct and ' - 'that the Alchemical instance is correctly configured.' - ) from e - -# Run migrations with metadata -run_migrations(db, { - 'render_as_batch': True, - 'compare_type': True, -}) diff --git a/migrations/env.py.NODEBUG b/migrations/env.py.NODEBUG deleted file mode 100644 index 027fd36..0000000 --- a/migrations/env.py.NODEBUG +++ /dev/null @@ -1,27 +0,0 @@ -from importlib import import_module -from alembic import context -from alchemical.alembic.env import run_migrations - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# import the application's Alchemical instance -try: - import_mod, db_name = config.get_main_option('alchemical_db', '').split( - ':') - db = getattr(import_module(import_mod), db_name) -except (ModuleNotFoundError, AttributeError): - raise ValueError( - 'Could not import the Alchemical database instance. ' - 'Ensure that the alchemical_db setting in alembic.ini is correct.' - ) - -# run the migration engine -# The dictionary provided as second argument includes options to pass to the -# Alembic context. For details on what other options are available, see -# https://alembic.sqlalchemy.org/en/latest/autogenerate.html -run_migrations(db, { - 'render_as_batch': True, - 'compare_type': True, -}) diff --git a/migrations/versions/c76e865ccb85_index_for_notesolours_substring.py b/migrations/versions/04df697e40cd_add_favouirit_to_playlists.py similarity index 50% rename from migrations/versions/c76e865ccb85_index_for_notesolours_substring.py rename to migrations/versions/04df697e40cd_add_favouirit_to_playlists.py index 6850c31..ea4fd70 100644 --- a/migrations/versions/c76e865ccb85_index_for_notesolours_substring.py +++ b/migrations/versions/04df697e40cd_add_favouirit_to_playlists.py @@ -1,16 +1,16 @@ -"""Index for notesolours substring +"""add favouirit to playlists -Revision ID: c76e865ccb85 +Revision ID: 04df697e40cd Revises: 33c04e3c12c8 -Create Date: 2025-02-07 18:21:01.760057 +Create Date: 2025-02-22 20:20:45.030024 """ from alembic import op import sqlalchemy as sa - +from sqlalchemy.dialects import mysql # revision identifiers, used by Alembic. -revision = 'c76e865ccb85' +revision = '04df697e40cd' down_revision = '33c04e3c12c8' branch_labels = None depends_on = None @@ -30,39 +30,29 @@ def downgrade(engine_name: str) -> None: def upgrade_() -> None: # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table('notecolours', schema=None) as batch_op: + batch_op.add_column(sa.Column('strip_substring', sa.Boolean(), nullable=False)) batch_op.create_index(batch_op.f('ix_notecolours_substring'), ['substring'], unique=False) - with op.batch_alter_table('playdates', schema=None) as batch_op: - batch_op.drop_constraint('fk_playdates_track_id_tracks', type_='foreignkey') - batch_op.create_foreign_key(None, 'tracks', ['track_id'], ['id'], ondelete='CASCADE') - with op.batch_alter_table('playlist_rows', schema=None) as batch_op: batch_op.drop_constraint('playlist_rows_ibfk_1', type_='foreignkey') - batch_op.create_foreign_key(None, 'tracks', ['track_id'], ['id'], ondelete='CASCADE') - with op.batch_alter_table('queries', schema=None) as batch_op: - batch_op.drop_constraint('fk_queries_playlist_id_playlists', type_='foreignkey') - batch_op.create_foreign_key(None, 'playlists', ['playlist_id'], ['id'], ondelete='CASCADE') + with op.batch_alter_table('playlists', schema=None) as batch_op: + batch_op.add_column(sa.Column('favourite', sa.Boolean(), nullable=False)) # ### end Alembic commands ### def downgrade_() -> None: # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('queries', schema=None) as batch_op: - batch_op.drop_constraint(None, type_='foreignkey') - batch_op.create_foreign_key('fk_queries_playlist_id_playlists', 'playlists', ['playlist_id'], ['id']) + with op.batch_alter_table('playlists', schema=None) as batch_op: + batch_op.drop_column('favourite') with op.batch_alter_table('playlist_rows', schema=None) as batch_op: - batch_op.drop_constraint(None, type_='foreignkey') batch_op.create_foreign_key('playlist_rows_ibfk_1', 'tracks', ['track_id'], ['id']) - with op.batch_alter_table('playdates', schema=None) as batch_op: - batch_op.drop_constraint(None, type_='foreignkey') - batch_op.create_foreign_key('fk_playdates_track_id_tracks', 'tracks', ['track_id'], ['id']) - with op.batch_alter_table('notecolours', schema=None) as batch_op: batch_op.drop_index(batch_op.f('ix_notecolours_substring')) + batch_op.drop_column('strip_substring') # ### end Alembic commands ### diff --git a/migrations/versions/9c1254a8026d_add_queries_table.py b/migrations/versions/9c1254a8026d_add_queries_table.py deleted file mode 100644 index b48f27a..0000000 --- a/migrations/versions/9c1254a8026d_add_queries_table.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Add queries table - -Revision ID: 9c1254a8026d -Revises: c76e865ccb85 -Create Date: 2025-02-14 16:32:37.064567 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '9c1254a8026d' -down_revision = 'c76e865ccb85' -branch_labels = None -depends_on = None - - -def upgrade(engine_name: str) -> None: - globals()["upgrade_%s" % engine_name]() - - -def downgrade(engine_name: str) -> None: - globals()["downgrade_%s" % engine_name]() - - - - - -def upgrade_() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('queries', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('name', sa.String(length=128), nullable=False), - sa.Column('sql', sa.String(length=2048), nullable=False), - sa.Column('description', sa.String(length=512), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade_() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('queries') - # ### end Alembic commands ### -