improved library scan speeds, added library manager component

-libraries tab now opens a library manager
-choose library directory, manually soft scan and rescan
-batched db queues
-mp3 header-based duration extraction
This commit is contained in:
2026-02-19 10:17:09 -05:00
parent 8c013d4179
commit 81793975b4
36 changed files with 2350 additions and 179 deletions
+4
View File
@@ -5,6 +5,7 @@ import '@components/now-playing/now-playing.ts';
import '@components/sidebar/app-sidebar.ts';
import '@components/queue-panel/queue-panel.ts';
import '@components/playlist-view/playlist-view.ts';
import '@components/library-manager/library-manager.ts';
import '@awesome.me/webawesome/dist/styles/themes/default.css';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
import { setBasePath } from '@awesome.me/webawesome/dist/webawesome.js';
@@ -36,6 +37,9 @@ document.addEventListener('navigate', (e: Event) => {
case 'playlists':
mainContent.innerHTML = '<playlist-view></playlist-view>';
break;
case 'libraries':
mainContent.innerHTML = '<library-manager></library-manager>';
break;
default:
mainContent.innerHTML = `<div style="padding: 1em; color: #b3b3b3;">
<p>Coming soon: ${view}</p>
@@ -0,0 +1,329 @@
import { LitElement, html, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { EventsOn, EventsOff } from '@runtime/runtime';
import { Scan, FullRescan } from '@go/library/Library';
import {
GetLibraryDirectory,
SetLibraryDirectory,
} from '@go/config/Config';
import { DirectoryPicker } from '@go/frontendutil/FrontendUtil';
import { Events } from '../../events';
@customElement('library-manager')
export class LibraryManager extends LitElement {
@state() private libraryDirectory = '';
@state() private selectedDirectory = '';
@state() private scanning = false;
@state() private statusMessage = '';
static override styles = css`
:host {
display: block;
padding: 1.5em;
color: #e9ecef;
font-family: system-ui, -apple-system, sans-serif;
overflow-y: auto;
}
h2 {
margin: 0 0 1em 0;
font-size: 1.4em;
font-weight: 600;
color: #f8f9fa;
}
.section {
margin-bottom: 2em;
padding: 1.25em;
background: #2b3035;
border-radius: 8px;
}
.section-title {
margin: 0 0 0.75em 0;
font-size: 1em;
font-weight: 600;
color: #dee2e6;
}
.section-description {
margin: 0 0 1em 0;
font-size: 0.85em;
color: #868e96;
line-height: 1.4;
}
.directory-row {
display: flex;
align-items: center;
gap: 0.75em;
margin-bottom: 1em;
}
.directory-path {
flex: 1;
padding: 0.5em 0.75em;
background: #1a1d20;
border: 1px solid #495057;
border-radius: 4px;
color: #adb5bd;
font-size: 0.85em;
font-family: monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-height: 1.2em;
}
.directory-path.has-value {
color: #e9ecef;
}
button {
padding: 0.5em 1.25em;
border: none;
border-radius: 4px;
font-size: 0.85em;
font-weight: 500;
cursor: pointer;
transition: background-color 0.15s ease;
white-space: nowrap;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: #4263eb;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #3b5bdb;
}
.btn-success {
background: #2f9e44;
color: white;
}
.btn-success:hover:not(:disabled) {
background: #2b8a3e;
}
.btn-warning {
background: #e8590c;
color: white;
}
.btn-warning:hover:not(:disabled) {
background: #d9480f;
}
.btn-danger {
background: #e03131;
color: white;
}
.btn-danger:hover:not(:disabled) {
background: #c92a2a;
}
.scan-actions {
display: flex;
gap: 0.75em;
flex-wrap: wrap;
}
.status-bar {
margin-top: 1.5em;
padding: 0.75em 1em;
background: #1a1d20;
border-radius: 4px;
font-size: 0.85em;
color: #868e96;
min-height: 1.2em;
}
.status-bar.active {
color: #ffd43b;
}
`;
override connectedCallback(): void {
super.connectedCallback();
this.loadCurrentDirectory();
EventsOn(
Events.LibraryScanStarted,
this.handleScanStarted,
);
EventsOn(
Events.LibraryScanComplete,
this.handleScanComplete,
);
}
override disconnectedCallback(): void {
super.disconnectedCallback();
EventsOff(Events.LibraryScanStarted);
EventsOff(Events.LibraryScanComplete);
}
private async loadCurrentDirectory(): Promise<void> {
try {
const dir = await GetLibraryDirectory();
this.libraryDirectory = dir;
this.selectedDirectory = dir;
} catch (err) {
console.error(
'Failed to load library directory:',
err,
);
}
}
private handleScanStarted = (): void => {
this.scanning = true;
this.statusMessage = 'Scanning...';
};
private handleScanComplete = (): void => {
this.scanning = false;
this.statusMessage = 'Scan complete.';
};
private handleSelectDirectory = async (): Promise<void> => {
try {
const dir = await DirectoryPicker();
if (dir) {
this.selectedDirectory = dir;
}
} catch (err) {
console.error('Directory picker failed:', err);
}
};
private handleSaveDirectory = async (): Promise<void> => {
if (!this.selectedDirectory) return;
try {
await SetLibraryDirectory(this.selectedDirectory);
this.libraryDirectory = this.selectedDirectory;
this.statusMessage =
'Library directory saved. A scan will start automatically if the directory changed.';
} catch (err) {
this.statusMessage = `Failed to save directory: ${err}`;
console.error('Failed to save directory:', err);
}
};
private handleSoftScan = async (): Promise<void> => {
try {
await Scan();
} catch (err) {
this.statusMessage = `Scan failed: ${err}`;
console.error('Soft scan failed:', err);
}
};
private handleFullRescan = async (): Promise<void> => {
const confirmed = confirm(
'This will delete ALL library data including cover art and re-scan from scratch. Continue?',
);
if (!confirmed) return;
try {
await FullRescan();
} catch (err) {
this.statusMessage = `Full rescan failed: ${err}`;
console.error('Full rescan failed:', err);
}
};
private get directoryChanged(): boolean {
return (
this.selectedDirectory !== this.libraryDirectory
);
}
override render() {
return html`
<h2>Library Manager</h2>
<div class="section">
<p class="section-title">
Library Directory
</p>
<p class="section-description">
Select the root directory containing your
music files. Changing this will
automatically trigger a scan.
</p>
<div class="directory-row">
<div
class="directory-path ${this.selectedDirectory ? 'has-value' : ''}"
>
${this.selectedDirectory ||
'No directory selected'}
</div>
<button
class="btn-primary"
@click=${this.handleSelectDirectory}
>
Browse
</button>
<button
class="btn-success"
?disabled=${!this.directoryChanged ||
this.scanning}
@click=${this.handleSaveDirectory}
>
Save
</button>
</div>
</div>
<div class="section">
<p class="section-title">Scan Actions</p>
<p class="section-description">
Soft scan finds new files, skips existing
ones, and removes orphaned entries. Full
rescan clears the entire database and
cover art cache, then re-imports
everything.
</p>
<div class="scan-actions">
<button
class="btn-warning"
?disabled=${this.scanning}
@click=${this.handleSoftScan}
>
${this.scanning
? 'Scanning...'
: 'Soft Scan'}
</button>
<button
class="btn-danger"
?disabled=${this.scanning}
@click=${this.handleFullRescan}
>
${this.scanning
? 'Scanning...'
: 'Full Rescan'}
</button>
</div>
</div>
<div
class="status-bar ${this.scanning ? 'active' : ''}"
>
${this.statusMessage || 'Ready.'}
</div>
`;
}
}
+1
View File
@@ -39,6 +39,7 @@ export const Events = {
RequestRemoveTracksFromQueue: "RequestRemoveTracksFromQueue",
// Library events
LibraryScanStarted: "LibraryScanStarted",
LibraryScanComplete: "LibraryScanComplete",
} as const;
+18
View File
@@ -0,0 +1,18 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {http} from '../models';
import {context} from '../models';
export function GetLibraryDirectory():Promise<string>;
export function Load():Promise<void>;
export function Save():Promise<void>;
export function ServeHTTP(arg1:http.ResponseWriter,arg2:http.Request):Promise<void>;
export function SetContext(arg1:context.Context):Promise<void>;
export function SetLibraryDirectory(arg1:string):Promise<void>;
export function Validate():Promise<void>;
+31
View File
@@ -0,0 +1,31 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetLibraryDirectory() {
return window['go']['config']['Config']['GetLibraryDirectory']();
}
export function Load() {
return window['go']['config']['Config']['Load']();
}
export function Save() {
return window['go']['config']['Config']['Save']();
}
export function ServeHTTP(arg1, arg2) {
return window['go']['config']['Config']['ServeHTTP'](arg1, arg2);
}
export function SetContext(arg1) {
return window['go']['config']['Config']['SetContext'](arg1);
}
export function SetLibraryDirectory(arg1) {
return window['go']['config']['Config']['SetLibraryDirectory'](arg1);
}
export function Validate() {
return window['go']['config']['Config']['Validate']();
}
+4
View File
@@ -3,6 +3,8 @@
import {library} from '../models';
import {context} from '../models';
export function FullRescan():Promise<void>;
export function GetAlbumTracks(arg1:number):Promise<Array<library.Track>>;
export function GetAllAlbums():Promise<Array<library.Album>>;
@@ -12,3 +14,5 @@ export function GetAllTracks():Promise<Array<library.Track>>;
export function Scan():Promise<void>;
export function SetContext(arg1:context.Context):Promise<void>;
export function SetQueue(arg1:library.queueClearer):Promise<void>;
+8
View File
@@ -2,6 +2,10 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function FullRescan() {
return window['go']['library']['Library']['FullRescan']();
}
export function GetAlbumTracks(arg1) {
return window['go']['library']['Library']['GetAlbumTracks'](arg1);
}
@@ -21,3 +25,7 @@ export function Scan() {
export function SetContext(arg1) {
return window['go']['library']['Library']['SetContext'](arg1);
}
export function SetQueue(arg1) {
return window['go']['library']['Library']['SetQueue'](arg1);
}
+601
View File
@@ -1,3 +1,132 @@
export namespace http {
export class Response {
Status: string;
StatusCode: number;
Proto: string;
ProtoMajor: number;
ProtoMinor: number;
Header: Record<string, Array<string>>;
Body: any;
ContentLength: number;
TransferEncoding: string[];
Close: boolean;
Uncompressed: boolean;
Trailer: Record<string, Array<string>>;
Request?: Request;
TLS?: tls.ConnectionState;
static createFrom(source: any = {}) {
return new Response(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Status = source["Status"];
this.StatusCode = source["StatusCode"];
this.Proto = source["Proto"];
this.ProtoMajor = source["ProtoMajor"];
this.ProtoMinor = source["ProtoMinor"];
this.Header = source["Header"];
this.Body = source["Body"];
this.ContentLength = source["ContentLength"];
this.TransferEncoding = source["TransferEncoding"];
this.Close = source["Close"];
this.Uncompressed = source["Uncompressed"];
this.Trailer = source["Trailer"];
this.Request = this.convertValues(source["Request"], Request);
this.TLS = this.convertValues(source["TLS"], tls.ConnectionState);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class Request {
Method: string;
URL?: url.URL;
Proto: string;
ProtoMajor: number;
ProtoMinor: number;
Header: Record<string, Array<string>>;
Body: any;
ContentLength: number;
TransferEncoding: string[];
Close: boolean;
Host: string;
Form: Record<string, Array<string>>;
PostForm: Record<string, Array<string>>;
MultipartForm?: multipart.Form;
Trailer: Record<string, Array<string>>;
RemoteAddr: string;
RequestURI: string;
TLS?: tls.ConnectionState;
Response?: Response;
Pattern: string;
static createFrom(source: any = {}) {
return new Request(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Method = source["Method"];
this.URL = this.convertValues(source["URL"], url.URL);
this.Proto = source["Proto"];
this.ProtoMajor = source["ProtoMajor"];
this.ProtoMinor = source["ProtoMinor"];
this.Header = source["Header"];
this.Body = source["Body"];
this.ContentLength = source["ContentLength"];
this.TransferEncoding = source["TransferEncoding"];
this.Close = source["Close"];
this.Host = source["Host"];
this.Form = source["Form"];
this.PostForm = source["PostForm"];
this.MultipartForm = this.convertValues(source["MultipartForm"], multipart.Form);
this.Trailer = source["Trailer"];
this.RemoteAddr = source["RemoteAddr"];
this.RequestURI = source["RequestURI"];
this.TLS = this.convertValues(source["TLS"], tls.ConnectionState);
this.Response = this.convertValues(source["Response"], Response);
this.Pattern = source["Pattern"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace library {
export class Album {
@@ -51,6 +180,163 @@ export namespace library {
}
export namespace multipart {
export class FileHeader {
Filename: string;
Header: Record<string, Array<string>>;
Size: number;
static createFrom(source: any = {}) {
return new FileHeader(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Filename = source["Filename"];
this.Header = source["Header"];
this.Size = source["Size"];
}
}
export class Form {
Value: Record<string, Array<string>>;
File: Record<string, Array<FileHeader>>;
static createFrom(source: any = {}) {
return new Form(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Value = source["Value"];
this.File = this.convertValues(source["File"], Array<FileHeader>, true);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace net {
export class IPNet {
IP: number[];
Mask: number[];
static createFrom(source: any = {}) {
return new IPNet(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.IP = source["IP"];
this.Mask = source["Mask"];
}
}
}
export namespace pkix {
export class AttributeTypeAndValue {
Type: number[];
Value: any;
static createFrom(source: any = {}) {
return new AttributeTypeAndValue(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Type = source["Type"];
this.Value = source["Value"];
}
}
export class Extension {
Id: number[];
Critical: boolean;
Value: number[];
static createFrom(source: any = {}) {
return new Extension(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Id = source["Id"];
this.Critical = source["Critical"];
this.Value = source["Value"];
}
}
export class Name {
Country: string[];
Organization: string[];
OrganizationalUnit: string[];
Locality: string[];
Province: string[];
StreetAddress: string[];
PostalCode: string[];
SerialNumber: string;
CommonName: string;
Names: AttributeTypeAndValue[];
ExtraNames: AttributeTypeAndValue[];
static createFrom(source: any = {}) {
return new Name(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Country = source["Country"];
this.Organization = source["Organization"];
this.OrganizationalUnit = source["OrganizationalUnit"];
this.Locality = source["Locality"];
this.Province = source["Province"];
this.StreetAddress = source["StreetAddress"];
this.PostalCode = source["PostalCode"];
this.SerialNumber = source["SerialNumber"];
this.CommonName = source["CommonName"];
this.Names = this.convertValues(source["Names"], AttributeTypeAndValue);
this.ExtraNames = this.convertValues(source["ExtraNames"], AttributeTypeAndValue);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace playlist {
export class Summary {
@@ -134,3 +420,318 @@ export namespace playlist {
}
export namespace tls {
export class ConnectionState {
Version: number;
HandshakeComplete: boolean;
DidResume: boolean;
CipherSuite: number;
CurveID: number;
NegotiatedProtocol: string;
NegotiatedProtocolIsMutual: boolean;
ServerName: string;
PeerCertificates: x509.Certificate[];
VerifiedChains: x509.Certificate[][];
SignedCertificateTimestamps: number[][];
OCSPResponse: number[];
TLSUnique: number[];
ECHAccepted: boolean;
static createFrom(source: any = {}) {
return new ConnectionState(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Version = source["Version"];
this.HandshakeComplete = source["HandshakeComplete"];
this.DidResume = source["DidResume"];
this.CipherSuite = source["CipherSuite"];
this.CurveID = source["CurveID"];
this.NegotiatedProtocol = source["NegotiatedProtocol"];
this.NegotiatedProtocolIsMutual = source["NegotiatedProtocolIsMutual"];
this.ServerName = source["ServerName"];
this.PeerCertificates = this.convertValues(source["PeerCertificates"], x509.Certificate);
this.VerifiedChains = this.convertValues(source["VerifiedChains"], x509.Certificate);
this.SignedCertificateTimestamps = source["SignedCertificateTimestamps"];
this.OCSPResponse = source["OCSPResponse"];
this.TLSUnique = source["TLSUnique"];
this.ECHAccepted = source["ECHAccepted"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace url {
export class Userinfo {
static createFrom(source: any = {}) {
return new Userinfo(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
}
}
export class URL {
Scheme: string;
Opaque: string;
// Go type: Userinfo
User?: any;
Host: string;
Path: string;
RawPath: string;
OmitHost: boolean;
ForceQuery: boolean;
RawQuery: string;
Fragment: string;
RawFragment: string;
static createFrom(source: any = {}) {
return new URL(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Scheme = source["Scheme"];
this.Opaque = source["Opaque"];
this.User = this.convertValues(source["User"], null);
this.Host = source["Host"];
this.Path = source["Path"];
this.RawPath = source["RawPath"];
this.OmitHost = source["OmitHost"];
this.ForceQuery = source["ForceQuery"];
this.RawQuery = source["RawQuery"];
this.Fragment = source["Fragment"];
this.RawFragment = source["RawFragment"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace x509 {
export class PolicyMapping {
// Go type: OID
IssuerDomainPolicy: any;
// Go type: OID
SubjectDomainPolicy: any;
static createFrom(source: any = {}) {
return new PolicyMapping(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.IssuerDomainPolicy = this.convertValues(source["IssuerDomainPolicy"], null);
this.SubjectDomainPolicy = this.convertValues(source["SubjectDomainPolicy"], null);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class OID {
static createFrom(source: any = {}) {
return new OID(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
}
}
export class Certificate {
Raw: number[];
RawTBSCertificate: number[];
RawSubjectPublicKeyInfo: number[];
RawSubject: number[];
RawIssuer: number[];
Signature: number[];
SignatureAlgorithm: number;
PublicKeyAlgorithm: number;
PublicKey: any;
Version: number;
// Go type: big
SerialNumber?: any;
Issuer: pkix.Name;
Subject: pkix.Name;
// Go type: time
NotBefore: any;
// Go type: time
NotAfter: any;
KeyUsage: number;
Extensions: pkix.Extension[];
ExtraExtensions: pkix.Extension[];
UnhandledCriticalExtensions: number[][];
ExtKeyUsage: number[];
UnknownExtKeyUsage: number[][];
BasicConstraintsValid: boolean;
IsCA: boolean;
MaxPathLen: number;
MaxPathLenZero: boolean;
SubjectKeyId: number[];
AuthorityKeyId: number[];
OCSPServer: string[];
IssuingCertificateURL: string[];
DNSNames: string[];
EmailAddresses: string[];
IPAddresses: number[][];
URIs: url.URL[];
PermittedDNSDomainsCritical: boolean;
PermittedDNSDomains: string[];
ExcludedDNSDomains: string[];
PermittedIPRanges: net.IPNet[];
ExcludedIPRanges: net.IPNet[];
PermittedEmailAddresses: string[];
ExcludedEmailAddresses: string[];
PermittedURIDomains: string[];
ExcludedURIDomains: string[];
CRLDistributionPoints: string[];
PolicyIdentifiers: number[][];
Policies: OID[];
InhibitAnyPolicy: number;
InhibitAnyPolicyZero: boolean;
InhibitPolicyMapping: number;
InhibitPolicyMappingZero: boolean;
RequireExplicitPolicy: number;
RequireExplicitPolicyZero: boolean;
PolicyMappings: PolicyMapping[];
static createFrom(source: any = {}) {
return new Certificate(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Raw = source["Raw"];
this.RawTBSCertificate = source["RawTBSCertificate"];
this.RawSubjectPublicKeyInfo = source["RawSubjectPublicKeyInfo"];
this.RawSubject = source["RawSubject"];
this.RawIssuer = source["RawIssuer"];
this.Signature = source["Signature"];
this.SignatureAlgorithm = source["SignatureAlgorithm"];
this.PublicKeyAlgorithm = source["PublicKeyAlgorithm"];
this.PublicKey = source["PublicKey"];
this.Version = source["Version"];
this.SerialNumber = this.convertValues(source["SerialNumber"], null);
this.Issuer = this.convertValues(source["Issuer"], pkix.Name);
this.Subject = this.convertValues(source["Subject"], pkix.Name);
this.NotBefore = this.convertValues(source["NotBefore"], null);
this.NotAfter = this.convertValues(source["NotAfter"], null);
this.KeyUsage = source["KeyUsage"];
this.Extensions = this.convertValues(source["Extensions"], pkix.Extension);
this.ExtraExtensions = this.convertValues(source["ExtraExtensions"], pkix.Extension);
this.UnhandledCriticalExtensions = source["UnhandledCriticalExtensions"];
this.ExtKeyUsage = source["ExtKeyUsage"];
this.UnknownExtKeyUsage = source["UnknownExtKeyUsage"];
this.BasicConstraintsValid = source["BasicConstraintsValid"];
this.IsCA = source["IsCA"];
this.MaxPathLen = source["MaxPathLen"];
this.MaxPathLenZero = source["MaxPathLenZero"];
this.SubjectKeyId = source["SubjectKeyId"];
this.AuthorityKeyId = source["AuthorityKeyId"];
this.OCSPServer = source["OCSPServer"];
this.IssuingCertificateURL = source["IssuingCertificateURL"];
this.DNSNames = source["DNSNames"];
this.EmailAddresses = source["EmailAddresses"];
this.IPAddresses = source["IPAddresses"];
this.URIs = this.convertValues(source["URIs"], url.URL);
this.PermittedDNSDomainsCritical = source["PermittedDNSDomainsCritical"];
this.PermittedDNSDomains = source["PermittedDNSDomains"];
this.ExcludedDNSDomains = source["ExcludedDNSDomains"];
this.PermittedIPRanges = this.convertValues(source["PermittedIPRanges"], net.IPNet);
this.ExcludedIPRanges = this.convertValues(source["ExcludedIPRanges"], net.IPNet);
this.PermittedEmailAddresses = source["PermittedEmailAddresses"];
this.ExcludedEmailAddresses = source["ExcludedEmailAddresses"];
this.PermittedURIDomains = source["PermittedURIDomains"];
this.ExcludedURIDomains = source["ExcludedURIDomains"];
this.CRLDistributionPoints = source["CRLDistributionPoints"];
this.PolicyIdentifiers = source["PolicyIdentifiers"];
this.Policies = this.convertValues(source["Policies"], OID);
this.InhibitAnyPolicy = source["InhibitAnyPolicy"];
this.InhibitAnyPolicyZero = source["InhibitAnyPolicyZero"];
this.InhibitPolicyMapping = source["InhibitPolicyMapping"];
this.InhibitPolicyMappingZero = source["InhibitPolicyMappingZero"];
this.RequireExplicitPolicy = source["RequireExplicitPolicy"];
this.RequireExplicitPolicyZero = source["RequireExplicitPolicyZero"];
this.PolicyMappings = this.convertValues(source["PolicyMappings"], PolicyMapping);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}