feat: Android download page, analysis retry and a collapsible search
ci / api / api (push) Successful in 37s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 46s
ci / db / db (push) Failing after 14s
ci / recommender / recommender (push) Successful in 51s
ci / ts / ts (packages + web) (push) Successful in 57s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m2s
ci / api / api (push) Successful in 37s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 46s
ci / db / db (push) Failing after 14s
ci / recommender / recommender (push) Successful in 51s
ci / ts / ts (packages + web) (push) Successful in 57s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m2s
- /android is a public page with the latest APK, the server address to copy and three install steps; linked from the sign-in screen and the profile menu - failed audio analysis can be retried from the library page, which also shows the most common error; the analyzer no longer burns attempts on files that are missing because the disk is not mounted - the top bar search folds into a round button and slides open on click, "/" or Ctrl+K Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d49cb3f19
commit
fda6ed48ab
27 files changed
+757
-277
No files matched your search
+25
-1
@@ -1006,6 +1006,24 @@ paths:
|
|||||||
$ref: "#/components/schemas/SearchResults"
|
$ref: "#/components/schemas/SearchResults"
|
||||||
default:
|
default:
|
||||||
$ref: "#/components/responses/Problem"
|
$ref: "#/components/responses/Problem"
|
||||||
|
/api/v1/admin/library/analysis/retry:
|
||||||
|
post:
|
||||||
|
operationId: retryAnalysis
|
||||||
|
tags: [library]
|
||||||
|
summary: Вернуть в очередь треки, которые не удалось проанализировать (admin)
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Сколько задач снова в очереди
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [requeued]
|
||||||
|
properties:
|
||||||
|
requeued:
|
||||||
|
type: integer
|
||||||
|
default:
|
||||||
|
$ref: "#/components/responses/Problem"
|
||||||
/api/v1/admin/search/reindex:
|
/api/v1/admin/search/reindex:
|
||||||
post:
|
post:
|
||||||
operationId: reindexSearch
|
operationId: reindexSearch
|
||||||
@@ -1379,6 +1397,7 @@ paths:
|
|||||||
get:
|
get:
|
||||||
operationId: getAndroidLatest
|
operationId: getAndroidLatest
|
||||||
tags: [app]
|
tags: [app]
|
||||||
|
security: [] # публично: страница /android раздаёт приложение без входа
|
||||||
summary: Последняя версия Android-приложения (из релизов Gitea через сервер)
|
summary: Последняя версия Android-приложения (из релизов Gitea через сервер)
|
||||||
description: |
|
description: |
|
||||||
Сервер сам опрашивает Gitea своим токеном и кэширует ответ на 10 минут, поэтому приложению
|
Сервер сам опрашивает Gitea своим токеном и кэширует ответ на 10 минут, поэтому приложению
|
||||||
@@ -1396,6 +1415,7 @@ paths:
|
|||||||
get:
|
get:
|
||||||
operationId: downloadAndroidApk
|
operationId: downloadAndroidApk
|
||||||
tags: [app]
|
tags: [app]
|
||||||
|
security: []
|
||||||
summary: APK версии (кэшируется на сервере, поддерживает Range для докачки)
|
summary: APK версии (кэшируется на сервере, поддерживает Range для докачки)
|
||||||
parameters:
|
parameters:
|
||||||
- name: version
|
- name: version
|
||||||
@@ -3081,7 +3101,7 @@ components:
|
|||||||
description: Рекомендатель недоступен — пачка собрана упрощённо (лайки, любимые исполнители)
|
description: Рекомендатель недоступен — пачка собрана упрощённо (лайки, любимые исполнители)
|
||||||
AnalysisStatus:
|
AnalysisStatus:
|
||||||
type: object
|
type: object
|
||||||
required: [queued, running, done, failed, analyzer_ver]
|
required: [queued, running, done, failed, analyzer_ver, top_error]
|
||||||
properties:
|
properties:
|
||||||
queued:
|
queued:
|
||||||
type: integer
|
type: integer
|
||||||
@@ -3094,6 +3114,10 @@ components:
|
|||||||
analyzer_ver:
|
analyzer_ver:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
top_error:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Самая частая ошибка среди неудавшихся — подсказка, что чинить
|
||||||
WaveMetrics:
|
WaveMetrics:
|
||||||
type: object
|
type: object
|
||||||
required: [served, listen_through_rate, early_skip_rate, like_rate, sessions, by_model]
|
required: [served, listen_through_rate, early_skip_rate, like_rate, sessions, by_model]
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { AlbumPage } from '../pages/AlbumPage';
|
|||||||
import { ArtistPage } from '../pages/ArtistPage';
|
import { ArtistPage } from '../pages/ArtistPage';
|
||||||
import { CollectionPage } from '../pages/CollectionPage';
|
import { CollectionPage } from '../pages/CollectionPage';
|
||||||
import { HomePage } from '../pages/HomePage';
|
import { HomePage } from '../pages/HomePage';
|
||||||
|
import { AppDownloadPage } from '../pages/AppDownloadPage';
|
||||||
import { LoginPage } from '../pages/LoginPage';
|
import { LoginPage } from '../pages/LoginPage';
|
||||||
import { SearchPage, type SearchType } from '../pages/SearchPage';
|
import { SearchPage, type SearchType } from '../pages/SearchPage';
|
||||||
import { SettingsPage } from '../pages/SettingsPage';
|
import { SettingsPage } from '../pages/SettingsPage';
|
||||||
@@ -59,6 +60,9 @@ const rootRoute = createRootRouteWithContext<RouterContext>()({
|
|||||||
errorComponent: RouteError,
|
errorComponent: RouteError,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Публичная: ссылку на приложение отправляют тем, у кого ещё нет аккаунта на этом устройстве.
|
||||||
|
const androidAppRoute = createRoute({ getParentRoute: () => rootRoute, path: '/android', component: AppDownloadPage });
|
||||||
|
|
||||||
const loginRoute = createRoute({
|
const loginRoute = createRoute({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
path: '/login',
|
path: '/login',
|
||||||
@@ -233,6 +237,7 @@ const adminSettingsRoute = createRoute({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const routeTree = rootRoute.addChildren([
|
const routeTree = rootRoute.addChildren([
|
||||||
|
androidAppRoute,
|
||||||
loginRoute,
|
loginRoute,
|
||||||
setupAdminRoute,
|
setupAdminRoute,
|
||||||
setupPasswordRoute,
|
setupPasswordRoute,
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { useSession } from '../app/session';
|
||||||
|
import { formatBytes } from '../shared/lib/format';
|
||||||
|
import { checkIcon, copyIcon, downloadIcon } from '../shared/ui/icons';
|
||||||
|
import { Icon } from '../shared/ui/ui';
|
||||||
|
import { AuthLayout } from './AuthLayout';
|
||||||
|
import s from './auth.module.css';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Публичная страница Android-приложения (/android): ссылку можно просто отправить человеку.
|
||||||
|
* APK — последний релиз, который сервер сам берёт из Gitea (docs/mobile.md#самообновление).
|
||||||
|
*/
|
||||||
|
export function AppDownloadPage() {
|
||||||
|
const { t, i18n } = useTranslation(['auth', 'common']);
|
||||||
|
const session = useSession();
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const latest = useQuery({
|
||||||
|
queryKey: ['android-latest'],
|
||||||
|
queryFn: async () => (await session.client.GET('/api/v1/app/android/latest')).data ?? null,
|
||||||
|
staleTime: 10 * 60_000,
|
||||||
|
});
|
||||||
|
const server = location.origin;
|
||||||
|
const copy = () =>
|
||||||
|
void navigator.clipboard?.writeText(server).then(() => {
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 1500);
|
||||||
|
});
|
||||||
|
const r = latest.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthLayout footer={<a href="/login">{t('app.to_login')}</a>}>
|
||||||
|
<span className={s.badge}>Android</span>
|
||||||
|
<h1>{t('app.title')}</h1>
|
||||||
|
<p className={s.lead}>{t('app.subtitle')}</p>
|
||||||
|
{latest.isPending ? null : r ? (
|
||||||
|
<>
|
||||||
|
<a className={s.download} href={r.download_url} download>
|
||||||
|
<Icon src={downloadIcon} size={22} />
|
||||||
|
<span>
|
||||||
|
{t('app.download')}
|
||||||
|
<span className={s.downloadMeta}>
|
||||||
|
{t('app.version', { version: r.version })} · {formatBytes(r.size, i18n.language)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<ol className={s.steps}>
|
||||||
|
<li>{t('app.step_install')}</li>
|
||||||
|
<li>
|
||||||
|
{t('app.step_server')}
|
||||||
|
<button type="button" className={s.server} onClick={copy} aria-label={t('app.copy_server')}>
|
||||||
|
<code>{server}</code>
|
||||||
|
<Icon src={copied ? checkIcon : copyIcon} size={16} />
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>{t('app.step_updates')}</li>
|
||||||
|
</ol>
|
||||||
|
<details className={s.details}>
|
||||||
|
<summary>{t('app.checksum')}</summary>
|
||||||
|
<code>{r.sha256}</code>
|
||||||
|
</details>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className={s.hint}>{t('app.unavailable')}</div>
|
||||||
|
)}
|
||||||
|
</AuthLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -37,7 +37,15 @@ export function LoginPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthLayout footer={info.data ? `pipu ${info.data.version}` : undefined}>
|
<AuthLayout
|
||||||
|
footer={
|
||||||
|
info.data ? (
|
||||||
|
<>
|
||||||
|
pipu {info.data.version} · <a href="/android">{t('app.link')}</a>
|
||||||
|
</>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
<h1>{t('login.title')}</h1>
|
<h1>{t('login.title')}</h1>
|
||||||
<p className={s.lead}>{t('login.subtitle')}</p>
|
<p className={s.lead}>{t('login.subtitle')}</p>
|
||||||
{info.data?.bootstrap_required && <div className={s.hint}>{t('login.bootstrap_hint')}</div>}
|
{info.data?.bootstrap_required && <div className={s.hint}>{t('login.bootstrap_hint')}</div>}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { unwrap } from '@pipu/core';
|
import { unwrap } from '@pipu/core';
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useId } from 'react';
|
import { useId } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@@ -32,6 +32,11 @@ export function LibraryPage() {
|
|||||||
const reindex = useMutation({
|
const reindex = useMutation({
|
||||||
mutationFn: async () => unwrap(await session.client.POST('/api/v1/admin/search/reindex')),
|
mutationFn: async () => unwrap(await session.client.POST('/api/v1/admin/search/reindex')),
|
||||||
});
|
});
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const retry = useMutation({
|
||||||
|
mutationFn: async () => unwrap(await session.client.POST('/api/v1/admin/library/analysis/retry')),
|
||||||
|
onSuccess: () => void queryClient.invalidateQueries({ queryKey: ['library-status'] }),
|
||||||
|
});
|
||||||
const d = status.data;
|
const d = status.data;
|
||||||
const tiles = d
|
const tiles = d
|
||||||
? ([
|
? ([
|
||||||
@@ -77,7 +82,19 @@ export function LibraryPage() {
|
|||||||
total: d.analysis.done + d.analysis.queued + d.analysis.running + d.analysis.failed,
|
total: d.analysis.done + d.analysis.queued + d.analysis.running + d.analysis.failed,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{d.analysis.failed > 0 && <span className={l.scanError}>{t('library.analysis_failed', { count: d.analysis.failed })}</span>}
|
{d.analysis.failed > 0 && (
|
||||||
|
<div className={l.retryRow}>
|
||||||
|
<span className={l.scanError}>
|
||||||
|
{t('library.analysis_failed', { count: d.analysis.failed })}
|
||||||
|
{d.analysis.top_error && <span className={l.errorDetail}>{d.analysis.top_error}</span>}
|
||||||
|
</span>
|
||||||
|
{admin && (
|
||||||
|
<Button variant="secondary" icon={refreshIcon} busy={retry.isPending} onClick={() => retry.mutate()}>
|
||||||
|
{t('library.analysis_retry')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{d.analysis.analyzer_ver && <span className={l.scanSummary}>{t('library.analyzer', { ver: d.analysis.analyzer_ver })}</span>}
|
{d.analysis.analyzer_ver && <span className={l.scanSummary}>{t('library.analyzer', { ver: d.analysis.analyzer_ver })}</span>}
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -118,6 +118,20 @@
|
|||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.retryRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorDetail {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--text3);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.bar {
|
.bar {
|
||||||
height: 6px;
|
height: 6px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|||||||
@@ -161,6 +161,83 @@
|
|||||||
color: var(--text3);
|
color: var(--text3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: var(--text2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Страница Android-приложения (/android). */
|
||||||
|
.download {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: var(--accent-fill);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: var(--glow);
|
||||||
|
transition:
|
||||||
|
background 0.2s,
|
||||||
|
transform 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download:hover {
|
||||||
|
background: var(--accent-fill2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadMeta {
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--text2);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: 6px;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--line2);
|
||||||
|
background: var(--surface2);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details code {
|
||||||
|
display: block;
|
||||||
|
margin-top: 6px;
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,8 @@ export { default as albumIcon } from '@material-symbols/svg-400/rounded/album.sv
|
|||||||
export { default as arrowDownIcon } from '@material-symbols/svg-400/rounded/arrow_downward.svg';
|
export { default as arrowDownIcon } from '@material-symbols/svg-400/rounded/arrow_downward.svg';
|
||||||
export { default as arrowUpIcon } from '@material-symbols/svg-400/rounded/arrow_upward.svg';
|
export { default as arrowUpIcon } from '@material-symbols/svg-400/rounded/arrow_upward.svg';
|
||||||
export { default as chevronRightIcon } from '@material-symbols/svg-400/rounded/chevron_right.svg';
|
export { default as chevronRightIcon } from '@material-symbols/svg-400/rounded/chevron_right.svg';
|
||||||
|
export { default as downloadIcon } from '@material-symbols/svg-400/rounded/download.svg';
|
||||||
|
export { default as androidIcon } from '@material-symbols/svg-400/rounded/phone_android.svg';
|
||||||
export { default as deleteIcon } from '@material-symbols/svg-400/rounded/delete.svg';
|
export { default as deleteIcon } from '@material-symbols/svg-400/rounded/delete.svg';
|
||||||
export { default as editIcon } from '@material-symbols/svg-400/rounded/edit.svg';
|
export { default as editIcon } from '@material-symbols/svg-400/rounded/edit.svg';
|
||||||
export { default as folderIcon } from '@material-symbols/svg-400/rounded/folder.svg';
|
export { default as folderIcon } from '@material-symbols/svg-400/rounded/folder.svg';
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
dislikeFillIcon,
|
dislikeFillIcon,
|
||||||
dislikeIcon,
|
dislikeIcon,
|
||||||
settingsIcon,
|
settingsIcon,
|
||||||
|
androidIcon,
|
||||||
storageFillIcon,
|
storageFillIcon,
|
||||||
storageIcon, inboxFillIcon, inboxIcon, myUploadsFillIcon, myUploadsIcon, uploadFillIcon, uploadIcon, tuneFillIcon, tuneIcon} from '../shared/ui/icons';
|
storageIcon, inboxFillIcon, inboxIcon, myUploadsFillIcon, myUploadsIcon, uploadFillIcon, uploadIcon, tuneFillIcon, tuneIcon} from '../shared/ui/icons';
|
||||||
import { Avatar, cx, Icon, Logo, Menu } from '../shared/ui/ui';
|
import { Avatar, cx, Icon, Logo, Menu } from '../shared/ui/ui';
|
||||||
@@ -102,8 +103,8 @@ function Shell() {
|
|||||||
<Link to="/" className={s.logoLink}>
|
<Link to="/" className={s.logoLink}>
|
||||||
<Logo />
|
<Logo />
|
||||||
</Link>
|
</Link>
|
||||||
<SearchBox />
|
|
||||||
<div className={s.topRight}>
|
<div className={s.topRight}>
|
||||||
|
<SearchBox />
|
||||||
<Menu
|
<Menu
|
||||||
label={t('profile.menu')}
|
label={t('profile.menu')}
|
||||||
triggerClassName={s.profileButton}
|
triggerClassName={s.profileButton}
|
||||||
@@ -122,6 +123,7 @@ function Shell() {
|
|||||||
}
|
}
|
||||||
items={[
|
items={[
|
||||||
{ label: t('profile.settings'), icon: settingsIcon, onSelect: () => void navigate({ to: '/settings' }) },
|
{ label: t('profile.settings'), icon: settingsIcon, onSelect: () => void navigate({ to: '/settings' }) },
|
||||||
|
{ label: t('profile.android_app'), icon: androidIcon, onSelect: () => void navigate({ to: '/android' }) },
|
||||||
{ label: t('profile.logout'), icon: logoutIcon, danger: true, onSelect: () => void session.logout() },
|
{ label: t('profile.logout'), icon: logoutIcon, danger: true, onSelect: () => void session.logout() },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
.box {
|
.box {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
flex: none;
|
||||||
max-width: 520px;
|
width: 40px;
|
||||||
|
transition: width 0.32s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded {
|
||||||
|
width: min(520px, 46vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 14px;
|
left: 11px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
color: var(--text3);
|
color: var(--text3);
|
||||||
@@ -16,13 +21,36 @@
|
|||||||
.input {
|
.input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding: 0 40px 0 42px;
|
padding: 0 40px 0 40px;
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
background: var(--surface2);
|
background: var(--surface2);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
transition:
|
||||||
|
border-color 0.2s,
|
||||||
|
box-shadow 0.2s,
|
||||||
|
background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Свёрнутое поле — круглая кнопка: без подсказки и курсора ввода. */
|
||||||
|
.box:not(.expanded) .input {
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box:not(.expanded) .input::placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box:not(.expanded) .input:hover {
|
||||||
|
background: var(--surface3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box:not(.expanded) .kbd {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input:focus {
|
.input:focus {
|
||||||
@@ -56,8 +84,8 @@
|
|||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
|
width: min(520px, calc(100vw - 32px));
|
||||||
top: calc(100% + 8px);
|
top: calc(100% + 8px);
|
||||||
z-index: 45;
|
z-index: 45;
|
||||||
max-height: min(70vh, 560px);
|
max-height: min(70vh, 560px);
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ type Item = {
|
|||||||
go: () => void;
|
go: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Поиск в топбаре: подсказки по мере ввода, Enter — страница поиска или выбранный элемент. */
|
/**
|
||||||
|
* Поиск в топбаре: подсказки по мере ввода, Enter — страница поиска или выбранный элемент.
|
||||||
|
* Без фокуса и текста свёрнут в кнопку с лупой; поле раскрывается влево с анимацией.
|
||||||
|
*/
|
||||||
export function SearchBox() {
|
export function SearchBox() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
@@ -34,6 +37,9 @@ export function SearchBox() {
|
|||||||
const [debounced, setDebounced] = useState('');
|
const [debounced, setDebounced] = useState('');
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [active, setActive] = useState(-1);
|
const [active, setActive] = useState(-1);
|
||||||
|
const [focused, setFocused] = useState(false);
|
||||||
|
// Свёрнут в круглую кнопку, пока не нужен: раскрывается по клику, «/» или Ctrl+K.
|
||||||
|
const expanded = focused || q !== '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = setTimeout(() => setDebounced(q.trim()), DEBOUNCE_MS);
|
const id = setTimeout(() => setDebounced(q.trim()), DEBOUNCE_MS);
|
||||||
@@ -141,7 +147,7 @@ export function SearchBox() {
|
|||||||
const optionId = (i: number) => `${listId}-${i}`;
|
const optionId = (i: number) => `${listId}-${i}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={s.box}>
|
<div className={cx(s.box, expanded && s.expanded)}>
|
||||||
<Icon src={searchIcon} size={18} className={s.icon} />
|
<Icon src={searchIcon} size={18} className={s.icon} />
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@@ -160,8 +166,14 @@ export function SearchBox() {
|
|||||||
setOpen(true);
|
setOpen(true);
|
||||||
setActive(-1);
|
setActive(-1);
|
||||||
}}
|
}}
|
||||||
onFocus={() => setOpen(true)}
|
onFocus={() => {
|
||||||
onBlur={() => setTimeout(close, 120)}
|
setFocused(true);
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setFocused(false);
|
||||||
|
setTimeout(close, 120);
|
||||||
|
}}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
/>
|
/>
|
||||||
<kbd className={s.kbd} aria-hidden>
|
<kbd className={s.kbd} aria-hidden>
|
||||||
|
|||||||
@@ -49,6 +49,9 @@
|
|||||||
индекс обновляется сам и пересобирается при старте api).
|
индекс обновляется сам и пересобирается при старте api).
|
||||||
- **Аудиоанализ** (темп, энергия, настроение — для «Моей волны») идёт в фоне: на 10 тыс. треков — несколько часов
|
- **Аудиоанализ** (темп, энергия, настроение — для «Моей волны») идёт в фоне: на 10 тыс. треков — несколько часов
|
||||||
при 4 ядрах (`analyzer` в `compose.yaml`, `--workers`). Волна работает и до его окончания, но точнее — после.
|
при 4 ядрах (`analyzer` в `compose.yaml`, `--workers`). Волна работает и до его окончания, но точнее — после.
|
||||||
|
Не удалось проанализировать часть треков (например, пока диск не был смонтирован) — «Повторить» под счётчиком
|
||||||
|
ошибок возвращает их в очередь; рядом показана самая частая ошибка. Пропавшие файлы анализатор не считает
|
||||||
|
попыткой и ждёт, пока диск вернётся.
|
||||||
|
|
||||||
## 4. Очередь на добавление
|
## 4. Очередь на добавление
|
||||||
|
|
||||||
@@ -91,6 +94,8 @@
|
|||||||
прочитайте заметки к релизу на странице релизов Gitea.
|
прочитайте заметки к релизу на странице релизов Gitea.
|
||||||
|
|
||||||
Android-приложение обновляется само: при запуске оно сверяется с сервером и предлагает новый APK.
|
Android-приложение обновляется само: при запуске оно сверяется с сервером и предлагает новый APK.
|
||||||
|
Раздать приложение — ссылка `https://<ваш-домен>/android` (без входа): кнопка «Скачать APK» последнего релиза,
|
||||||
|
адрес сервера для копирования и короткая инструкция; та же страница — внизу экрана входа и в меню профиля.
|
||||||
|
|
||||||
## 7. Бэкапы и восстановление
|
## 7. Бэкапы и восстановление
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,8 @@
|
|||||||
|
|
||||||
## Самообновление
|
## Самообновление
|
||||||
|
|
||||||
|
Первая установка — публичная страница `/android` веб-клиента: последний APK, адрес сервера и инструкция.
|
||||||
|
|
||||||
Приложение распространяется только через Gitea Releases (не Google Play).
|
Приложение распространяется только через Gitea Releases (не Google Play).
|
||||||
|
|
||||||
**Поток:**
|
**Поток:**
|
||||||
|
|||||||
@@ -880,6 +880,23 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/v1/admin/library/analysis/retry": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/** Вернуть в очередь треки, которые не удалось проанализировать (admin) */
|
||||||
|
post: operations["retryAnalysis"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/v1/admin/search/reindex": {
|
"/api/v1/admin/search/reindex": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -2012,6 +2029,8 @@ export interface components {
|
|||||||
done: number;
|
done: number;
|
||||||
failed: number;
|
failed: number;
|
||||||
analyzer_ver: string | null;
|
analyzer_ver: string | null;
|
||||||
|
/** @description Самая частая ошибка среди неудавшихся — подсказка, что чинить */
|
||||||
|
top_error: string | null;
|
||||||
};
|
};
|
||||||
/** @description Метрики волны за 7 дней */
|
/** @description Метрики волны за 7 дней */
|
||||||
WaveMetrics: {
|
WaveMetrics: {
|
||||||
@@ -3355,6 +3374,29 @@ export interface operations {
|
|||||||
default: components["responses"]["Problem"];
|
default: components["responses"]["Problem"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
retryAnalysis: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Сколько задач снова в очереди */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
requeued: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default: components["responses"]["Problem"];
|
||||||
|
};
|
||||||
|
};
|
||||||
reindexSearch: {
|
reindexSearch: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|||||||
@@ -160,7 +160,8 @@
|
|||||||
"wave_early_skip": "Early skips",
|
"wave_early_skip": "Early skips",
|
||||||
"wave_likes": "Likes per 100",
|
"wave_likes": "Likes per 100",
|
||||||
"wave_model": "Model version",
|
"wave_model": "Model version",
|
||||||
"wave_by_model": "Wave model versions compared"
|
"wave_by_model": "Wave model versions compared",
|
||||||
|
"analysis_retry": "Retry"
|
||||||
},
|
},
|
||||||
"write_template": {
|
"write_template": {
|
||||||
"title": "Uploads and path template",
|
"title": "Uploads and path template",
|
||||||
|
|||||||
@@ -20,5 +20,19 @@
|
|||||||
"min_length": "At least {{min}} characters",
|
"min_length": "At least {{min}} characters",
|
||||||
"submit": "Save and continue",
|
"submit": "Save and continue",
|
||||||
"logout": "Sign out"
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"link": "Android app",
|
||||||
|
"title": "pipu for Android",
|
||||||
|
"subtitle": "Your music from this server on your phone: My Wave, your collection and offline listening.",
|
||||||
|
"download": "Download APK",
|
||||||
|
"version": "version {{version}}",
|
||||||
|
"step_install": "Open the downloaded file. If Android asks, allow installs from this source.",
|
||||||
|
"step_server": "Launch pipu and enter the server address:",
|
||||||
|
"copy_server": "Copy the server address",
|
||||||
|
"step_updates": "Sign in with your account. The app offers new versions by itself.",
|
||||||
|
"checksum": "SHA-256 checksum",
|
||||||
|
"unavailable": "The app is not available yet: release downloads are not configured on this server (docs/deployment.md).",
|
||||||
|
"to_login": "Sign in to the web app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,8 @@
|
|||||||
"profile": {
|
"profile": {
|
||||||
"menu": "Profile menu",
|
"menu": "Profile menu",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"logout": "Sign out"
|
"logout": "Sign out",
|
||||||
|
"android_app": "Android app"
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"admin": "Administrator",
|
"admin": "Administrator",
|
||||||
|
|||||||
@@ -160,7 +160,8 @@
|
|||||||
"wave_early_skip": "Ранние скипы",
|
"wave_early_skip": "Ранние скипы",
|
||||||
"wave_likes": "Лайков на 100",
|
"wave_likes": "Лайков на 100",
|
||||||
"wave_model": "Версия модели",
|
"wave_model": "Версия модели",
|
||||||
"wave_by_model": "Сравнение версий модели волны"
|
"wave_by_model": "Сравнение версий модели волны",
|
||||||
|
"analysis_retry": "Повторить"
|
||||||
},
|
},
|
||||||
"write_template": {
|
"write_template": {
|
||||||
"title": "Приём загрузок и шаблон пути",
|
"title": "Приём загрузок и шаблон пути",
|
||||||
|
|||||||
@@ -20,5 +20,19 @@
|
|||||||
"min_length": "Не короче {{min}} символов",
|
"min_length": "Не короче {{min}} символов",
|
||||||
"submit": "Сохранить и продолжить",
|
"submit": "Сохранить и продолжить",
|
||||||
"logout": "Выйти"
|
"logout": "Выйти"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"link": "Приложение для Android",
|
||||||
|
"title": "pipu для Android",
|
||||||
|
"subtitle": "Ваша музыка с этого сервера на телефоне: «Моя волна», коллекция и офлайн-прослушивание.",
|
||||||
|
"download": "Скачать APK",
|
||||||
|
"version": "версия {{version}}",
|
||||||
|
"step_install": "Откройте скачанный файл. Если Android спросит — разрешите установку из этого источника.",
|
||||||
|
"step_server": "Запустите pipu и введите адрес сервера:",
|
||||||
|
"copy_server": "Скопировать адрес сервера",
|
||||||
|
"step_updates": "Войдите своим логином. Новые версии приложение предложит установить само.",
|
||||||
|
"checksum": "Контрольная сумма SHA-256",
|
||||||
|
"unavailable": "Приложение пока недоступно: на сервере не настроена загрузка релизов (docs/deployment.md).",
|
||||||
|
"to_login": "Войти в веб-версию"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,8 @@
|
|||||||
"profile": {
|
"profile": {
|
||||||
"menu": "Меню профиля",
|
"menu": "Меню профиля",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"logout": "Выйти"
|
"logout": "Выйти",
|
||||||
|
"android_app": "Приложение для Android"
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"admin": "Администратор",
|
"admin": "Администратор",
|
||||||
|
|||||||
@@ -288,6 +288,9 @@ type AnalysisStatus struct {
|
|||||||
Failed int `json:"failed"`
|
Failed int `json:"failed"`
|
||||||
Queued int `json:"queued"`
|
Queued int `json:"queued"`
|
||||||
Running int `json:"running"`
|
Running int `json:"running"`
|
||||||
|
|
||||||
|
// TopError Самая частая ошибка среди неудавшихся — подсказка, что чинить
|
||||||
|
TopError *string `json:"top_error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AndroidRelease defines model for AndroidRelease.
|
// AndroidRelease defines model for AndroidRelease.
|
||||||
@@ -1464,6 +1467,9 @@ type ServerInterface interface {
|
|||||||
// Журнал действий (admin), новые сверху
|
// Журнал действий (admin), новые сверху
|
||||||
// (GET /api/v1/admin/audit)
|
// (GET /api/v1/admin/audit)
|
||||||
ListAudit(w http.ResponseWriter, r *http.Request, params ListAuditParams)
|
ListAudit(w http.ResponseWriter, r *http.Request, params ListAuditParams)
|
||||||
|
// Вернуть в очередь треки, которые не удалось проанализировать (admin)
|
||||||
|
// (POST /api/v1/admin/library/analysis/retry)
|
||||||
|
RetryAnalysis(w http.ResponseWriter, r *http.Request)
|
||||||
// Файлы, которые не удалось прочитать (admin)
|
// Файлы, которые не удалось прочитать (admin)
|
||||||
// (GET /api/v1/admin/library/errors)
|
// (GET /api/v1/admin/library/errors)
|
||||||
ListLibraryErrors(w http.ResponseWriter, r *http.Request, params ListLibraryErrorsParams)
|
ListLibraryErrors(w http.ResponseWriter, r *http.Request, params ListLibraryErrorsParams)
|
||||||
@@ -1695,6 +1701,12 @@ func (_ Unimplemented) ListAudit(w http.ResponseWriter, r *http.Request, params
|
|||||||
w.WriteHeader(http.StatusNotImplemented)
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Вернуть в очередь треки, которые не удалось проанализировать (admin)
|
||||||
|
// (POST /api/v1/admin/library/analysis/retry)
|
||||||
|
func (_ Unimplemented) RetryAnalysis(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
// Файлы, которые не удалось прочитать (admin)
|
// Файлы, которые не удалось прочитать (admin)
|
||||||
// (GET /api/v1/admin/library/errors)
|
// (GET /api/v1/admin/library/errors)
|
||||||
func (_ Unimplemented) ListLibraryErrors(w http.ResponseWriter, r *http.Request, params ListLibraryErrorsParams) {
|
func (_ Unimplemented) ListLibraryErrors(w http.ResponseWriter, r *http.Request, params ListLibraryErrorsParams) {
|
||||||
@@ -2183,6 +2195,26 @@ func (siw *ServerInterfaceWrapper) ListAudit(w http.ResponseWriter, r *http.Requ
|
|||||||
handler.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetryAnalysis operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) RetryAnalysis(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||||
|
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
siw.Handler.RetryAnalysis(w, r)
|
||||||
|
}))
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
handler = middleware(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
// ListLibraryErrors operation middleware
|
// ListLibraryErrors operation middleware
|
||||||
func (siw *ServerInterfaceWrapper) ListLibraryErrors(w http.ResponseWriter, r *http.Request) {
|
func (siw *ServerInterfaceWrapper) ListLibraryErrors(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
@@ -3057,12 +3089,6 @@ func (siw *ServerInterfaceWrapper) DownloadAndroidApk(w http.ResponseWriter, r *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
siw.Handler.DownloadAndroidApk(w, r, version)
|
siw.Handler.DownloadAndroidApk(w, r, version)
|
||||||
}))
|
}))
|
||||||
@@ -3077,12 +3103,6 @@ func (siw *ServerInterfaceWrapper) DownloadAndroidApk(w http.ResponseWriter, r *
|
|||||||
// GetAndroidLatest operation middleware
|
// GetAndroidLatest operation middleware
|
||||||
func (siw *ServerInterfaceWrapper) GetAndroidLatest(w http.ResponseWriter, r *http.Request) {
|
func (siw *ServerInterfaceWrapper) GetAndroidLatest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
siw.Handler.GetAndroidLatest(w, r)
|
siw.Handler.GetAndroidLatest(w, r)
|
||||||
}))
|
}))
|
||||||
@@ -4455,6 +4475,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl
|
|||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Get(options.BaseURL+"/api/v1/admin/audit", wrapper.ListAudit)
|
r.Get(options.BaseURL+"/api/v1/admin/audit", wrapper.ListAudit)
|
||||||
})
|
})
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Post(options.BaseURL+"/api/v1/admin/library/analysis/retry", wrapper.RetryAnalysis)
|
||||||
|
})
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Get(options.BaseURL+"/api/v1/admin/library/errors", wrapper.ListLibraryErrors)
|
r.Get(options.BaseURL+"/api/v1/admin/library/errors", wrapper.ListLibraryErrors)
|
||||||
})
|
})
|
||||||
@@ -4709,6 +4732,36 @@ func (response ListAuditdefaultApplicationProblemPlusJSONResponse) VisitListAudi
|
|||||||
return json.NewEncoder(w).Encode(response.Body)
|
return json.NewEncoder(w).Encode(response.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RetryAnalysisRequestObject struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type RetryAnalysisResponseObject interface {
|
||||||
|
VisitRetryAnalysisResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type RetryAnalysis200JSONResponse struct {
|
||||||
|
Requeued int `json:"requeued"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response RetryAnalysis200JSONResponse) VisitRetryAnalysisResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RetryAnalysisdefaultApplicationProblemPlusJSONResponse struct {
|
||||||
|
Body Problem
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response RetryAnalysisdefaultApplicationProblemPlusJSONResponse) VisitRetryAnalysisResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/problem+json")
|
||||||
|
w.WriteHeader(response.StatusCode)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response.Body)
|
||||||
|
}
|
||||||
|
|
||||||
type ListLibraryErrorsRequestObject struct {
|
type ListLibraryErrorsRequestObject struct {
|
||||||
Params ListLibraryErrorsParams
|
Params ListLibraryErrorsParams
|
||||||
}
|
}
|
||||||
@@ -6832,6 +6885,9 @@ type StrictServerInterface interface {
|
|||||||
// Журнал действий (admin), новые сверху
|
// Журнал действий (admin), новые сверху
|
||||||
// (GET /api/v1/admin/audit)
|
// (GET /api/v1/admin/audit)
|
||||||
ListAudit(ctx context.Context, request ListAuditRequestObject) (ListAuditResponseObject, error)
|
ListAudit(ctx context.Context, request ListAuditRequestObject) (ListAuditResponseObject, error)
|
||||||
|
// Вернуть в очередь треки, которые не удалось проанализировать (admin)
|
||||||
|
// (POST /api/v1/admin/library/analysis/retry)
|
||||||
|
RetryAnalysis(ctx context.Context, request RetryAnalysisRequestObject) (RetryAnalysisResponseObject, error)
|
||||||
// Файлы, которые не удалось прочитать (admin)
|
// Файлы, которые не удалось прочитать (admin)
|
||||||
// (GET /api/v1/admin/library/errors)
|
// (GET /api/v1/admin/library/errors)
|
||||||
ListLibraryErrors(ctx context.Context, request ListLibraryErrorsRequestObject) (ListLibraryErrorsResponseObject, error)
|
ListLibraryErrors(ctx context.Context, request ListLibraryErrorsRequestObject) (ListLibraryErrorsResponseObject, error)
|
||||||
@@ -7108,6 +7164,30 @@ func (sh *strictHandler) ListAudit(w http.ResponseWriter, r *http.Request, param
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetryAnalysis operation middleware
|
||||||
|
func (sh *strictHandler) RetryAnalysis(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var request RetryAnalysisRequestObject
|
||||||
|
|
||||||
|
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.RetryAnalysis(ctx, request.(RetryAnalysisRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "RetryAnalysis")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(r.Context(), w, r, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||||
|
} else if validResponse, ok := response.(RetryAnalysisResponseObject); ok {
|
||||||
|
if err := validResponse.VisitRetryAnalysisResponse(w); err != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ListLibraryErrors operation middleware
|
// ListLibraryErrors operation middleware
|
||||||
func (sh *strictHandler) ListLibraryErrors(w http.ResponseWriter, r *http.Request, params ListLibraryErrorsParams) {
|
func (sh *strictHandler) ListLibraryErrors(w http.ResponseWriter, r *http.Request, params ListLibraryErrorsParams) {
|
||||||
var request ListLibraryErrorsRequestObject
|
var request ListLibraryErrorsRequestObject
|
||||||
@@ -9130,248 +9210,250 @@ func (sh *strictHandler) GetReadyz(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||||
var swaggerSpec = []string{
|
var swaggerSpec = []string{
|
||||||
|
|
||||||
"H4sIAAAAAAAC/+x9fW8cx5nnV2nM7gFkdkRStuVsKCwOsmzHurViRlTOd4h0M82ZItmrnu5xv1BiDAGk",
|
"H4sIAAAAAAAC/+x9/3PcxpXnv4Ka3asisyOSsi1nQ9XWlSzbsW+tmBGV811FuhlwpkliNQOMAQwlRqUq",
|
||||||
"GFv2URFjX3Y32I3teHOLOyD/jCiOOOIr4E9Q/RXySQ71PFXdVd3VL0P2UPbi/pI401Nd9dRTTz2vv+fj",
|
"Uowt+6iIsS+7m9qN7XhzW3dV+WVEccQRv1b5L2j8C/lLrvq9bqAb6AYwJIayt+4XW8PBNLpfv379vn7e",
|
||||||
"Rsft9V2HOIHfmP+40Tc9s0cC4sFf10PPdz32vy7xO57VDyzXacw36Fd0SE+jDXpC96MNOoge0+Nom740",
|
"g1rL6/Y8l7hhUJt/UOvZvt0lIfHh0/W+H3g++1ebBC3f6YWO59bma/QrOqSn0QY9ofvRBh1Ej+lxtE1f",
|
||||||
"6EG0FW1Em/Qk2jDoiO4bbYc8CFodGKhtwI+GdC/apnvRVvQ5HdKXRrQZPWKj0GM6ij6NthvNhsXe8lFI",
|
"WvQg2oo2ok16Em1YdET3raZL7oeNFgzUtOBHQ7oXbdO9aCv6nA7pSyvajB6xUegxHUWfRtu1es1hb/m4",
|
||||||
"vPVGs+GYPdKYb+AYjWbD76ySnslmFaz32Td+4FnOSuPhw2bjHSewgvUbXfYtDNI3g9VkDKvbaDY88lFo",
|
"T/z1Wr3m2l1Sm6/hGLV6LWitkq7NZhWu99g3Qeg77krt4cN67R03dML199vsWxikZ4eryRhOu1av+eTj",
|
||||||
"eaTbmA+8kMjjLbtezwwa840whCez479v9awgHjw1Qxu+lAfskmUztIPG/JW5ZqNnPrB6Ya8x/9oc+8ty",
|
"vuOTdm0+9PtEHm/Z87t2WJuv9fvwZHb8D5yuE8aDp2bYgS/lAdtk2e53wtr8lbl6rWvfd7r9bm3+tTn2",
|
||||||
"8K/L8XssJyArxIMXLbqh1yGTW8gv+rZrdhcDMwj9dy07ILpt/jZ6RAfRo2gr2oy2jegxHcL27Rt0nw7o",
|
"yXHx0+X4PY4bkhXiw4sWvb7fIpNbyC96Hc9uL4Z22A/edToh0W3zt9EjOogeRVvRZrRtRY/pELZv36L7",
|
||||||
"abQD3z1tNBvkQd92u6Qxv2zaPtFvoA8vU+hjBaQHnPbXHlluzDf+ajZhx1l8zJ+Vp9p4GC/G9Dxznf3t",
|
"dEBPox347mmtXiP3ex2vTWrzy3YnIPoNDOBlCn2ckHSB0/7aJ8u1+dpfzSbsOIuPBbPyVGsP48XYvm+v",
|
||||||
"B+s2+4Atmv39C594kyLcQ/Zjv+86PoF5L3jukk167L8d1wmIA8xh9vu21TEZGWf7+MTf/IPPaPqx9K6i",
|
"s89BuN5hf2CLZp9/ERB/UoR7yH4c9Dw3IDDvBd9b6pAu+2fLc0PiAnPYvV7HadmMjLM9fOJv/iFgNH0g",
|
||||||
"NYtx4Y2pXfk6+oyO6DN6QAfG1K13rxs/eePKj6dhV/nP2ejXuj3LYaSAY+25feIFFk664xEzIN2WGShL",
|
"vStvzWJceGNqV76OPqMj+owe0IE1dfPd69ZP3rjy42nYVf5zNvq1dtdxGSngWPtej/ihg5Nu+cQOSbth",
|
||||||
"7poBuRRYPZJdd7PRtfy+ba63kIwfN5zQts0lRnckYeYHVrcCPZsNy28t2W7nHulKB3rJdW1iOux72/SD",
|
"h8qS23ZILoVOl2TXXa+1naDXsdcbSMYHNbff6dhLjO5IwswPnHYJetZrTtBY6nitu6QtHeglz+sQ22Xf",
|
||||||
"lu2uWE7RdEtnY7sd0yYamdFs9EI/aHVWTWeFtDoe6RInsEzb109Hfrhv+v5918uZuOfi+4p2+RZ75mGz",
|
"d+wgbHS8FcfNm27hbDpey+4Qjcyo17r9IGy0Vm13hTRaPmkTN3TsTqCfjvxwzw6Ce55vmLjv4fvydvkm",
|
||||||
"EfrEE4TNHtWEW3+J7Bs/ntoW/tJ4tQp1c+aev/6mzCfpjbgb09hd+gfSCdgqYo5bMFdIluvis17p0Cfs",
|
"e+ZhvdYPiC8Imz2qCbf+Etk3fjy1Lfyl8WoV6hrmbl5/XeaT9EbciWnsLf0DaYVsFTHHLdgrJMt18Vkv",
|
||||||
"qznx0p1SgRfTJITXq4Nol2Mvhb3sMkwvsPygdPrw1C2yzEbquGvEa3Vc2/VUAmR4Mb1Q/GXo2ZWOXDf0",
|
"degT9tWceOlOKcGLaRLC69VBtMvpLPW72WXYfugEYeH04ambZJmN1PLWiN9oeR3PVwmQ4cX0QvGXfb9T",
|
||||||
"QOa0lNHjW6XymQysIOesBJ7ZuVd9E2+zx3XrWidm0c7JF2GG+3F2fAyZRClCN8VWqYSJF5G752zXsjLz",
|
"6si1+z7InIYyenyrlD6ToRMazkro26275TfxFntct651YuftnHwRZrgfZ8fHkEmUInRdbJVKmHgRxj1n",
|
||||||
"gvbw3Ft0EaTNJd1i2OuZ3voP6tTUcyhaHTfEyz977C6Y2+X5lO3U+3xXziWq5Z3PbIdW+mqn5Zj2um/5",
|
"u5aVmRe0h+feoosgrZF0i/1u1/bXf1CnpppD0Wh5fbz8s8fugrldnk/RTn3Ad+Vcolre+cx2aKWvdlqu",
|
||||||
"XNHLshD7/lfEa60Rr5o4dB2i35Bl07IVhUP67qOQhHnfeaHjsME1X6aWyUdJfsKnE7+7qa5HT5Cu51rd",
|
"3VkPnIArelkWYt//iviNNeKXE4eeS/Qbsmw7HUXhkL77uE/6pu/8vuuywbVfhl6vQXxfay99Swf0iA6i",
|
||||||
"W8Qmpq+5ULvufYdpxoLTs5qN5bT8sN93vQBXlFIkv2T6fLRJR3RkMOOLvqBDg57QZ9EOKPjH0Tb8SY/p",
|
"HaZED5jBAx/oiaTKRZtoG9GRRY/pMNqie3RAd9kT0SfRZrRj/WXjdxY9pSd0L9pkP6H77L91K3ocPaIn",
|
||||||
"Cd2lo+hR9CTajHYaTQ1tHDcgfqune8//iR7RE4OPckKf0xNj6qbp3WMLQCsRzIpD9l86mNadPn/VfO3K",
|
"bOgR2FCPoifFOlFqX/iykzVy+sXEqqsbIK9Xv5tt33PaN0mH2IFGG2h791ym1otjmlXLHLcR9Hs9zw9x",
|
||||||
"m9pV+taviHKCLSd48w3tHNeI51uohpMHZq8PZsPczJszl3Xv5E+3OmDYlG65GDz1Q4kyfK7xaprqFqY3",
|
"O1Ik/ZIZI9EmHSG9RvQFHVr0hD6LdsA6OY624SM9pid0F6nCqFira/bO9UISNLq69/wfoC0f5YQ+pyfW",
|
||||||
"TMsTsRxNHQ52BotkUEUpl6OFlgo5nejiOqk8tQrCKb4CslLpXEvInaF2FmHXCq51Atc78zTOqtPnTucd",
|
"1A3bv8sWgCYubNsh+ycdTOtER7Bqv3blTe0qA+dXRBE/jhu++YZ2jmvEDxy0Ich9u9sDm2du5s2Zy7p3",
|
||||||
"J9DerJ0gw9JssBnU3nVTM8XCTNv+YLkx/8sS+Z4Q4+HdtDXKpLDxl43fGUyORJvRIzqkR3TAjvUhHRnR",
|
"8qcbLbDKHhTJGjF46ocSZfhc49XU1S1Mb5iWJ+JLIHWymQDJE6AlRbRBhS6U0Dq5yxVqeWolJGt8f2VF",
|
||||||
"Ft2jA3oYfUGPhY/olJ7Qw+gJ3Qd5MICfHEZPMmbcw+aZLNWK+9M319nJAzJ0uxZbjmkvSKTFWyXnlkn2",
|
"6rmWYJyhdhb9thNea4Wef+ZpnNUgMU7nHTfUqgWtMMPSbLAZND10U7PFwuxO58Pl2vwvCy6nhBgP76RN",
|
||||||
"JjC9FRK09G8tvaP4r/HzsS0ZuEZgY5qCD9Qh5eklS1bomstvtVhxCeO+MjMuDFZvu/eIo1MpOh3i+62A",
|
"aSYiQaoyOQJSecjENDvWh3RkcRF8GH1Bj4WDi4nfw+gJ3Qd5MICfHGrk7cP6mczskvvTs9fZyQMytNsO",
|
||||||
"fa2VeuRB3/KI37IcrXdsg57QA4O+gIvrmI4MHPASu+/oAR3SYzpospMxpAfRFj2me+Dd1CgWZNkj/moy",
|
"W47dWZBIiyLfcAUkexPa/goJG/q3Fl6w/Nf497HNMLhSYGPqgg/UIeXpJUtW6Grkt0pM0IRxX5kN2g9X",
|
||||||
"k9Sr/o0fmQN2j+7Rw2jHaHdsizi4z/OGibpCW6u5skFjFiMOs2p/2XiLmJ6ieKiiq2xnb5LMjijUVF6r",
|
"b3l3iavTh1otEgSNkH2tlXrkfs/xSdBwXK1rb4Oe0AOLvoCL65iOLBzwErvv6AEd0mPQLTbpkB5EW/SY",
|
||||||
"EJKPr9urt1w38APP7F9P/BC3yEch0V15Drmvc8FIFwG536ouhZWnm+rourleB4/JAn8kd5ad0PPYNpXO",
|
"7oFrVqMVkWWfBKvJTFKv+jd+ZA7YPbpHD6Mdq9nqOMTFfZ63bNQVmlq1mw0asxhxmUn+y9pbxPaJTDJV",
|
||||||
"tOABzUyLpwaccTu14ffJEmidwCjafb8OUuEXPvFyl5N2A/bMB+8TZyVYbcy/+YZmyMl4waR9ghdoiYAi",
|
"dBXt7A2S2RGFmsprFULy8XV79ZbnhUHo273riRPlJvm4T3RXnkvu6fxH0kVA7jXKS2Hl6bo6um6u18Hd",
|
||||||
"Tu8BDUiv73qmt66QPXXovoFDPKD70Ta7p+gwesS0X6YS79ERPTYgaLHfOOMJkjxcmuWxUbOT1C30nTXi",
|
"s8AfMc6y1fd9tk2FM815QDPT/KkBZ9xKbfg9sgQaKDCKdt+vg1T4RUB843LSPsyuff8D4q6Eq7X5N9/Q",
|
||||||
"BG+ZQWc1u06yJgI7lcT1gm2uw3BgPZgPbuCPrszNlVh1/D3F07tFfIiF6KRwP8izuboh+tJJjk/LI+xN",
|
"DDkZF560T/ACLRFQxOndtyHp9jzf9tcVsqcO3TdwiAd0P9pm9xQdRo/AhmDWAzMTLIi47NfOeIIk95xm",
|
||||||
"WvvmW6aCRJ8xqUmPIMxEB9FGtE2HxtSdcG7udWK8Psfk6TEd0pfTsc6yacAnTKjv0iH8jqkuRwbEqYY4",
|
"eWzU7CR1C31njbjhW3bYWs2uk6yJqFQpcb3QsddhOLAe7Pvv44+uzM0VmKT8PfnTu0kCCOTopHAvNBmM",
|
||||||
"XqPUfo/XpSxCmrGOXO9axO6+43k6zTNliCTstsx+VH5s8LEmjqN7+XvEtIPVPAPcjz8XIsW9p5EjqZfy",
|
"7T4GAojBIecT9iatffMtU0Giz5jUpEcWNxk3om06tKZu9+fmXifW63NMnh7TIX05Hessm2A9glDfpUP4",
|
||||||
"X2lfZ/mB660zLjuzms2k0ZgKIpggFV2XOo0reaUYq2BxdWhRMp1elRr1nusHb1ue3l/UtcZ10UFQrpRh",
|
"HVNdjiwIsg1xvFqh8yFel7IIacY6cr3rkE77HWEDpx1obb1yvsx+VHxs8LE6jqN7+XvE7oSrJu9BEP9d",
|
||||||
"eegOhtdN6n1ricnHnONCxMea82KTVkUG84jdyplts+FDlLbaUJnDiHOQB5He1uTTL1t2HfylkPFVMRif",
|
"iBTvrkaOpF7Kf6V9nROEnr/OuOzMajaTRmMqiGCClPS76jSu5JVirJzFVaFFyXR6VWrUe14Qvu34emdX",
|
||||||
"RK73z14K88IaJvcclt64qofxofCZ5gwLOwS7UPBAzlc9y/ctZ6VV8Ahue/VdwowAnHueKEjiM9n33TfX",
|
"2xnXvwgRxUKG5XFHGF43qQ+cJSYfDccl9iRpzkuHNEoymE86DcNs67UAQszlhsocRpyDPIj0tnrN7BiS",
|
||||||
"SlWxD801cpMEntXxs9cZp1VT7EX8OkGJ9LJVEiYrlnaMT0vLEO6K5eTr1Yn1U7YoSR2GYPqa1SGxAptJ",
|
"l10FfylkfFUMxidhdF12lvqmmIzN3Z6FN67qHn0oHL6GYWGHYBdyHjB81XWCwHFXGjmP4LaX3yVMZ8C5",
|
||||||
"bBlAjsqIHtFhtGF89+frq57bI8as8aHldN37/neHTeO7Py9YD4ht/O13h42mrANfljQl2dVQoPufSeuV",
|
"m0RBElzKvu+evVaoin1kr5EbJPSdVpC9zjit6mIv4tcJSqSXrZIwWbG0Y3xaWobwVhzXrFcn1k/RoiR1",
|
||||||
"wrcyIXRkvEm+3wkA3/PIvNXqe2S5PEfFWoDHJhnMj+dyvlC+jkk+cJZc0+vy2IYudKfKqYnERtNpLvQZ",
|
"GDIB1pwWiRXYTFbOABJsRvSIDqMN67s/X1/1vS6xZq2PHLft3Qu+O6xb3/15wblPOtbffndYq8s68GVJ",
|
||||||
"PaQn9AUkukSbdECPePSAPkN/SPSZ+GAA7hH2MTofo010M0JOGToYd6r4416dm1z+5Zih1zRVV4jjkaIN",
|
"U5JdDTm6/5m0Xin2LBNCR8Yb5PudvfA9TytwGj2fLBcn2DgL8NgkMxHiuZwvD0HHJB+6S57tt3lgRhd3",
|
||||||
"q2n6mplXmV4yRPZ0pt4Q/78pXT98ecV8fH3VtTokj5tbVlelT+mOy9ZwJYqX3grJiK+XmdfSpAuXv2AG",
|
"VOXURAK76Rwd+owe0hP6god2IP6D0QP6DP0h0WfiDwNwj7A/o/Mx2kQ3IwZzwMG4U8Yf9+rc5PIvx4wb",
|
||||||
"qwtmEBDP0eiEfos4jPnzxCJZIQ+yM58rU/Dwd015/Jy5sXnpDYg+fjmGp0JaacnuZG0KfFXBJBc8smaR",
|
"p6m6Qlyf5G1YRdPXzLzM9JIhsqcz9Yb433Xp+uHLy+fj66ue0yImbm44bZU+hTsuW8OlKF54KyQjvl5k",
|
||||||
"++fVq/kwecpa6PTMoLOqd3zkaNHJb8qnn6s7TZLazYbPo1DnyX6tvmGxx0pzKzkBszlQpJceR/F42k8O",
|
"XkuTzl3+gh2uLthhSHxXoxMGDeIy5jeJRbJC7mdnPlek4OHv6vL4hrmxeekNiB5+OYanQlppwe5kbQp8",
|
||||||
"+inXeeUsC5+YXmcVxLNtExF8WUVTvcHzCLRu1mXP7ZWkaKlX4S9+cePttR8b9CR6ZNADiJMP6XH0iA4g",
|
"Vc4kF3yy5pB759Wr+TAmZa3vdu2wtap3fBi06OQ3xdM36k6TpHa9FvAo1HlSd8tvWOyx0txKbshsDhTp",
|
||||||
"8sY+ip5Gjw06onsQfjtlj/JnjukJeLBGTB0vk3JuB/zV42mG7vKybSmpFpJU4c6SvOX2Xd8qTFnDy6Vq",
|
"hcdRPJ72k4N+ynVeOUUkILbfWgXx3OkQEXxZRVO9xpMgtG7WZd/rFuSXqVfhL37x/ttrP7boSfTIogcQ",
|
||||||
"jk5q70Cb8gPTC7jbpkUcsLjvWX3YQnIPlOnQB32L+CGs0Lbugb7l8P90LT/+SPxft7GMVVo+8SH4fxaf",
|
"Jx/S4+gRHWA+wwE9jJ5Gjy06onsQfjtlj/JnjukJeLBGTB0vknJeC/zV42mG3vJyx1HyRCSpwp0lpuX2",
|
||||||
"QPKItHB1V/IOwZLZuXcdWTjPl5ZVvc0lYhfoaR/XtMTcCTNjluSe3OoR6jQBHt7VxJK54YeTLlUESxkT",
|
"vMDJzbfDy6VsglFq70CbCkLbD7nbpkFcsLjvOj3YQnIXlOl+APoWCfqwwo5zF/Qtl/+j7QTxn8S/dRvL",
|
||||||
"z/dYOkRaUsIQLcvp4r2r8233iRm0etzvG7tdl5cZVzgol2wtL/qr4fKynXMoiw5VKWXCfrfMeCwdY821",
|
"WKURkACC/2fxCSSPSAtXd8V0CJbs1t3ryMImX1pW9baXSCdHT3tQ0RKNE2bGLDGe3PIR6jQBHt7RxJK5",
|
||||||
"Q0UFdcLekkb2S4dA3hBBfZWEsQxvJMtXaRi/WOYGZUV3y7j1htMPg0KWHYtRU2xZ7kpQ2TK+Ruea9bBo",
|
"4YeTLlQECxkTz/dYOkRaUsIQDcdt472r8233iB02utzvG7tdl5cZV7golzpaXgxW+8vLHcOhzDtUhZTp",
|
||||||
"YdAnw7HFr/9ecm/CebFCcrmpW0gdHKnlJkkpzHdZl8snM1htQUDHz09hybfAk/lw9SrZ1IxH7IT7wqJN",
|
"99pFxmPhGGtep6+ooG6/u6SR/dIhkDdEUF8lYSzDa8nyVRrGL5a5QVnRnSJufd/t9cNclh2LUVNsWexK",
|
||||||
"SNg5pQO6G31Gh2B5R5/RATfUIdkhzgeCfAgRKs3fGYVfCjztXhw8LI3a3CSBCUx0z+r386ydwFw5F+Ey",
|
"UNkyvkbn6tWwaG7QJ8Ox+a//XnJvwnmxQnK5rltIFRyp5SZJKTS7rIvlkx2uNiCgE5hTWMwWeDIfrl4l",
|
||||||
"RlDst1cJmkxD3TM+g3hhRf5+qchIHxBMbdkf6ABKhOKMqwO2D0x/FIVDI5FFYl3+W6fJ9kp1e7bNMFid",
|
"m5rxiJ1wX1i0CQk7pzylcgiWd/QZHXBDHZId4nwgyIcQoVLzzij8kuNp9+PgYWHU5gYJbWCiu06vZ7J2",
|
||||||
"sZw107a6sitJm2DSJYFp2foIerQTbUI09ZhpreK1wC/PGTsZU+zb+PNTEXOng7xEsafaBNHET59bLTUC",
|
"QnvlXITLGEGx314laDINdc/4DOKF5fn7pQopfUAwtWV/oANIio0zrg7YPsipsiORReJc/lu3zvZKdXs2",
|
||||||
"DRpUZDbqcbQdfcJfAnWIvKhtgynMdMAs2iq2kRSw1ca5PLPnlyWZZXY8CbVqNGKum8XWVcNccsNgfsk2",
|
"7X64OuO4a3bHacuuJG2CSZuEttPRR9CjnWgToqnHTGsVrwV+ec7YyZpi38Z/PxUxdzowJYo91SaIJn56",
|
||||||
"nXuNiuHYgiDwLWJ2AtK9LaKk53cdnyPgqpQg5Qdb5TnXERFTaHDuiFizEbiBaY9h5uPz5UEzmKdI/+T3",
|
"Y6nXCDRoUJHZqMfRdvQJfwkUUfKKvA2mMNMBs2jL2EZSwFYb5/LtblCUZJbZ8STUqtGIuW4WW1c1e8nr",
|
||||||
"a8po0V2v4mc5yownjVpKJfZcVhryL3Lm3M0N83VWCQ9g5Qlm9Xi33XttkbfRXjYtu21MoSSLHoMERBMZ",
|
"h/NLHdu9WysZjs0JAt8kdisk7VsiSnp+1/E5Aq5K/ZQ52CrPuYqImEKDc0fE6rXQC+3OGGY+Pl8cNIN5",
|
||||||
"EvLiBLpdIX+GkJsXbUBy+2ZO6nlKnvyRDul+tAXuZ6iONXgl9C6MBGKGSbAXdI+eCKmC4zPpmhjjBYde",
|
"ivRPfr+mjBbd9Sp+ZlBmfGnUQiqx57LSkH9hmHPbGOZrrRIewDIJZvV4N727TZG30Vy2nU7TmkJJxlP5",
|
||||||
"yq9g71/xzC7pVk61aAoq6qkPmYa5vqFxMhF5gcJVg62dPmOyFAuE9+gg+iJ6ZCR5kIzoHde9Z5FqFuIt",
|
"0USGhLw4gW5XyJ8h5OZFG5DcvmlIPU/Jkz/SId2PtsD9DKW9Fi/j3oWRQMwwCfaC7tETIVVwfCZdE2M8",
|
||||||
"HqMRlDC7PUgdZMqkZ2LGayqNMNmxxY7pLHjuikd8XSS5283LNaqufhWHhR3LXz2nhbTshk7OLJdD286J",
|
"59BL+RXs/Su+3Sbt0qkWdUFFPfUh09DoGxonE5EXKFy12NrpMyZLh7wAYxB9ET2ykjxIRvSW5911SDkL",
|
||||||
"gWH4NSck7a7lrbvvuR3i+9XLXqR3gntl3AvBbfFX6l/IDbIKAjMprwGqKNNRd0KQVHm7vPQm54zk9YJm",
|
"8SaP0QhK2O0upA4yZdK3MeM1lUaY7Nhiy3YXfG/FJ4Euktxum3KNyqtf+WFh1wlWz2khLXt91zDL5X6n",
|
||||||
"CWHjjS9S1xj75Z4vsXkZTQlODhNavOb+iUF3mXQyol/TAX1JD6PtpsG0IqYuqfIMCmsgrY0JnxHdZ1oc",
|
"Y4iBYfjVEJL21kzr7vleiwRB+Zod6Z3gXhn3QvAa/JX6F3KDrITATEptgCrKdNSdECRV3i4vvc45I3m9",
|
||||||
"PY5z7ofJHsiBluzEwaWJaXqFKRg1lIU1tXHFysWB2UCi50FSXQtBB7IU/udEuYMbYC/aiLZAB30JiZzR",
|
"oFlC2Hjj89Q1xn7G8yU2L6MpwclhQosDBjyx6C6TTlb0azqgL+lhtF23mFbE1CVVnkFhDaS1MeEzovtM",
|
||||||
"JrhWB3SPSaymQYfRJlwrTEOEiGL0CZgzjNpH0RYYONFjbgahEnsMivchCPknVU75mfQIvZb7KejYB4xD",
|
"i6PHcc79MNkDOdCSnTi4NDFNLzcFo4Katro2rli6sjEbSPR9SKprIGJClsL/nCh3cAPsRRvRFuigLyGR",
|
||||||
"TugzgNYA++wxXD2HGEIV9tseV8hf4gmommdTlC2Tn3pSOY9EGzyspeJ4jFQWTuIsS5VrY4vEWyPeDWfZ",
|
"E+rBDumA7jGJVbfoMNqEa4VpiBBRjD4Bc4ZR+yjaAgMneszNIFRij0HxPgQh/6TMKT+THqHXcj8FHfuA",
|
||||||
"1RyevtXSlp+taWvPlkQmfEuOlmqFxi7aeLvIngbs7hHoPiOBrMLEBfuXMfXn0RecW1ExGUYb0WdMVMSx",
|
"ccgJfQa4IM95RRssZCDbb3tcIX+JJ6Bsnk1etow59aR0Hok2eFhJufQYqSycxFmWKtbGFom/Rvz33WVP",
|
||||||
"BBFeMCDe/oIOQcdJDDMUTuyDPTg07MODaEsjZLCijaect9L1dBmrFeZMj3gwH21X/qPrbpcIfeMSN1Ux",
|
"c3h6TkNbframrT1bEpnwDTlaqhUau2jj7SJ7WrC7R7yMkcPCMHHB/s+Y+vPoC86tqJgMo43oMyYq4liC",
|
||||||
"OQAYOdppgpXLF3rClgcTZJwebUCFBk/hTlS+YbQBlQl8Jy7Pzf3kJ9rKRh4tT/asb/XDS73QtzraWkVR",
|
"CC9YEG9/QYeg4ySGGQqnVBFltKURMljRxlPOG+l6uozVCnOmRzyYj7Yr/9F1r02EvnGJm6qYHACMHO3U",
|
||||||
"wdfC1A1f+TG7s9hrQTmunikh8U5eFWe0o6yNDoypRdL7r8SblpfZmJu5PDNXaify2H5S1CgzsG6NBXut",
|
"wcrlCz1hy4MJMk6PNqBCg6dwJyrfMNqAygS+E5fn5n7yE21lI4+WJ3vWc3r9S91+4LS0tYqigq+BqRuB",
|
||||||
"ZWf9AfLFImvK9lLN1TTlQFk9inbi0wNmfzUdhhd2aNPOh9EmbkgTy1sV1nzJdgmcEHTAFGPJ66A9QqmE",
|
"8mN2Z7HXgnJcPlNC4h1TFWe0o6yNDqypRdL9r8SflpdZm5u5PDNXaCfy2H5S1CgzsG6NOXutZWf9AQrE",
|
||||||
"tdryrkw/aIX+WJqbLlwlT0/NRtMBiIgXJvQr4IM6itMFS529Lh3zLvPKFvxWCPBEukv5GxBTQqaBGB3x",
|
"IivK9lLN1TTlQFk9inbi0wNmfzkdhhd2aNPOh9Embkgdy1sV1nzJdgmcEHTAFGPJ66A9QqmEtcryruwg",
|
||||||
"6OwG/rFv0JMYVmkPy7AHBliOz0A6H8aes6mfvbuIEvmYGZCgvcQVKQNmzMIXJ/QolvJiOOAwuA2eTGtZ",
|
"bPSDsTQ3XbhKnp6ajaZDPxEvTOiXwwdVVNYLljp7UT3mXZrKFoJGH7CVdJfyNyCmhEwDMTri0dkN/LBv",
|
||||||
"bIJllrDrfsesAb4ndg5r6nT2QK055fqPCHvHuV9P2N/G+zfeunXt1n9vvffB4u0mM9u3wBg/wZsPSz2e",
|
"0ZMYE4qXrQ8ssByfgXQ+jD1nUz97dxEl8jEzIEF7iStSBsyYhS9O6FEs5cVwwGFwGzyZ1rLYBMssYdeD",
|
||||||
"gPXOdHD0EVTS4vLSqZxlv0UeMEFZ6eSyx11Ylp87nA86RqXhGNWrx1AVo1kbQIVdZPeit2barZ7lhIHO",
|
"ll0B9lDsHNbU6eyBWnPK9R8R9o5zv56wz9YH779189rN/95478PFW3Vmtm+BMX6CNx+WejwB653p4Ogj",
|
||||||
"JUN/S3eREYGOoCODnjBC0ccUhmMpUJDyzTDGBjw5BBYrDxskbpOq2dPJr1o94vvcX1ihVnel1fcs17OC",
|
"KKXFmdKp3OWgQe4zQVnq5LLHPVhWYBwuAB2j1HCM6uVjqIrRrA2gwi6ye9FfszuNruP2Q51Lhv6W7iIj",
|
||||||
"9VI91FxZEI9KPtvyGYqL675nBaQVkF7f5jHyFJX/bxJ3QVZ+hOcdTMHok9hsRCd71+34s1xSzfS6f/Xj",
|
"Ah1BRwY9YYSijykMx1KgIOWbYYwNYHiIilYcNkjcJmWzp5NfNbokCLi/sESt7kqj5zue74TrhXqovbIg",
|
||||||
"aTVUw9XBPbY35SxflMaIkl9iVeUYqEyuHOwUgfP4rZkRvBliNSWoOXWbU/IodUPBgcm/AnK8pZp7QANj",
|
"HpV8tsUzFBfXPd8JSSMk3V6Hx8hTVP6/SdwFWfkRnncwBaNPYrMRnextrxXMckk1023/1Y+n1VANVwf3",
|
||||||
"pgiwXAlSGv9VJco5JYhWYmhPeBwfvVLlRL7igyIR8Up5+qLMt/l7X4sqgorEeTWRBX2pZ7k68i26hTCy",
|
"2N6MDXyhpDGi5JdYVTkGKpMrBztFYBO/1TOCN0OsuoSTp25zSh6lbig4MOYrwOAt1dwDGgw2RYAZJUhh",
|
||||||
"hioH2kg8f4zrKD97d/FS9IkA5IQSys+Z/uHdh7r26DFesonKMdArFVKyqToRgIhEwcNuavF2gJcxdG+O",
|
"/FeVKOeUIFqJoT3hcXz0SpkT+YoPikTEK8XpizLfmve+ElUEFYnzaiIL+lLPYnXkW3QLYWQNVQ60kXj+",
|
||||||
"yy5HvIiTi7kdof4cg/YErvVoi54y03G6aTD2xNeoy+Qv2uSANAPt7H/gB1al+FwetSGUX3ZXN2o+47Ve",
|
"GNdRfvbu4qXoE4EmCiWUnzP9w78Hde3RY7xkE5VjoFcqpGRTdSKAb4mCh93U4u0AL2Pp3hyXXY54EScX",
|
||||||
"bVfTely0RZ+hzyN6MmMAUi2aw+hPgDAMcC09QMgS+FX7jddeMyDaDUBxM2J2LR4Db99xok2jjeHcGY+Y",
|
"cztC/TkG7Qlc69EWPWWm43TdYuwpAf6kX7TJAWkG2tn/wA+sSvE5E7UhlF90V9cqPuOVXm1X03pctEWf",
|
||||||
"vuu0ZwxQ8ZPQD/e9ocdFivbwGzZGVpK00oEIfQNormB+7tTAbRsyXf40W4xutIGSl8RcZyHs056546hF",
|
"oc8jejJjAcwumsPoT4AwDHAtPUDIEvhV843XXrMg2g0odzNidg0eA2/edqNNq4nh3Bmf2IHnNmcsUPGT",
|
||||||
"STlCMEe8LGaiUH3idNH53XNDBx3j6P2GKGM65VuKw6Qr1s5a5FuPEZGrpteuKF+MRlqghVXWf0pUntvE",
|
"0A/3vaHHRYr28Bs2RlaStNKBCH0D4q9gfu7UwG0bMl3+NFuMbjWBkpfEXGch7NOcue2qRUkGIWgQL4uZ",
|
||||||
"D3LL9sOu5SYFjpnL5kBW6wcABDSiJ4paCnknL7k3+oRHbtXjFAt8tIZHKhIDOz+X5+bmprUySiJlNmn5",
|
"KFSPuG10fne9vouOcfR+Q5QxnfItxWHSFWtnLfKtxogwqumVK8oXo5HmaGGl9Z8ClecWCUJj2X6/7XhJ",
|
||||||
"nl5RQ+E0jlmAv8DxUkT4E4IfYTwA/e/7EBwF2ZugI3Fp3J4Bx6I41X7QZndfFvolddVfNTDSzYQ3KPXT",
|
"gWPmsjmQ1foBAAGN6ImilkLeyUvujT7hkVv1OMUCH63hkYrEwM7P5bm5uWmtjJJImU1avqtX1FA4jWMW",
|
||||||
"+achr9AGIs0Jl8h7W8AdqXRrZzlW6fXyIPCI2ft5aNr80oiju2HgNpoN17NWLAcSH1atlVWYUdeCtHvb",
|
"4C9wvBQR/oTgRxgPQP/7PgRHQfYm6EhcGjdnwLEoTnUQNtndl4V+SV31Vy2MdDPhDUr9tPk0mAptINKc",
|
||||||
"va8dUr5apAEDc8VvLVseZOrztCv2h3YIAbOxIFUNVoELKT6OFdE7ctJsTAE+Wxph4wGxWqNp5pppce7J",
|
"cIm8tznckUq3dpdjlV4vD0Kf2N2f9+0OvzTi6G4/9Gr1muc7K44LiQ+rzsoqzKjtQNp9x7unHVK+WqQB",
|
||||||
"8PQ/gpb1xEgrXOxu5WfbmNJocSklDE58M/kJXoVHHG5jqFcpu5bfaTluFSzNCmi4y8QMQk/Qu4roh816",
|
"Q3slaCw7PmTq87Qr9kE7hIDZWJCqBsvAheQfx5LoHYY0G1sg5xZG2HhArNJomr1mO5x7Mjz9j6BlPbHS",
|
||||||
"V/wsC4ZGfyvEHA9R7IPgYorrFLj9NukhIwXKM4F3iLc8T4AbCH9h9CniMu5iGWO0Df48pgBtgCw85rIQ",
|
"Che7W/nZtqY0WlxKCYMTX09+glfhEYfbGOpVyrYTtBquVwYItASU7zKxw74v6F1G9MNmvSt+lgVDo78V",
|
||||||
"FQX6HCTnEXiaRYZJ5mLSlKmVBh4qwyQkOUPViJlkD+nvUCYrckpDhccPknboMQg/pkFFm9E2PcSbA/gp",
|
"Yo6HKPZBcDHFdQrcfpv0kJEC5ZnAO8RbnifADYS/MPoUcRl3sYwx2gZ/HlOANkAWHnNZiIoCfQ6S8wg8",
|
||||||
"vnaMqcuvGdHj6auy3wmtiqz5MKziAylHhK3GqEWgrypucSpqmZQCJgdDerV8ihVySnslnYFc2fSudEpU",
|
"zSLDJHMxacrUCgMPpWESkpyhcsRMsof0dyiTFYbSUOHxg6QdegzCj2lQ0Wa0TQ/x5gB+iq8da+rya1b0",
|
||||||
"GbXU7xUsUORPM0OQeCvrlR613bDrEN9v2SHWXJf8IkU8NqP4fenRcld4o+vrNFO/xsz5zCYXTKcOV0O1",
|
"ePqq7HdCqyJrPgzL+ECK4WzLMWoeYq0KupyKWialgMnBkF4tn2KFnNJeSWfAKJvelU6JKqOWet2cBYr8",
|
||||||
"MHi+pyHJo86/mbKwkgD0mSA9Zx/I/4oxcDWxfhYZVnJSq734jCjOccGgQp7kDIuzHp9ljvSMM+OU0e2R",
|
"aWYIEn9lvdSjHa/fdkkQNDp9rLku+EWKeGxG8fvSoxlX+H470GmmQYWZ85lNzplOFa6GcmFws6chyaM2",
|
||||||
"gBzI7lBvySJKAbcS6nF9f9nskpavFEG82Syp5ui5S5ZNWlDP58m6FpSnZ4E8JLuLHR2bo/RqMvFXSU9R",
|
"30xZWEkA+kxgqrMPmL9iDFxOrJ9FhhWc1HIvPiMEdVwwqJAnOcPirMdnmcNU48w4ZXR7JCAHsjvUXXKI",
|
||||||
"I7umdw+q8FZWwURZ95ntmFtwt2Z6lonLjfMmvSUY4KMwB+nuPlnSrGQJCG/5tunkZFtmNwFSxW6SyoB5",
|
"UsCthHq8IFi226QRKEUQb9YLqjm63pLTIQ2o5/NlXQvK07NAHpLdxY5Oh6P0ajLxV0lXUSPbtn8XqvBW",
|
||||||
"mRDhFscl2xF9cMDOF5Cqz3g6OnfXjOgR4BiUwe4VgGqMjWiRv+yxkAIvZuFl7USqY3/ol80MLgCz4/CC",
|
"VsFEWQ+Y7WgsuFuzfcfG5cZ5k/4SDPBx34B0d48saVayBIR3go7tGrIts5sAqWI3SGnAvEyIcCuGsubZ",
|
||||||
"mluRfVu1YDW2nvOQNTCcnlO/WQB99y+i4uNRXE7BCCsiQFiktBzadjP+LPSJ1/oodAMz9UXgui3b9FZI",
|
"GmDnC0jVZzwdnbtrRvQIcAyKYPdyQDXGRrQwL3sspMCLWXhRL5Ty2B/6ZTODC8DsOLyg5lZk35YtWI2t",
|
||||||
"8lGMZyf93ElyQvi6q+hQ56nlCkK/Mso/TrNVgPw1nrcl2ZnM2MnEpC2SllqOkZFWawRLCYa5W8yb+aBJ",
|
"ZxOyBobTDfWbOdB3/yIqPh7F5RSMsCIChEVKy/1Opx7/rR8Qv/Fx3wvt1Beh5zU6tr9Ckj/FeHbSz90k",
|
||||||
"bq/Hb4YykIcxebPcD551mL925UoJ3lomyVET1+eY0tEWz5mNgxsgXD4HQ/MYM/FFisAwA9o0NyHI+QKm",
|
"J4Svu4wOdZ5arrAflG5RgNNs5CB/jedtSXYmM3YyMWmLpKUWY2Sk1RrBUoJh7uTzphk0yet2+c1QBPIw",
|
||||||
"EZ46BRFet7EpxbJnOfzPyyVsU8Yr71q2Lq0lDFbdUvBSFP3gPEDulFir9CyOJSKXrKDVJX1kiHItbckK",
|
"Jm8W+8GzDvPXrlwpwFvLJDlq4vocUzra4jmzcXADhMvnYGgeYya+SBEYZkCb5iYEOZ/DNMJTpyDC6zY2",
|
||||||
"PB5VKH+Y74fMcqWT77hd0qn25FlQtoRgbdlkDYvzhXYSNw3okcDsmoGJCe4rxOt7lhM0mmxCd5tV2gSJ",
|
"pVh2HZd/vFzANkW88q7T0aW19MNVrxC8FEU/OA+QOyXWKjyLY4nIJSdstEkPGaJYS1tyQp9HFYof5vsh",
|
||||||
"d7jLrXMJ4JSLpZzg4g31pbFxOJQWatc69AzAdONOZpAPSn4PHcLF+N2f6TPwFUCWVjr/6sSgB0xLgY6F",
|
"s1zh5Ftem7TKPXkWlC0hWBsdsobF+UI7iZsGdElot+3QxgT3FeL3fMcNa3U2oTv1Mj2OxDu85ca5BHDK",
|
||||||
"oLbsRlvRU5AukAouwU9FWwAKNzYt44UIrJ8zriTxqCWrGAl/efEqdKhZT8+0FsGhlZ1BKJGwclfnDhIj",
|
"xVJMcPGG6tLYOBxKA7VrHXoGYLpxJzPIByW/hw7hYvzuz/QZ+AogSyudf3Vi0QOmpUC7RVBbdqOt6ClI",
|
||||||
"tkjXCvKUO+E/buVGevqu71tLNmnFZyAPC6lDrDXSbS2t84Bqhe4iWNh9zrofRM9pVZdh7AIZa5rVolPp",
|
"F0gFl+Cnoi0AhRublvFCBNbPGVeSeNSSVYyEvzx/FTrUrKdnWovg0NLOIJRIWLmrcweJERuk7YQm5U74",
|
||||||
"pos8kISRz6rpUucsTee9Cwo0gm+Saz/H5Wyo0WZjKpX1dMr9fsz8GNFDdkyaRqqCkO4ayvKnx+jsUIgQ",
|
"jxvGSE/PCwJnqUMa8RkwYSG1iLNG2o2ldR5QLdFdBAu7z1n3g+g5jfIyjF0gY02zXHQq3TGSB5Iw8lk2",
|
||||||
"O46GW9QRgEfxow04xXGwWJIFuwL+Gpp50iFX2zGsWwfkR4XYpKTGqjpDUygdUuBSPcvZq1ph/MyBjWWW",
|
"Xeqcpem8d0GORvBNcu0bXM6WGm22plJZT6fc78fMjxE9ZMekbqUqCOmupSx/eozODrkIseNouHkdAXgU",
|
||||||
"uK8TxUA9YrJ+kekThwX60nWblkI6qa25kvKu3Ox1r5VQGl7Kno30CVXFUSrRTdrPhMeKFcU6qqoltfNV",
|
"P9qAUxwHiyVZsCvgr6ETKR1ytR3DulVAfpSITUpqrKoz1IXSIQUu1bOcvaoVxs8c2Fhmifs6UQzUIybr",
|
||||||
"oQxL90y+Z7Mkd4L77hJvZvnz1R8d0xFayfaK3Z+lb79od2ixN3QcZyjsa05qnKyOVFVCqgnwSh5CmJlH",
|
"F5kmd1igL123aSmkk9qaK8l05Wave62E0vBS9mykT6gqjlKJbtJ+JjyWryhWUVUtqZ2vCmVYumfMns2C",
|
||||||
"IDsnvy60xNou8J2PZ4iXhkXrabWW53IvsIjHNoXPZf0mO6JPbzmfa06vaGZyZEQ9JrSdUDI9jtCBgRnZ",
|
"3Anuu0u8mcXPl390TEdoKdsrdn8Wvv2i3aH53tBxnKGwr4bUOFkdKauElBPgpTyEMDOfQHaOuS60wNrO",
|
||||||
"WMWbUu85XgvTgJpS1R/00os+Ywq8NoaeAOnE2ReaPhVQCMndfloPumw3ppeFAAgHbHZKywy5AvXQmKJ7",
|
"8Z2PZ4gXhkWrabVmcrnnWMRjm8Lnsn6THdGnt5zPNadXNDM5MqIeE9pOKJkeR+jAwIxsrOJNqfccr4Vp",
|
||||||
"0RZ9xpYsEBxAG/sEyvoALEZDhOnx7ZEClovBd1RYp/TWje2xK2O+n4eu7gaCBvJjKfM98wH6Hcf5UZ94",
|
"QHWp6g966UHHw0faGHoCpBNnX2j6VEAhJHf7aT3ost2YXhYCIByw2SktM+QK1ENriu5FW/QZW7JAcABt",
|
||||||
"0OqoVfa+8lsA6rnGeTe8d8xfpYgrz1qZQc7Ksi/N0C1/p24VQ6l2ScfyUwAsZr/vuWsk9v7qQUDRWXy+",
|
"7BMo6wOwGA0Rpse3R3JYLgbfUWGd0ls3tseuiPl+3vd0NxB0vx9Lme/a99HvOM6PesSHVkeNovcV3wJQ",
|
||||||
"IHOBLxCi/L62YPQb1bjhWcEA4JXNO90FUNFR9EiyMiq4T/XNLiBVQVCsnOZ6wZzVOXMw1CpiR4pO+zq3",
|
"zzXOu+G9Y/4qRVx51soMDCvLvjRDN/NO3cyHUm2TlhOkAFjsXs/31kjs/dWDgKKz+HxB5hxfIET5A23B",
|
||||||
"Q0UnlD6ZUGcDQZ5dPnTEGcP13FgnQWA5K76OSwEq6pzG6KuWNRi0GldG6hrzpsRDjtRIrbepIWP5ZsQq",
|
"6DeqccOzggHAK5t3ugugoqPokWRllHCf6ptdQKqCoFgxzfWCOatzGjDUSmJHcnAtrduhpBNKn0yos4Eg",
|
||||||
"aeoQ/itkecG5Os7HOtuBLNEcz0a0BeWgh1C5wc7tUxWB56nw+gGOCH3OPpT9H7vRdqocYDZdCkZPpmcy",
|
"z84MHXHGcD031kkYOu5KoONSgIo6pzH6qmUNBq3GlZG6Jr0p8WCQGqn11jVkLN6MWCVNHcJ/hSwvOFfH",
|
||||||
"EBFabsrpCkqf0SHdzy5hkLeEs7lOSxmzCG86n0nzFpWlHCTzVdhPeXl1c35R/kYaU1vP8WMxeKZOQHYp",
|
"ZqyzHcgSNXg2oi0oBz2Eyg12bp+qCDxPhdcPcEToc/ZH2f+xG22nygFm06Vg9GR6JgMRoeUmQ1dQ+owO",
|
||||||
"cVgd/gcWELQ4qnpT3JBsc61e3/UCfA7/L33ciptly4opL8lQo6v3rWC165n3nbjZpD7zQwSuvg9tfT80",
|
"6X52CQPTEs7mOi1kzDy8aTOTmhaVpRwk85XYT3l5VXN+Xv5GGlNbz/FjMXimTkB2KXFYHf4BCwgaHFW9",
|
||||||
"18hbedVcgbXGk5rLsaJM217ihrsGrewAETzpMaaeY+BUm/UoqqUGvMgL9PVn/OgO2GlFoILPEVoIUvYP",
|
"Lm5ItrlOt+f5IT6H/5b+3IgbZ8uKKS/JUKOr95xwte3b99y42aQ+80MErr4PbX0/stfIW6ZqrtBZ40nN",
|
||||||
"wR45oKOmwU4EfQZGCYIRZeMJdKTP7O25bjVc5rGwpycEHyPNoZnsFF+E3BVJ7Eve5ucCdo+1/RVp9zBn",
|
"xVhRdqezxA13DVrZASJ40mNMPcfAqTbrUVRLDXiRF+jrz/jRHbDTikAFnyO0EKTsH4I9ckBHdYudCPoM",
|
||||||
"Eu8S0l3SJqG7YdBx1dwsRjOboBuVg6jfs/otYnr2eoKcXoQ9OAagez70rphZHmFFDysdyswQTNIRIvVJ",
|
"jBIEI8rGE+hIn9nb9bxyuMxjYU9PCD5GmkM92Sm+CLkrktgX0+YbAbvH2v6StHtomMS7hLSXtEnoXj9s",
|
||||||
"2dX7dGD8OO4Gmbn1ltYByVjvix8a2Dv/SB0dkKN25S77R5C5PIyt3Tg7Z2rtsjFrXHt/0Zg1+IF7QUdY",
|
"eWpuFqNZh6AblYOo33V6DWL7nfUEOT0Pe3AMQHcz9K6YmYmwooeVDmVmCCbpCJH6pOzqfTqwfhx3g8zc",
|
||||||
"DhN9zgac5qlRcf53jKnKNHc8hsdVUUuBSGw5cbevrAEBm9qC/S0JR0kJvNa9JHiVUXlQUojKH+Py3Jzs",
|
"ekvrgGSs98UPLeydf6SODshRu3KX/SPIXB7G1m6cnTO1dtmata59sGjNWvzAvaAjLIeJPmcDTvPUqDj/",
|
||||||
"H9jNvwvlF/gBcVrBqueGK6vVJwZFlDn4dvw8VwGv4sPo55ElmUwQ6T3NhJtyOVjenKyNc96tmRidYVWt",
|
"O8ZUZZo7HsPjsqilQCS2nLjbV9aAgE1twP4WhKOkBF7nbhK8yqg8KClE5Y91eW5O9g/smu9C+QVBSNxG",
|
||||||
"3HrV3F1I0TkZpnkemudR92daqbssScLKpygWn+VtaLRT+aAfaCGPrI7WeP894hWN4MQcg5i5aQbEs0zb",
|
"uOp7/ZXV8hODIkoDvh0/z2XAq/gw+nlkSSYTRHpPPeEmIwfLm5O1cc67NROjM6yqYaxXNe5Cis7JMPXz",
|
||||||
"WFzvLbmAXZYh/D2yrkJYJcCJxUKX/bCJk7lbuAI/9xazxmhyKFFEI5HYTVfPWJlWvfFMxVvyVrsI7UQu",
|
"0NxE3Z9ppe6yJAlLn6JYfBa3odFO5cNeqIU8clpa4/33iFc0ghNzDGLmhh0S37E71uJ6d8kD7LIM4e+S",
|
||||||
"5MaGDiXdM6uq6W4oIoiSamVTCivLIT201oCWqz0rILd5wP36KtGpFj3LxzDpGNspD3uT/149dZc1LQu0",
|
"dRXCKgFOzBe67Id1nMyd3BUExlvMGaPJoUQRjURiN101Y2Va9cYzFW8xrXYR2olcyI0NHUraZ1ZV091Q",
|
||||||
"hZlfQgw8ri3HRt6JrsvUgaSQ+xg7zjE7NgHXFACKzED/y+f/bvxk7j8p7m3sIw13WwJndyQD/ypZyaHT",
|
"RBAl1cqmEFaWQ3porQEtV/tOSG7xgPv1VaJTLbpOgGHSMbZTHvYG/7166i5rWhZoCzO/hBh4XFuOjbwT",
|
||||||
"bQWe1c+5oMweSWMRyLGWiujS4HwS0NLSO+UXNOWtuVtpd3Odo2cH5Yh/WTqDmBHO0UshaaOgqTM7VLEF",
|
"XZepA0kh9zF2nGN2bAKuKQAUmYH+l8//3frJ3H9S3NvYRxrutgTO7kgG/lWykvtuuxH6Ts9wQdldksYi",
|
||||||
"wbKBbX0e/QYgBE6jbZ1/g2OKnkLNLy/3i8FbB/TgqtHmnQLaSTqKwkEnUg+FY8RGwuK/F0L1a4yDIFiQ",
|
"kGMtJdGlwfkkoKWld8ovqMtbc6fU7hqdo2cH5Yh/WTiDmBHO0UshaaOgqTM7VLEFwbKBbX0e/QYgBE6j",
|
||||||
"RvNHhUuldNq4Wnugr/lzuiQHmlIk5pymekREWxngIU0KzthBHSkPIp5VvLFFHleFmRbDlRXi6+9jmZnP",
|
"bZ1/g2OKnkLNLy/3i8FbB/TgqtXknQKaSTqKwkEnUg+FY8RGwuK/F0L1q42DIJiTRvNHhUuldNq4Wnug",
|
||||||
"zLwg0juhZwXri0yscdOCmB7xroW4N/jXu0K6/5cPb0OdPlbJ8G+T3VgNgn7jIRvY4rij2Z0A2o8AVIvt",
|
"r/lz28QATSkSc05TPSKirQzwkCYFZ+ygjpQHEc8q3tg8j6vCTIv9lRUS6O9jmZnPzLwg0lt93wnXF5lY",
|
||||||
"7q13Fm8b1xZuGAtWPzRuhr7VmTHo7xFDgz3FtAtEvH0EYBnbEkaFAi/pmn3rUsftkhXiTN9xEPo8aU8G",
|
"46YFsX3iX+vj3uCnd4V0/y8f3YI6fayS4d8mu7Eahr3aQzaww3FHszsBtB8BqBbb3ZvvLN6yri28by04",
|
||||||
"yFNunzjsMTZfnNj0jAGhz+eMr6CRSBJ0AJwqs2/N9LqIY8HjyI1krmzqEjKlALRkoh5f1ZhvvD4zN/M6",
|
"vb51ox84rRmL/h4xNNhTTLtAxNtHAJaxLWFUKPCSnt1zLrW8Nlkh7vRtF6HPk/ZkgDzl9YjLHmPzxYlN",
|
||||||
"L/0GErMxZ9cuzwKO+KwZdi2QVysE/mH7DEHxG93GfON9yw+uwRO8fwMJiOfnBhKSR2bft3oW46rSB69j",
|
"z1gQ+nzO+AoaiSRBB8CpsnvOTLeNOBY8jlxL5sqmLiFTCkBLJurxVbX52uszczOv89JvIDEbc3bt8izg",
|
||||||
"hsrDuxCC7LuOj5zw2txc3E0Ik6nNPjq+LNeZ/Qcez8EbsbTCmy0B0naAPdJBWkSGBdJ/SgcGfRFtRRtc",
|
"iM/a/bYD8mqFwP/YPkNQ/P12bb72gROE1+AJ3r+BhMQPjIGE5JHZD5yuw7iq8MHrmKHy8A6EIHueGyAn",
|
||||||
"UA0Qd5L3l9C/Jp53EkEB7FPEe55v0H9KBjTA5n2JOadwa07BRkw3ZfDqaJPbyp8gmCzkDwro97tsdHUX",
|
"vDY3F3cTwmRqu4eOL8dzZ/+Bx3PwRiys8GZLgLQdYI90kBaRYYH0n9KBRV9EW9EGF1QDxJ3k/SX0r4nn",
|
||||||
"bewYPptgsedup9zh3P9hb2um+Xul3RWtX07oQR17+78lSHL1asRrbEsBvuBeQxnbnO+/tM18N4s2etX1",
|
"nURQAPsU8Z7na/SfkgEtsHlfYs4p3JpTsBHTdRm8OtrktvInCCYL+YMC+v0OG13dxQ52DJ8VzaRnfRIi",
|
||||||
"g0tdq2Sv33P94G1Lt815d1QeXiQsbYMe85RQBS0Sv2Jy7gm4pBvzjRgmGtzDDZGWF+9q+taYJJdwIkAt",
|
"9HPPCzT7epN9fS3pPH0uyqdR88FFWwqxXDyqkQ6abVJhNLhjPnoswRdBJrCCIlnF7n3JMXOSsgrpFexm",
|
||||||
"sY5BVMzMUR08kRoynZ0+UOA3kzjKM0ZIQ/4d3jVQwnhJo1idiXWSpGct3/yUCBGxKBJQJ32ARWq19vQ+",
|
"iDGZMrcj3mRbCvYFR/aQC9+5c4OjYXCGkPadb2/ezico/MaDLPe2D37YBzrT9r/UuRZNf07oQRV88b8l",
|
||||||
"jr6IHkGIHigyirG4ou06tupbDhJxEu0IH+IzEA+HANHziIOtIaWbRtx0oyLRsTXqrEfinmJ919dQ/RY+",
|
"MPqy266g2p9po1e9ILzUdgr2+j0vCN92dNts0k5MSKGwNMb+O1mcUPyK3XBPIBhRm6/FAOEQGKiJhMx4",
|
||||||
"sCg6qaZI/lp+nwTIXWB3ClPAASoj2hFIylu86GtQD1NjLdlmYg8K8PMR7EnSeSGZUbStYVLeL1ZLLgwV",
|
"V9P6wiS5hBMBqsh1DKKipVYiK1JDpusSBgrwahJBe8YIacm/Qy0DilcvaVTqM7FOku6u5ZufEiEiFkXq",
|
||||||
"z0rQfXlcmor0T5BNU2/S8elXzMLhivvLusRIZtA0RHKWtIJud7GdnTbe/ifeE+OJUDjZnsVBWwQMGaET",
|
"8aQPsEiq157ex9EX0SNIzgCKjGIUtmi7iq36lsODnEQ7wnv8DMTDIYAzPeIwe0jpuhW3WylJdGyKO+uT",
|
||||||
"fx8O2g4osLy3Q6rRg7jfoh2oF+KmTMwKbIDnANC8jwt4Qfd4mp7IzJfXhFfQJsQinmHHPIzczzSaKQ7g",
|
"uJuc6SqGBxZFD90UyV8zd8iArBWmTTDTC0BSoh2Bob3Fy/0G1TA1VhFuJp4AAXs/gj1Jem4kM4q2NUzK",
|
||||||
"dddZJgAL/C23uz6h/cc0hoeqqcOTZP7DseDvYycPD+4fn4cns+cdouvFSswif2aC1JWATbWXEMfXOaEH",
|
"OwVryYVJArMSaKOJS1M5HhNk09SbdHz6FbNtucn2sioxkhk0DY6dJa2g2x1sZKjNtPgT74byRJgabM/i",
|
||||||
"GfdGHUT+97Eu9mZ8haT6f4Ga0JZhPiUgcE2JnKKEqPbsVaPtLPt8rAzQuuEHrmeukEvmCnGCmTtOZgVD",
|
"cD1CxYwwfLMPB20HTBfe1SPV4kPcb9EOVIpxIzZmBTbAc4Dm3scFvKB7PEFT1GSk9Ci4fHahUusEE62Y",
|
||||||
"BcMRZQOvJ9pDr43aVCYNSRpjmab053mldodrAydxlxeUZahPoS67C2mzQzSgVd5C2AKORDsZwSGDJVcS",
|
"/TtTq6c4gFfcZ5kAfC9vee31Ce0/JrA8VNVYnh71H44Ffx+793hax/F5eDJ73iGvIl+JWeTPTJC6EqSt",
|
||||||
"GJdrfrWWnfWbhXB8THbXpwGX49mOo8ByYTH7sdV9iOwP4XdNEFpg5DaljlGKdRZ9YtBnAJA1yjTEi37D",
|
"3uZAZKUTepBxbFVB5H8f62Kvx1dIqvMbqAlNGeBVgoDXFEcqSojqybhqNd3lgI+Vgdi3gtDz7RVyyV4h",
|
||||||
"ewdk59uUrrskp4y3tVA0/Ox99TbMVmI3ZePfKIBMHNa1KV9nQW8zwItjblIzVz3LW+ncK2HxSYjpMsqM",
|
"bjhz282sYKigd6Js4JVke+ivU9sJpcFoYxTblP48r1RtcW3gJO7vg7IM9SnUZXchYXqIrhOVtxCwgmMQ",
|
||||||
"51nhsqILLpM81e33qZgLAOjtcbV8YEBfjLh+ILYiiyCus/udp2pdgKR8JarVRbNRWqWqUyzO8jhKoeEU",
|
"T0ZwyDDZpQTG5YpfrWVn/WYhECOT3dVpwMVIxuMosFxYzD5w2g+R/SHxQpN+kFjiSa8wxTqLPrHoM4BG",
|
||||||
"0xqfnCBp+TtyFSsJ3ZoJaMgRwpYwO4xRo6066J1+CQQ6h4njdQKnNwwKuy8PDJ6IeApBnh2Ob6kD3046",
|
"G2VaIUa/4V0jsvOtS9ddkk3IG5ooGn72vnobZiuxm7Lxb+SAZQ6r2pSvs3DHGcjNMTepblTPTCudeyUs",
|
||||||
"aCP2tiFBb4PHoM1keBqMO3OCF0Ldrtd/jDMbfnHHuIzXvpX00bgbpBqPq8Wh9M90oB7vz87Cf9VO+Wyf",
|
"PgkxXUSZ8TwrXFa0wWViUt1+n4q2AXTiHlfLBxZ0RIkrR2IrMg/cPLvfJlXrAiTlK1GtLpqN0ipVlWJx",
|
||||||
"5xHPf3wuftW6pHh3/ovkG/5KEUV/NRzEJ6Floj9CcgT308YuW2zHIKF516JBc4tadGE/YtprtCESNJ6J",
|
"lkfQcg2nmNb45ARJy99hVKwkXHMmoCE7DJsB7TBGjbaqoHf6JRDiHiYu9wmc3n6Y23d7YPEU1FMI7+1w",
|
||||||
"NoYJP4kCAx16/tl5TSDC18BfGlx0aPs2onvQI5kp37/G4quropX0c1RueG8Cumu0f/rObUPr0m7PGPRP",
|
"ZFMd7HrSOx1R1y0JdB08Bk0mw9Mw7JkTvNDX7Xr1xziz4Rd3jIt47VtJH437gKqR2EocSv9MB+rx/uws",
|
||||||
"aFuKEVGkzv3E4E/OANw7T/jSyMnFjulMVs+RGuI+5Pyd9fTW9qoEnF8rEQ/ktnvcWVyXDBRBorjFCL7s",
|
"/FfulM/2eAb5/INz8avWJbWAY18k3/BXivyJV8NBfBJaJvojpMVwP23sssVGHBKOeyUaNLeoRf/9I6a9",
|
||||||
"7IwYiMSaugXdbeJfmLUidRHQy5eyjup1iRYx5EhqYaogko/OpYfq2nFMRoqkOqRAITi4t3eS3ggHxhUp",
|
"RhsiNeeZaGCZ8JMoLdH1TTg7r4leABXwlyaUBw3/RnQPumMz5fvXWHZ3VQTInqNyw7tS0F2r+dN3blla",
|
||||||
"vXqgs+0HzUwO3DNIOpeKx7jbO5V7FO3QI8xNkrLksqIF8sOUFJsJiZj8rLQLvk81yY86nv89RhcuiNNl",
|
"l3ZzxqJ/QttSjIgide4nFn9yBoD+eaqfRk4utmx3snqO1Ar5IefvrKe3slclbRkMoVSp4SJ3FlclA0WQ",
|
||||||
"dYzXp2n61Iwbli/ifR8zqSbD/V+m8+C0nC3l0u1C0GfI7AhISMFMiEMIBgFIgWiSDd0zJeVC0XY4LtSQ",
|
"KG4ugy87OyOGIqWqakF3iwQXZq1I/SP08qWol35VokUMOZKa1ypY9KNz6aG6RiyTkSKp3jgAAQDu7Z2k",
|
||||||
"7gLlPpcbkewqNJ6549CvsnkQU+ySjx6prfmE6yJzwOLsOPRj7EK/Zd7yajqxm8QlH3csCp0Y+L2tcwHz",
|
"K8aBdUVKrB/obPtBPZP9+Awi8lLZIHd7p7LOoh16hFlpUn5kVrRAZqCSXDUhEWPOR7zg+1ST9qrj+d9j",
|
||||||
"JLfsqbyIEyFl2GnTErj+l7R25p0lJOLW56mVQ7rqEYmbTkH6oh4y7yXHLY626AG45jfGvTzK4r3MrPtF",
|
"dOGCOF1Wx3hloqZD0bhh+TzeDzCHbjLc/2U6A1LL2VIW5S4EfYbMjoDUE8yEOIRgEMBTiPbo0DdVUi4U",
|
||||||
"3ORwvHMkF2m+a9kBZAp/rE1UScC34v0sLczSjwSwXnUMpEXBymTSSJX2P6wcrhSsVsUMLingWI9jXIlA",
|
"bYcjgg3pLlDuc7kFza5C45nbLv0qmwcxxS756JHalFG4LjIHLM6LRD/GLnTa5s3OphO7SVzyca+qvhtD",
|
||||||
"5zceHie2yb+aBQCUOGgxHuu+4wRWsF7iFC5F/uN5G9zrvy33jeJ2VrxMdIo8AlVnJLdLhl9JdfRp9B2l",
|
"/jd1LmCe3pg9lRdxIqTcSm1aAtf/kqbevKeIRNzqPLVySFc9InG7MUhc1YMlvuSI1dEWPQDX/Ma4l0dR",
|
||||||
"n3O+Exm3e6Jx+lcYn0c8Nw0L8yTBulQaAWT6RA+OpAdKOCfzznok8NbPy8I5yU+Bt44UvNHjvXFf8U5J",
|
"vJeZdb+I21uOd47k8tx3nU4IOeIPtIkqCexavJ+FJXn6kQDQrYqBtPhnmUwaCWPhh5XDlQJUK5nBVXFu",
|
||||||
"PTEN6YIc1nfncgAZCdU/6Yae9EdSiv/PtoWSizCH+uz75HKd3Mm89Sq9exo8nWJrBLNemRa6h2DGSTC5",
|
"39dqBNrccnqc2Cb/ahagb+KgxXis+44bOuF6gVO4EPOR521wr/+23DGM21nxMtEp8ghUnZHcKBt+JSEo",
|
||||||
"lrjE1zFXxYgjMv7QSKk60ebRFW2+T5TcYI0zfANTOoDJDwQKCY9fj+hxtDVjtD9qGyg9OFcq3xtC6g+x",
|
"pHGXlE7eZicybvdE4/SvMD6PSH4aFuZJglWpNALC9okeFksPkXFO5pWSic/BwuY8ZKTg+13eFfkV75SS",
|
||||||
"OIe7H7cwZRPrfuAe4QlDmqA4qHUw1YxSp9OFPlJUn+IGGHn6FLS9aFZkGtH/4gdW7sBYgJG1ukqlxWiB",
|
"TpxckMPq7lwOHST1c0j64CedsRTYh7NtoeQiNFCffZ9crpM7mTdfpXdPg6SUb41g1ivTQvcQxjoJJlcS",
|
||||||
"4v+axF128JKkWlHrkGv6fkMHwL2A4PEcg+JwmESWkpzSZAiEg33VZDV+9CNMM0S0wAHd/9GPrsYqzSkf",
|
"l/g65qoYa0ZGnhop9UbaPLq8zQ+IkhuscYZvYEoHMPmBwJ/h8esRPY62Zqzmx00LpQfnSuV7S0j9IZZl",
|
||||||
"AGzhXYDj24PUJZ7/GG3RF5AeD/75OObDbN4/4LPcsXASbcg9L79gs5PTTtrsvLYV7EJwdwOKiKizO2FE",
|
"cffjFqZsYsUX3CM8YUgTFAe1DqaaUep0utDHiuqT3/rEpE9Bw5N6SaYRnU9+YIUujAUYWcurVFp0HoB9",
|
||||||
"y89kYhs+IfGdvGAs4X255gl0YYl5OfxZiKEnSmJTTdnictvSfGSj0oodENCaVCZd0lC8s1VThqIv6lnu",
|
"qEjcZQcvSKoVVS5G0/cbOgDuBeyW5xgUh8MkspTklCZLYFvsqyar9aMfYZoh4kQO6P6PfnQ1VmlO+QBg",
|
||||||
"n5TUoFxgruzNJB3e8Ux1n3gl1s4fxZGXvLypHK74EEF1nej0Lk6QgXfVIaQfKxfgEX3OlT7eKQVv5P3E",
|
"C+8CEOMepC7x/Mdoi76A9Hjwz8cxH2bz/gGf5Y6Fk2hD7nb6BZudnHbSZOe1qaBWgrsb8GNEheUJI5o5",
|
||||||
"+MEXHaCllEsQdv6/ESocR1F5jhM7AC/jLrzmuTJXzLvGipt9ZmKdohpA9xMV8VDYZOnpIz/G0S5grhno",
|
"k4lt+ITEd/KCsYT35Yon0IYlmnL4s+BST5TEpoqyxeWGtWZMq8JaLRDQmlQmXdJQvLNlU4aiL6pZ7p+U",
|
||||||
"bww7oXV+JY2rJqb0dc8iNSZwD2plxtf0Gd/twzgh4GXenj6ZSDI1tPJqGuIaa2r4slr9XyJN2CxIcElu",
|
"1CAjJFv2ZpIO73imekD8Amvnj+LIS17eVA5XfIigrlL0+BcnyMK76hDSj5UL8Ig+50of75GDN/J+Yvzg",
|
||||||
"znv2I6i/eL/i6f3gFGYq3pHkngTIrPha1ty46D9Q79yrHJgPr+xNgRCUCwS4DxbwMNqIPkucElnt8hYj",
|
"iw7QUjIShJ3/b4QKx/FznuPEDsDLuAuvea7MFfOuseJmn5lYp6gGQGHYIwkSdj/2zhykSsbiaBcw1wx0",
|
||||||
"BepBnBoTZLZs72Qd031ZSLB6rqhUvzh5/GJWspfCXnIn5aWPXeOYGZM7tvACHfV+m/SWAUgJKaJXjyOw",
|
"toad0Dq/kpZlE1P62meRGhO4B7Uy42v6jO/2YZwQ8NK0p08mkkwNTdzqlrjG6hq+LFf5mUgTNgsSXpLb",
|
||||||
"+AUJ9TpmYNruyhluOMkdIpO+3581na7nWt3ZrnvfYXbd7Me8rjt/N97mj17Dn17r38uxo3gBJjeBRL14",
|
"Mp/9COov3q94ej84hZmKdyS5JwEsLb6WNTcu+g/UO/cqh2TEK3tTYEMZISD3wQIeRhvRZ4lTIqtd3mSk",
|
||||||
"WhbLVhFPMWrMN/7HnTvdv7lzZ0b6Z+qS1+H/nf7Pf63xZY9n3aw53Rm++pm+2bkHpQVeZ9VaIyrXxC70",
|
"QD2IU2OCzJbtmq1jui9zCVbNFZXqFCiPn89KnaV+N7mTTOlj1zhayuSOLbxAR73fJl2FAExEiuhV4wjM",
|
||||||
"JcsxwahLvzjDM9cW/r4Gxri28PcqetkUPYh+A9Di3NpImvArlRTDpoitozHwImnbb9wynRUSd7vew3Io",
|
"f0FCvZYd2h1v5Qw3nOQOkUnf683abtv3nPZs27vnMrtu9gGv6Dfvxtv80Wv402u9uwY7ihdgchNIIAWk",
|
||||||
"gBscKbpUv586pxKz2KZIi8gz9BMTCDpEHxm8I/cAAn5iLj+1AmIavPIKwcqFooPWz8hIr1gqsBAYZgaq",
|
"ZbFsFfEUo9p87X/cvt3+m9u3Z6T/TV3yW/yf0//5rzW+7PGsmzW3PcNXP9OzW3ehtMBvrTprROWa2IW+",
|
||||||
"MkmBl8ilj7ZEbD4JZ42ip3ccDOyhIQWtnCQYRoMe4LxmDPpV/Jq4+mgovhaaEZxV3kmcnV62aVwlesMw",
|
"5Lg2GHXpF2d45trC35+dMTgyRW3+l3dkNrm28Pcqit0UPYh+AxDz3PYQ9QsDta5iWBeRdjQNXvCW5UyF",
|
||||||
"+/0Z7PVRGhVkMg7J+z5Sd5KyDl90i9jE9EmebSNpkiP6UlpoTdZ5Mn60A959wenRjsFneEmzfzvGFBQ/",
|
"uWm7KyTuer6HxVEAOzlSNKteL3VqJdbp2CJJwmT2JwYRdAo/snhn9gGE/8RcfuqExLZ4HRaC1gu1B22h",
|
||||||
"SIQ/obuCl0RIZV85EcWcjW29y68g0StjcvuCb9B7CLOtwepRD7UDT/DSkajNL/9iIBB4/ho+OOn7fxHp",
|
"kZVesVRuIbDsLFRsknIvkVkfbYlIfRLcGkVPb7sY5kOzClp6SXCcFj3Aec1Y9Kv4NXEt0lB8LfQkOLm8",
|
||||||
"kpvOK93UzBLT9WvbMaaEQOOVYpi/cMxryaNN4clR7/npmjWJ+ud3QTwRuP1LCWpsCV/cdvu3BdprBVcu",
|
"ozw7y2zTuIL0hmX3ejPY86UwRsgkHpL3A6TuJCUfvugm6RA7ICZLR9IrR/SltNDKGV19W7QDnn/B99GO",
|
||||||
"oC0rftiY4pfnmkkz7itzxYjVE3WkJk3odSwol5nB1SqgWw6jLcihGAr0X+5H5LBnNXlUTyFPcAdQOTBq",
|
"xed7SbObO9YUFEZI23BCdwVniXDLvnI+8vkcm70XX0+ig8rkdgnfoPceZhvGVaM6agee4IUkUZsrBvnw",
|
||||||
"K+aTy268woLbBzhN1AF4Hhu3w6JPCjzCE2W/MFidtd0Vy5HjVWkbig7pM2OqzbuXMI6YN+6Tpfa0mPVW",
|
"MPD8NXxw0rrBItLFmOor3eLMStN18duxpoR441VkmNtwzOvMo03h5VF1gOmKtYzq53dBPBF6vUsJlnAB",
|
||||||
"XN7kkWWP+KuXEnXGoLtGx3XvWcRo961+2PKC9h1nqv1eEPQ/cOz1q8Yi6YQeuWosmj2yaAXk7xYDz+oE",
|
"X9zyercEBnAJNy9gcCs+2pjil+fqSYv2K3P5OOYTdbLCgowsKJegwUUrAH0Ooy3IrxgKTGjuY+RgeBV5",
|
||||||
"V40FM1j9O3mq7emmuB/jqlMk+9Bo81e3AvceAQ8MU2K4WshNvziYIldtSd6AAQdIw6RmxdhmG6n1BMmG",
|
"W08hh3AHEDswoivmY2Q3Xn3BbQecJmoEPMeN22jRJzne4omyXz9cne14K44rx7LS9hUd0mfWVJP3tGEc",
|
||||||
"JV7AKFVkH7aAHImx8O847TfmLhtsTTO81KTV8UiXOIFl2pBZ/Q3P99pG1AAF5v0U2HEbiQxyS65K4/lZ",
|
"MW/dI0vNaTHrrbj0ySfLPglWLyXKjUV3rZbn3XWI1ew5vX7DD5u33anme2HY+9DtrF+1FtlNSa5ai3aX",
|
||||||
"P8HhA9dt9UxnvcUMjF4/8LU62PvAA5PxS8HYr8olFQartxlD6FOy/wTndhh9JvaQb9jZZQaHWGvM//Ku",
|
"LDoh+bvF0Hda4VVrwQ5X/06eanO6Lu7HuCIVyT60mvzVjdC7S8A7w1QariRyszAOtMgVXZKnYMBh8zDh",
|
||||||
"IkG+5Jygj+olbPhU1tfCII3awc+rixVP+gDz+/h9JQf2t8L/k3bzgA+3mTm5BgdTqhHn5MtoWz4lsQcY",
|
"WTHEBTBSxkskG514AaNUkf3bAo4k7pBw222+MXfZYmua4WUojZZP2sQNHbsDWdff8FywbUQUUMD/T4Ed",
|
||||||
"02IOeJ78S8VXVZVIl0zbLiPUNduuRqsvz+oyG9ZV65SQijcH5ZcHpv4jRgRHOhtEn5QTiYvMfNl/SyPO",
|
"t5HIILfkijWeu/UTHD70vEbXdtcbzPjo9sJAq5F9ADwwGZ8VjP2q3FX9cPUWYwh9uvaf4NwOo8/EHvIN",
|
||||||
"n8GqvpCrsTNcMgU+/WfTsZwFswWvtIExxWU4oPFB3v0GplrGb7njIO4AdxjHTYTjezYhOZe/R1fjMOGp",
|
"q17r+5Jzgj7il7DhU1lf64dpRA9+Xj2shtIHnz/A70s5t78VvqG0Cwj8u/XMybU40FKFGChfRtvyKYm9",
|
||||||
"lO3Bfj8UybN70f+MdpQEMznegBbvbrQZPYVNpkfRE/Zf2TDf1clOTiQuZvTaGFJIyjJESpWDdNUvjvl0",
|
"w5gyc8Bz6F8qfqyyRLpkdzpFhLrW6ZSj1ZdndacNq6qDSkjFW8byywPLAhA/guPfDaJPionERaZZ9t/U",
|
||||||
"C6tiLkwAf8XPzY6QgYMUweuXxH+ETRfITVmFBQFHcw9Ox12TI4k5biDI4Dtk0suIPuXWOjaiiwPSbRip",
|
"iPNnsKov5ErtDJdMgb//2XQsZ8FswSttYE1xGQ4YjZCTv4FpmPFbbruIScCdyXFr6fieTUjO5e/R1TiE",
|
||||||
"FXp2W+nizQ5IjCcyAk0XRHFcbgbn/IAO5iGVnnsIsGeGIeWlHRnttTbGuBLo4GM6mL56x5GxMpXUSx7f",
|
"eCplgrDfD0Vi7V70P6MdJflMjkWg/bsbbUZPYZPpUfSE/VM203d1spMTiYsZvTaGFJIyEJFSxQBe1Ytj",
|
||||||
"a/vWrwjTQkR6FtBMzNxZsZwHssPhv1261ukQ+9It0rU80glyvDzXXURVP6vmmpNhslboWK2YpuJbK/UM",
|
"Pt3cipkLE8Bf8XOzI2TgIEXw6iXxH2HTBapTVmFBGFrjwWl5a3KU0eAUguy+Qya9rOhTbq1je8I4WN2E",
|
||||||
"8yuit7Jen5trCjzsN99osj8vvzY3d/dMJpbVM1fI7H2y1D+3w5b+C8hfgFtlXFT/gftaYdI0q39Ilhb0",
|
"kRp9v9NUeruzAxJjjYxA0wVRHJeiwTk/oIN5SLPnHgLspGJJOWtHVnOtifGvBFD6mA6mr952ZQRVJS2T",
|
||||||
"Jo506Mgae3GBjfKNKvNFG+1NaE4RFx8jlNo2JGLtGFPw/CPsn2F1pwUAjpy3m2Tg7sEd8YLuRduaem/X",
|
"x/6agfMrwrQQkboFNBMzd1cc977scPhvl661WqRz6SZpOz5phQafz3UPsfbPqrkask/Wcp2uJVNYAmel",
|
||||||
"D97BOU5GaMPgb72KTNvkzQVZfNDXTyCX1WNwpxoBwaYhPnSONW1MMUFmXJmbk9mpb5vr2AhH5ifsLJPn",
|
"mmF+RfRW1utzc3WBkv7mG3X28fJrc3N3zmRiOV17hczeI0u9cztz6b+A/AUQXsZF1R+4rxUmTbP6R2Rp",
|
||||||
"7rw50aKUmySXhCfRrwGhoBb35r/F2vOoKM6ekKpHCmHsOCK50Q6tVt8jy37bQEQp5ZxEmyoUlYj0H9HR",
|
"QW/iSIeOrLEX59go36gyXzRX34SWJXFhMsKsbUOS1o41Bc8/wq4qTntagOPIOb1Jdu4e3BEv6F60rakF",
|
||||||
"1VRZkZJCfcpHx0d4rkl+qBlzHW6SiaZS3CSvyGrNYZG8DIq6OSc3b2IHdOWDphaWbgSgZcw2+DUiLiuK",
|
"94LwHZzjZIQ2DP7Wq8jCTd6ck+EH3R4Fqlk1BneqPRRsGqKGG6xpa4oJMuvK3JzMTr2OvY7tkWR+wn5D",
|
||||||
"FDCXcgJnl1w38APP7F+SXB8FMv7f5JwjzBhs90I/aHVWTWeFZBwocmLEHniJeMyL6TKISj5IwM9OMNso",
|
"JnfnjYkWrNwgRhKeRL8G9IJK3Jv/FmvPo7wYfEKqLsmFuOM49Vaz7zR6PlkOmhaiTSnnJNpUYapEFsAR",
|
||||||
"sTif05OZO07Sr4HjCGdMwFTKEodd2422hb4kQpUlmq62YI4EbwkqXZeINBmu173q++q3wZxnLjwUSbIf",
|
"HV1NlRwp6dWnfHR8hOehmMPQmAdxg0w0zeIGeUVWq4FFTNkVVXOOMadiB3Tlg7oWsm4EgGbMNvg14nAr",
|
||||||
"A4KA3d2sQPr64Fp5no6o8N+kBxmITtkJmUrfSZLT4hxurtOUHCXe9awQhuht8cwPGoP8FjE7AelCpKBq",
|
"ihQwl3ICZ5c8LwxC3+5dklwfOTL+3+R8JMwmbHb7QdhordruCsk4UOSkiT3wEvEIGNNlEKt+kACjnWAm",
|
||||||
"unUd2/s7MKLkjoOl6PEd17ZJB+aU3q9Vyw9crO7JS0W9bhPTe48/V8k/9bXkmTupq0xvJIFK8D+wiuYp",
|
"UmJxPqcnM7fdpIsHxxjOmICpdCYOybYbbQt9SQQuCzRdbTEdCd8SVLouEWkyXK971ffVb4P50Fx4KJJk",
|
||||||
"RNNkfZojOys6M7ZuQGyFuK1DKuFCoVM+wF1Cix8w+/JFVC8UkEheE8KstIc7ueo0fXlODi8VR+//f1l0",
|
"PwYLAbu7XoL01UG58hweUf2/SQ8y8J2yEzKV2pMkrsX53VynKThKvBdeLkTR2+KZHzQ++U1it0LShkhB",
|
||||||
"1i3815qkkOssuaYHbXOZ1hVqNmrRXCMfJI9NRu9IXnB91bUENmCZsvFGTp2VDMV0UhdkfVzeySt/k+4B",
|
"2VTsKrb3d2BEyX0oC3sKtLxOh7RgTun9WnWC0MPKH1Oa6vUOsf33+HOl/FNfS565k6pK+EYS4AT/gBU2",
|
||||||
"J/SYR/wgwxbaDUzF4UW42xVNgOP0iC075WDuikS8b65l73glxViPSNwJPY84QUs82kawjR2mj8QB5eOm",
|
"TyGaJuvTHPVZ0ZmxoQfiLsTNPlLpFwqdzOB3CS1+wOzLF1G+iEAieUXos9Ie7hjVafrynBxeKI4++P+y",
|
||||||
"QQ8wQJmEzx+DeqSo08kgU3Eck7cVVQOjPN0sCWFNJ3pzqap8VVW6oewvE81g49ejUF+HtSn5yRMo31Fe",
|
"6Kxb+K8VSSHPXfJsH5opM62rr9moRXuNfJg8Nhm9I3nB9VXPEbiBRcrGG4YaLBmm6aQqOPu49JNXBSed",
|
||||||
"8v3UoBXrKPYPM7PyohTmb1UbU582rdF1hUfnkh/wnlh5V8wCf3IxmDDOiPqiPDREpe3F1Uxbl1M4nVA7",
|
"BU7oMY/4QfYttCKYisOLcLcrmgDH8BFbdsqB3hWJeM9ey97xSvqxHq241fd94oYN8WgTgTh2mD4SB5SP",
|
||||||
"N4w+j76QGuJzAQKu081op/YsRI5Qk23NEc+JCTWR8MJTbPfhQhuBtau0FMhxvzX1N8xCqNmnCSAeyu8Y",
|
"6xY9wABlEj5/DOqRok4ng0zFcUzebFYNjPLksySENZ3ozYWq8lVV6YaSwEw0g41fjUJ9Hdam5C5PoLRH",
|
||||||
"AyH8oplE8ZtB8DK7L5O51Er33zb94BKCLd23HL+Kl3XWYyqS5Tr+LCa2lVbggdkDGtUt/tOqiQw1ep/5",
|
"ecn3U4NWrKPYP8zMyotSmL9VbUx9SrVG1xUenUtByDulma6YBf7kYjhhDBL1RSakRKUlxtVMy5dTOJ1Q",
|
||||||
"YIwo2EXzADj9ab7Rch6MBq3eRYIsESYRC8bhxzgQF6Z2cS03TiHYky1wKUWzmq4rN9rOzay8ub6Y9Mme",
|
"VzeMPo++4K4QyZQA1+lmtFNRRotSS8PRxVNtO+I5MaEmEl54wu0+XGgjsHaVdgMG91tdf8Ms9DX7NAE0",
|
||||||
"HDIhvqNa+4tEfyqwyGrJn41LJcX4ieI2pUswmS6+pgXBS0/8LbLm3iMx6StV1FjdwmBtGfTT3Rpyo+rC",
|
"RPkdY6CHXzSTKH4zCF5m92Uyl1rh/nfsILyEQEz3HDco42Wd9ZmK5HhuMIuJbYXVeWD2gEZ1k/+0bCJD",
|
||||||
"7RSDxkJYvPQpL2/Jkn5YQHnVptNmJ7QF4aTeJQeAwMjLrDMmzXxu+MiQexNxZ8InvNrw0BBtZ1U7iKkx",
|
"hd5nPhgjCvZWPQBOf2o2Ws6D36DVu0iYJcIkYsE4/BgH4sLULq7lxikEe7IFLqVoltN15fbrxszKG+uL",
|
||||||
"mGyJ2dgAd6f4ObCW5THioAFwXU5GQco0ndCBld6irTEEzXUE1/YjSD6f0J39dXpfDKxYjbM+dXnIvAD7",
|
"Sff0yaEW4jvKtcZI9Kcci6yS/Nm4jFKMnyhuU7oEk+n8a1oQvPDE3yRr3l0Sk75UtY3Tzg3WFsFC3akg",
|
||||||
"BUxvg01P129NVEUlqIODQnuUtxTLZTF24v7OtKX2OLiR2LdMRHEwfUTGX0VcwgQGBgFA6YiZfnEn7FM6",
|
"N6oqTE8xaCyExUuf8mKXLOmHOZRXbTptdkJTEE7qa3IA6Iy8BDtj0swbw0eW3LeIOxM+4ZWIh5ZoRqza",
|
||||||
"QA86ZvHQEWa0QFN6yIJ9JLD7mEQ7hSkIfzr6YaJtAHZ3yIOg1QFPkR6vULSHq4iVki+RJOyU17Q9qPWD",
|
"QUyNwWRLzMYGKDzFz4GVLY8RIw1A7QwZBSnTdEIHVnqLtv4QNNcRXNuPIPl8Qnf21+l9sbCaNc761OUh",
|
||||||
"8v7nmnSShglpi6LBOv7FqwlEo3Vox481Ane1r/whOdtwLzAHwa8G5ltTx0Iddgs6bQYlTfZ4N0IJkVSf",
|
"8+LsFzC9DTY9XS82USOVIBIOcu1R3m7MyGLsxP2d3ZFa5+BGbiqtRjF9RMZmRczCBCIGwUHpiJl+cX/0",
|
||||||
"LvaPUmsmYfGBWzba0rSAAxzRl9Dx75ieNFO4K1L2I0/minF0DboHxYTP2V0iuoQfgEq1x2x7g//Bzhu2",
|
"UzpADzpm8dARZrQwtj2ALNhHAtePSbRTmILwp6MfJtoG0HeX3A8bLfAU6bEMReu4kjgqZokk4aq8pu1M",
|
||||||
"fNqItu84U9/9eWV1qRs43x0af/n0S+O7P/PaNKhF/O5wOhfsBeWRjHcnYtbSGgFkvON6HukEpNuCE1Bw",
|
"rh+Ud8XXpJPUbEhbFG338ROvJhDt99k/eD3BHe0rf0jONtwLzEEIygH9VtTNUIfrgk6bQUEDPt6pUEIr",
|
||||||
"GjmM50UcylfLzgAUyu/L/boAer+MtkF87oEHDlNDYi8kUzhl7kbZvUdPjNf1V8uQKaWvqyldJ3S3aVxJ",
|
"1aeL/aPUtklYfOCWjbY07eEAY/QldAM8pif1FCaLlP3Ik7lijF2L7kFp4XN2l4je8QegUu0x297iH9h5",
|
||||||
"9PMTult6Srw14l0STcT1R4RNfF/FUozvgwFHzcWmab+GCBO/GeYNzERmLCoqR2TgY8wxkSswn6ZaszXv",
|
"w3ZQG9H2bXfquz+vrC61Q/e7Q+svn35pffdnXpsGlYnfHU4bgWBQHslYeCJmLa0RAMhbnu+TVkjaDTgB",
|
||||||
"OBzO94jjsMqoxUrT8YGRtFdLlR+LYiWeQQE69W/gtgJEeA0ceLQznaP0LAK9bjByTZRB47fojZRd8B3F",
|
"OaeRQ3xexKF8tewMIKL8vtyvCrz3y2gbxOceeOAwNST2QjKFU+ZulN179MR6XX+1DJlS+rqa0nVCd+vW",
|
||||||
"iMEnqULs+vMIS18ZazGpTvAy/637Aeml+C/wiNmrI6MXR8KU3sRAVdN5i4B9QHPE9F7j8mtG9Hj6qqGm",
|
"lUQ/P6G7hafEXyP+JdFaXn9E2MT3VZzF+D4YcERdbKj2a4gw8Zth3sJMZMaionJEBkXGHBO5AvNpqm1b",
|
||||||
"6h5hObCatYumQ/sjKHo6W8buvMF1b65XgfJO9zXo1nH1N78VuUq3x8T4B/3QF/3ZoHh9oJXjQCVwb9Sf",
|
"/bbLoX6POEarjGistKIfWEnrtVQxsihW4hkUoFP/Bm4rQIvXQIVHO9MGpWcR6PU+I9dEGTR+i95I2QXf",
|
||||||
"DByeyzDMGZQ86Fcb1nKCN99oaFJ6J5t1/FFlgD2k/c9D02YnrNIVF3Ytd/ZH588z/i1Ako/O4xzKkwzx",
|
"UYwmfJIqy64+j7DwlbEWI29Kiv/Wg5B0U/wX+sTuVpHRiyNhSm9ioKrpvHmgP6A5Ynqvdfk1K3o8fdVS",
|
||||||
"0LpTlwMEUe6+5B7LJZEPqS8i+in31PlvcQTqSfjp4A03uv5F+6wrFuSKQkzRSCyJW2ByweBq0sjxCynn",
|
"U3WPsBxYzdpF06H5MRQ9nS1jd97iujfXq0B5p/sa5Ou4Fpzfilyl22Ni/MNePxC926CUfaCV40AlcG9U",
|
||||||
"M/YKfF5fjZQ0KURQFyHWaMuwulCZxC5vubxIqQeSoK5L09JTPu28OJQQdZPdpqItqpGwE6xGlkDpc1JA",
|
"nwzcP5dhaBiU3O+VG9ZxwzffqGlSeiebdfxxafA9pP3P+3aHnbBSV1y/7XizPzp/nvFvAa58dB7nkEky",
|
||||||
"f4fX5GmSbg69RZWuBlD+GoQ+XsNTQegbl2fmZuaaKmrde+9ce9uYNRau3b7+HkTE0Tc1QoX3zTcM+gf6",
|
"xEPrTp0BFqLYfck9lksiH1JfRPRT7qkL3uLo1JPw08Eb3m8HF+2zLlmQKwoxRZOxJG6ByQWDq0mTxy+k",
|
||||||
"xTRCoeCNneh/u5AqMwQl9C8bv7vjtPnUZz4KSUhay6Ftt41ZI/449InX+ih0AxO/azJTbAu9MDhh8WSM",
|
"nM/YK/B5dTVS0qQQXV2EWKMty2lDZRK7vOXyIqUeSILBLkxLT/m0TXEoIeomu015W1QhYSdYjSwB1htS",
|
||||||
"Ot+OK37iF4u2wrvouCmApoTBJimQpDe8IohKaQYcrTI3zi0VEoimu4OJoFPCq3hJ7z54BYd0n2uoKnRg",
|
"QH+H1+Rpkm4OfUeVjgdQ/hr2A7yGp8J+YF2emZuZq6uIdu+9c+1ta9ZauHbr+nsQEUff1AgV3jffsOgf",
|
||||||
"vKcnxlQMx9o0qkEO65Hu89rzyozOZaKEEvRC6WkabYkklQ06ir6gR8nR4oDF7DhNAaDiELsqHnNF8AAY",
|
"6BfTCIyCN3ai/+1CqswQlNC/bPzuttvkU5/5uE/6pLHc73Sa1qwV/7kfEL/xcd8LbfyuzkyxLfTC4ITF",
|
||||||
"9IM+ca4t3JjOZux/aAWrXc+8LyHSV8hofIReBRTVTZlsSt/6F8IbUlfbXv5W4ZNnJ+6LuIxUBuVBqwNd",
|
"kzEifTOu+IlfLFoO76LjJge2EgabpECS3vCK4CulGXAkS2OcWyokEA15BxNBroRX8ZLeffAKDuk+11BV",
|
||||||
"maK/v4JImuxgrUJytmc5pCSYVXMDj//obS50u1sLHvMfEHJGM360c47j3/dI0ucrDzoy5TtMiySEFpsx",
|
"WMF4T0+sqRiqtW6VgyPWo+CbWvfKjM5looQZ9ELpdxptiSSVDTqKvqBHydHiYMbsOE0B2OIQOy4ec0Xw",
|
||||||
"Ft+7dum1K28a2J+bd6OQLU/RzkK0MUZss6mu2/HFhGZ63b96Q3PyF/g8LwLyXrzrlYLeJ5P4HsDeK30t",
|
"ABj0wx5xry28P53N2P/ICVfbvn1PQqsvkdH4CL0KKKrrMtmUnvYvhDekqpa+/K3CJ89O3BdxGakMyoNW",
|
||||||
"425fg0QBgfDbnnA8Pwee2EcX1ZlZE1SdIpUYCfVzeGzi+4Gv0W3DvwpMFQzbAQV4HgkvQ8Pbpo6N+Beu",
|
"B7oyRe9/Ba002cFKheRs13FJQTCr4uYe/9FbYOh2txKs5j8g5Ixm/GjnHMe/55OkB5gJVjLlO0yLJAQa",
|
||||||
"t6Ub6hT1vxl7A+6ba2TWhfUVZl9/aK6RD/hjk2zAJb2mQui0rpg6buEIPWopEENRKxo35hdeQ+vy3zq8",
|
"m7EW37t26bUrb1rYu5t3qpAtT9HqQrQ4RqSzqbbXCsSEZrrtv3pDc/IX+DwvAg5fvOuVAuInk/geQOIr",
|
||||||
"xQKPJZYl5QKx5cQSvYdgMTA9IPikGhCaawTeocdyuFzri6Qa5KJ0CQnMP04jfXy+8vJki7+CwVA5++7P",
|
"PS/jTmCDRAGB8NuecDw/B57YRxfVmVkTVJ08lRgJ9XN4bOL7ga/RbcO/CkwVDNsBBXgeCS9Dw9umio34",
|
||||||
"7HqNnmIA6pAeR1vfHVbftdLslHecbrx1pfrqP7+KJBGx7u3sqpu1tL/SVd5OmJ2vs4EeXHwHzUIGT2GV",
|
"F663pZvt5PXGGXsD7tlrZNaD9eVmX39kr5EP+WOTbM4lvaZE6LSqmDpu4Qg9ailIQ1ErGjftF15D5/Lf",
|
||||||
"1MbOqUTo5A5iV8CsRn7xGLDBboroUWyzxZZu0oIM7hToqiqCZmMejVmHbcMkmlD9jDyYtFD8GWehV8cy",
|
"urz9Ao8lFiXlArHlxBK9h2AxtH0g+KSaE9prBN6hx3K4XOmLpBrkvHQJCeg/TiN9fL7y8mSLv4LBUDn7",
|
||||||
"39TLJ5CuzbtAqnwIdfeiExCgaUuJq0zB43hR20qtbAJhqGeLVWLaweqvihSJ9/gjk6yZg1egOVoAnfAp",
|
"7s/seo2eYgDqkB5HW98dlt+1wuyUd9x2vHWF+uo/v4okEbHu7eyq65W0xtJV3k6Yna+zge5ffHfNXAZP",
|
||||||
"71uOrv7igME/QTQgDuKeygOoWR1yvNAjZne9kBq38InJ1px11wtowcvV93lgcDOJVnPXpkB7Fv72K3Ov",
|
"YZVUxs6pROjkDmJXwKxGfvEYsMVuiuhRbLPFlm7SngzuFOi4KoJmYx6NWZdtwyQaVP2M3J+0UPwZZ6FX",
|
||||||
"X9jkvoZigziEqMkI0058iAw8zE6/aI//F2jbJ9j8Q+z0CX2GFU8iIJTIXJFys21MVSTftJZR1Dl93Fgi",
|
"xzLfVMsnkK7NO0SqfAh196JLECBtS4mrTMHjeFHbSq1sAmGoZ4tVYnfC1V/lKRLv8UcmWTMHr0BzNAc6",
|
||||||
"pke8a2GwyqbIBCImPKAgDT27Md+YBUHJx/o4jg7imA+bH8v9UuW/e0T5FnoMSB+IBrDSR8JnL33EczKk",
|
"4VPe0xxd/fkBg3+CaEAcxD2VB1CzOuR4oU/s9nouNW7iE5OtOWuv59CCl6vv88DgZhKt5q5Ngf0s/O1X",
|
||||||
"T+J4mPzDJLda+hSEhTyFfr/x8O7D/xcAAP//h/5WG9w3AQA=",
|
"5l6/sMl9DcUGcQhRkxGmnfgQGXiYnX7eHv8v0LZPsDGI2OkT+gwrnkRAKJG5IuVm25oqSb5pLaOoc3pQ",
|
||||||
|
"WyK2T/xr/XCVTZEJREx4QEHa9zu1+dosCEo+1oM4OohjPqw/kHupyp+7RPkW+g9IfxDNYaU/CZ+99Cee",
|
||||||
|
"kyH9JY6HyT9Mcqulv4KwkKfQ69Ue3nn4/wIAAP//8doNsq86AQA=",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|||||||
@@ -565,5 +565,6 @@ func TestPublicRoutes(t *testing.T) {
|
|||||||
e := newEnv(t)
|
e := newEnv(t)
|
||||||
assert.Equal(t, http.StatusOK, e.do(http.MethodGet, "/api/v1/server-info", nil).Code)
|
assert.Equal(t, http.StatusOK, e.do(http.MethodGet, "/api/v1/server-info", nil).Code)
|
||||||
assert.Equal(t, http.StatusOK, e.do(http.MethodGet, "/healthz", nil).Code)
|
assert.Equal(t, http.StatusOK, e.do(http.MethodGet, "/healthz", nil).Code)
|
||||||
|
assert.NotEqual(t, http.StatusUnauthorized, e.do(http.MethodGet, "/api/v1/app/android/latest", nil).Code, "APK page is public")
|
||||||
assert.Equal(t, http.StatusUnauthorized, e.do(http.MethodGet, "/api/v1/unknown", nil).Code)
|
assert.Equal(t, http.StatusUnauthorized, e.do(http.MethodGet, "/api/v1/unknown", nil).Code)
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,8 @@ var publicRoutes = map[string]bool{
|
|||||||
"GET /api/v1/server-info": true,
|
"GET /api/v1/server-info": true,
|
||||||
"POST /api/v1/auth/login": true,
|
"POST /api/v1/auth/login": true,
|
||||||
"POST /api/v1/auth/refresh": true,
|
"POST /api/v1/auth/refresh": true,
|
||||||
|
// Android-приложение раздаётся по ссылке /android без входа: в APK нет ничего, кроме клиента.
|
||||||
|
"GET /api/v1/app/android/latest": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Что разрешено, пока пользователь обязан сменить учётные данные / пароль.
|
// Что разрешено, пока пользователь обязан сменить учётные данные / пароль.
|
||||||
@@ -90,7 +92,8 @@ func Middleware(svc *Service, log *slog.Logger) func(http.Handler) http.Handler
|
|||||||
// Обложки открывает <img> без заголовка Authorization — ссылку защищает подпись.
|
// Обложки открывает <img> без заголовка Authorization — ссылку защищает подпись.
|
||||||
// Аудио открывает <audio> — тоже без заголовка; ссылка подписана для пользователя.
|
// Аудио открывает <audio> — тоже без заголовка; ссылка подписана для пользователя.
|
||||||
cover := (r.Method == http.MethodGet || r.Method == http.MethodHead) &&
|
cover := (r.Method == http.MethodGet || r.Method == http.MethodHead) &&
|
||||||
(strings.HasPrefix(r.URL.Path, "/api/v1/covers/") || strings.HasPrefix(r.URL.Path, "/api/v1/stream/"))
|
(strings.HasPrefix(r.URL.Path, "/api/v1/covers/") || strings.HasPrefix(r.URL.Path, "/api/v1/stream/") ||
|
||||||
|
strings.HasPrefix(r.URL.Path, "/api/v1/app/android/download/"))
|
||||||
if !strings.HasPrefix(r.URL.Path, "/api/v1/") || publicRoutes[route] || cover {
|
if !strings.HasPrefix(r.URL.Path, "/api/v1/") || publicRoutes[route] || cover {
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -292,6 +292,15 @@ func (h *Handler) ScanSource(ctx context.Context, req gen.ScanSourceRequestObjec
|
|||||||
return gen.ScanSource202JSONResponse(progressDTO(p)), nil
|
return gen.ScanSource202JSONResponse(progressDTO(p)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetryAnalysis реализует gen.StrictServerInterface.
|
||||||
|
func (h *Handler) RetryAnalysis(ctx context.Context, _ gen.RetryAnalysisRequestObject) (gen.RetryAnalysisResponseObject, error) {
|
||||||
|
n, err := h.svc.RetryAnalysis(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gen.RetryAnalysis200JSONResponse{Requeued: n}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetLibraryStatus реализует gen.StrictServerInterface.
|
// GetLibraryStatus реализует gen.StrictServerInterface.
|
||||||
func (h *Handler) GetLibraryStatus(ctx context.Context, _ gen.GetLibraryStatusRequestObject) (gen.GetLibraryStatusResponseObject, error) {
|
func (h *Handler) GetLibraryStatus(ctx context.Context, _ gen.GetLibraryStatusRequestObject) (gen.GetLibraryStatusResponseObject, error) {
|
||||||
st, err := h.svc.Status(ctx)
|
st, err := h.svc.Status(ctx)
|
||||||
@@ -304,7 +313,7 @@ func (h *Handler) GetLibraryStatus(ctx context.Context, _ gen.GetLibraryStatusRe
|
|||||||
MissingFiles: int(c.Missing), FileErrors: int(c.Errors), Sources: []gen.SourceStatusItem{},
|
MissingFiles: int(c.Missing), FileErrors: int(c.Errors), Sources: []gen.SourceStatusItem{},
|
||||||
Analysis: gen.AnalysisStatus{
|
Analysis: gen.AnalysisStatus{
|
||||||
Queued: st.Analysis.Queued, Running: st.Analysis.Running, Done: st.Analysis.Done,
|
Queued: st.Analysis.Queued, Running: st.Analysis.Running, Done: st.Analysis.Done,
|
||||||
Failed: st.Analysis.Failed, AnalyzerVer: st.Analysis.AnalyzerVer,
|
Failed: st.Analysis.Failed, AnalyzerVer: st.Analysis.AnalyzerVer, TopError: st.Analysis.TopError,
|
||||||
},
|
},
|
||||||
Wave: gen.WaveMetrics{
|
Wave: gen.WaveMetrics{
|
||||||
Served: st.Wave.Served, Sessions: st.Wave.Sessions, ListenThroughRate: st.Wave.ListenThrough,
|
Served: st.Wave.Served, Sessions: st.Wave.Sessions, ListenThroughRate: st.Wave.ListenThrough,
|
||||||
|
|||||||
@@ -587,6 +587,15 @@ func (s *Service) previewOne(ctx context.Context, root, rel string, patterns []m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartScan запускает скан источника в фоне.
|
// StartScan запускает скан источника в фоне.
|
||||||
|
// RetryAnalysis возвращает в очередь треки, которые анализатор не смог обработать (например, пока диск
|
||||||
|
// не был смонтирован).
|
||||||
|
func (s *Service) RetryAnalysis(ctx context.Context) (int, error) {
|
||||||
|
if err := requirePerm(ctx, auth.PermManageLibrary); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return s.store.RetryFailedAnalysis(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) StartScan(ctx context.Context, id uuid.UUID, full bool) (Progress, error) {
|
func (s *Service) StartScan(ctx context.Context, id uuid.UUID, full bool) (Progress, error) {
|
||||||
if err := requirePerm(ctx, auth.PermManageLibrary); err != nil {
|
if err := requirePerm(ctx, auth.PermManageLibrary); err != nil {
|
||||||
return Progress{}, err
|
return Progress{}, err
|
||||||
|
|||||||
@@ -605,7 +605,7 @@ func (st *Store) AlbumCoverPath(ctx context.Context, albumID uuid.UUID) (*string
|
|||||||
// AnalysisCounts — состояние очереди аудиоанализа.
|
// AnalysisCounts — состояние очереди аудиоанализа.
|
||||||
type AnalysisCounts struct {
|
type AnalysisCounts struct {
|
||||||
Queued, Running, Done, Failed int
|
Queued, Running, Done, Failed int
|
||||||
AnalyzerVer *string
|
AnalyzerVer, TopError *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnalysisCounts считает задачи анализа по статусам и текущую версию анализатора.
|
// AnalysisCounts считает задачи анализа по статусам и текущую версию анализатора.
|
||||||
@@ -614,14 +614,27 @@ func (st *Store) AnalysisCounts(ctx context.Context) (AnalysisCounts, error) {
|
|||||||
err := st.pool.QueryRow(ctx, `
|
err := st.pool.QueryRow(ctx, `
|
||||||
SELECT count(*) FILTER (WHERE status = 'queued'), count(*) FILTER (WHERE status = 'running'),
|
SELECT count(*) FILTER (WHERE status = 'queued'), count(*) FILTER (WHERE status = 'running'),
|
||||||
count(*) FILTER (WHERE status = 'done'), count(*) FILTER (WHERE status = 'failed'),
|
count(*) FILTER (WHERE status = 'done'), count(*) FILTER (WHERE status = 'failed'),
|
||||||
(SELECT analyzer_ver FROM track_features ORDER BY analyzed_at DESC LIMIT 1)
|
(SELECT analyzer_ver FROM track_features ORDER BY analyzed_at DESC LIMIT 1),
|
||||||
FROM analysis_jobs`).Scan(&c.Queued, &c.Running, &c.Done, &c.Failed, &c.AnalyzerVer)
|
(SELECT error FROM analysis_jobs WHERE status = 'failed' AND error IS NOT NULL
|
||||||
|
GROUP BY error ORDER BY count(*) DESC LIMIT 1)
|
||||||
|
FROM analysis_jobs`).Scan(&c.Queued, &c.Running, &c.Done, &c.Failed, &c.AnalyzerVer, &c.TopError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AnalysisCounts{}, fmt.Errorf("library: analysis counts: %w", err)
|
return AnalysisCounts{}, fmt.Errorf("library: analysis counts: %w", err)
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetryFailedAnalysis возвращает неудавшиеся задачи анализа в очередь с нуля попыток.
|
||||||
|
func (st *Store) RetryFailedAnalysis(ctx context.Context) (int, error) {
|
||||||
|
tag, err := st.pool.Exec(ctx, `
|
||||||
|
UPDATE analysis_jobs SET status = 'queued', attempts = 0, error = NULL, updated_at = now()
|
||||||
|
WHERE status = 'failed'`)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("library: retry analysis: %w", err)
|
||||||
|
}
|
||||||
|
return int(tag.RowsAffected()), nil
|
||||||
|
}
|
||||||
|
|
||||||
// WaveMetrics — метрики волны за 7 дней (docs/recommendations.md#1-цели-и-как-измеряем-успех).
|
// WaveMetrics — метрики волны за 7 дней (docs/recommendations.md#1-цели-и-как-измеряем-успех).
|
||||||
type WaveMetrics struct {
|
type WaveMetrics struct {
|
||||||
Served, Sessions int
|
Served, Sessions int
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ log = logging.getLogger(__name__)
|
|||||||
MAX_ATTEMPTS = 3
|
MAX_ATTEMPTS = 3
|
||||||
MAX_DURATION_S = 15 * 60
|
MAX_DURATION_S = 15 * 60
|
||||||
IDLE_SLEEP_S = 10.0
|
IDLE_SLEEP_S = 10.0
|
||||||
|
MISSING_SLEEP_S = 60.0 # файлы пропали целиком — ждём, пока смонтируют диск
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -168,6 +169,18 @@ class Worker:
|
|||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
def postpone(self, conn: psycopg.Connection[Any], track_ids: list[str]) -> None:
|
||||||
|
"""Файла нет на диске (не смонтирован?) — обратно в очередь, попытка не засчитывается."""
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
UPDATE analysis_jobs SET status = 'queued', attempts = greatest(attempts - 1, 0),
|
||||||
|
error = 'file not found — is the disk mounted?', updated_at = now()
|
||||||
|
WHERE track_id = ANY(%s::uuid[])
|
||||||
|
""",
|
||||||
|
(track_ids,),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
def requeue_stale(self, conn: psycopg.Connection[Any]) -> None:
|
def requeue_stale(self, conn: psycopg.Connection[Any]) -> None:
|
||||||
"""Задачи, брошенные упавшим воркером, и признаки старой версии анализатора — снова в очередь."""
|
"""Задачи, брошенные упавшим воркером, и признаки старой версии анализатора — снова в очередь."""
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@@ -189,6 +202,16 @@ class Worker:
|
|||||||
self.requeue_stale(conn)
|
self.requeue_stale(conn)
|
||||||
while True:
|
while True:
|
||||||
jobs = self.claim(conn, self.workers * 2)
|
jobs = self.claim(conn, self.workers * 2)
|
||||||
|
missing = [j for j in jobs if not Path(j.path).exists()]
|
||||||
|
if missing:
|
||||||
|
self.postpone(conn, [j.track_id for j in missing])
|
||||||
|
jobs = [j for j in jobs if j not in missing]
|
||||||
|
if not jobs:
|
||||||
|
log.warning(
|
||||||
|
"analysis: %d files not found, waiting — is the disk mounted?", len(missing)
|
||||||
|
)
|
||||||
|
time.sleep(MISSING_SLEEP_S)
|
||||||
|
continue
|
||||||
if not jobs:
|
if not jobs:
|
||||||
time.sleep(IDLE_SLEEP_S)
|
time.sleep(IDLE_SLEEP_S)
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in new issue
Block a user