mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
feat: enhance storage provider management and localization support
- Added support for new storage providers including Backblaze B2 and GitHub. - Introduced a new UI schema for storage provider configuration, allowing for better user experience. - Updated localization files to include new keys for storage provider fields and usage metrics. - Refactored existing storage provider logic to accommodate new categories and improve overall structure. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import type {
|
||||
BuilderPluginESMImporter,
|
||||
BuilderPluginEventPayloads,
|
||||
} from '../plugins/types.js'
|
||||
import type { StorageProviderFactory } from '../storage/factory.js'
|
||||
import type { StorageProviderFactory, StorageProviderRegistrationOptions } from '../storage/factory.js'
|
||||
import type { StorageConfig } from '../storage/index.js'
|
||||
import { StorageFactory, StorageManager } from '../storage/index.js'
|
||||
import type { BuilderConfig, UserBuilderSettings } from '../types/config.js'
|
||||
@@ -580,8 +580,16 @@ export class AfilmoryBuilder {
|
||||
return this.ensureStorageManager()
|
||||
}
|
||||
|
||||
registerStorageProvider(provider: string, factory: StorageProviderFactory): void {
|
||||
StorageFactory.registerProvider(provider, factory)
|
||||
setStorageManager(manager: StorageManager): void {
|
||||
this.storageManager = manager
|
||||
}
|
||||
|
||||
registerStorageProvider(
|
||||
provider: string,
|
||||
factory: StorageProviderFactory,
|
||||
options?: StorageProviderRegistrationOptions,
|
||||
): void {
|
||||
StorageFactory.registerProvider(provider, factory, options)
|
||||
|
||||
if (this.getStorageConfig().provider === provider) {
|
||||
this.storageManager = null
|
||||
|
||||
@@ -36,7 +36,20 @@ export type {
|
||||
BuilderPluginHooks,
|
||||
BuilderPluginReference,
|
||||
} from './plugins/types.js'
|
||||
export type { ProgressCallback, ScanProgress, StorageConfig, StorageObject, StorageProvider } from './storage/index.js'
|
||||
export type {
|
||||
LocalStorageConfig,
|
||||
LocalStorageProviderName,
|
||||
ProgressCallback,
|
||||
RemoteStorageConfig,
|
||||
RemoteStorageProviderName,
|
||||
ScanProgress,
|
||||
StorageConfig,
|
||||
StorageObject,
|
||||
StorageProvider,
|
||||
StorageProviderCategory,
|
||||
} from './storage/index.js'
|
||||
export type { StorageProviderFactory, StorageProviderRegistrationOptions } from './storage/index.js'
|
||||
export { LOCAL_STORAGE_PROVIDERS, REMOTE_STORAGE_PROVIDERS } from './storage/index.js'
|
||||
export { StorageFactory, StorageManager } from './storage/index.js'
|
||||
export type { BuilderConfig, BuilderConfigInput } from './types/config.js'
|
||||
export type { AfilmoryManifest, CameraInfo, LensInfo } from './types/manifest.js'
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface PhotoExecutionContext {
|
||||
storageConfig: StorageConfig
|
||||
normalizeStorageKey: (key: string) => string
|
||||
loggers?: PhotoProcessingLoggers
|
||||
prefetchedBuffers?: Map<string, Buffer>
|
||||
}
|
||||
|
||||
const photoContextStorage = new AsyncLocalStorage<PhotoExecutionContext>()
|
||||
|
||||
@@ -48,11 +48,11 @@ export async function preprocessImage(
|
||||
photoKey: string,
|
||||
): Promise<{ rawBuffer: Buffer; processedBuffer: Buffer } | null> {
|
||||
const loggers = getGlobalLoggers()
|
||||
const { storageManager } = getPhotoExecutionContext()
|
||||
const { storageManager, prefetchedBuffers } = getPhotoExecutionContext()
|
||||
|
||||
try {
|
||||
// 获取图片数据
|
||||
const rawImageBuffer = await storageManager.getFile(photoKey)
|
||||
const rawImageBuffer = prefetchedBuffers?.get(photoKey) ?? (await storageManager.getFile(photoKey))
|
||||
if (!rawImageBuffer) {
|
||||
loggers.image.error(`无法获取图片数据:${photoKey}`)
|
||||
return null
|
||||
|
||||
@@ -13,9 +13,13 @@ export default function b2StoragePlugin(options: B2StoragePluginOptions = {}): B
|
||||
name: `afilmory:storage:${providerName}`,
|
||||
hooks: {
|
||||
onInit: ({ registerStorageProvider }) => {
|
||||
registerStorageProvider(providerName, (config) => {
|
||||
return new B2StorageProvider(config as B2Config)
|
||||
})
|
||||
registerStorageProvider(
|
||||
providerName,
|
||||
(config) => {
|
||||
return new B2StorageProvider(config as B2Config)
|
||||
},
|
||||
{ category: 'remote' },
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,9 +13,13 @@ export default function eagleStoragePlugin(options: EagleStoragePluginOptions =
|
||||
name: `afilmory:storage:${providerName}`,
|
||||
hooks: {
|
||||
onInit: ({ registerStorageProvider }) => {
|
||||
registerStorageProvider(providerName, (config) => {
|
||||
return new EagleStorageProvider(config as EagleConfig)
|
||||
})
|
||||
registerStorageProvider(
|
||||
providerName,
|
||||
(config) => {
|
||||
return new EagleStorageProvider(config as EagleConfig)
|
||||
},
|
||||
{ category: 'local' },
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Inject Eagle image metadata (name, tags) into manifest items before saving.
|
||||
|
||||
@@ -13,9 +13,13 @@ export default function githubStoragePlugin(options: GitHubStoragePluginOptions
|
||||
name: `afilmory:storage:${providerName}`,
|
||||
hooks: {
|
||||
onInit: ({ registerStorageProvider }) => {
|
||||
registerStorageProvider(providerName, (config) => {
|
||||
return new GitHubStorageProvider(config as GitHubConfig)
|
||||
})
|
||||
registerStorageProvider(
|
||||
providerName,
|
||||
(config) => {
|
||||
return new GitHubStorageProvider(config as GitHubConfig)
|
||||
},
|
||||
{ category: 'remote' },
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,9 +13,13 @@ export default function localStoragePlugin(options: LocalStoragePluginOptions =
|
||||
name: `afilmory:storage:${providerName}`,
|
||||
hooks: {
|
||||
onInit: ({ registerStorageProvider }) => {
|
||||
registerStorageProvider(providerName, (config) => {
|
||||
return new LocalStorageProvider(config as LocalConfig)
|
||||
})
|
||||
registerStorageProvider(
|
||||
providerName,
|
||||
(config) => {
|
||||
return new LocalStorageProvider(config as LocalConfig)
|
||||
},
|
||||
{ category: 'local' },
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,9 +13,13 @@ export default function s3StoragePlugin(options: S3StoragePluginOptions = {}): B
|
||||
name: `afilmory:storage:${providerName}`,
|
||||
hooks: {
|
||||
onInit: ({ registerStorageProvider }) => {
|
||||
registerStorageProvider(providerName, (config) => {
|
||||
return new S3StorageProvider(config as S3Config)
|
||||
})
|
||||
registerStorageProvider(
|
||||
providerName,
|
||||
(config) => {
|
||||
return new S3StorageProvider(config as S3Config)
|
||||
},
|
||||
{ category: 'remote' },
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
import type { StorageConfig, StorageProvider } from './interfaces.js'
|
||||
import type { StorageConfig, StorageProvider, StorageProviderCategory } from './interfaces.js'
|
||||
import { LOCAL_STORAGE_PROVIDERS, REMOTE_STORAGE_PROVIDERS } from './interfaces.js'
|
||||
|
||||
export type StorageProviderFactory<T extends StorageConfig = StorageConfig> = (config: T) => StorageProvider
|
||||
|
||||
export type StorageProviderRegistrationOptions = {
|
||||
category?: StorageProviderCategory
|
||||
}
|
||||
|
||||
type StorageProviderRegistration = {
|
||||
factory: StorageProviderFactory
|
||||
category: StorageProviderCategory
|
||||
}
|
||||
|
||||
const BUILTIN_PROVIDER_CATEGORY = new Map<string, StorageProviderCategory>([
|
||||
...REMOTE_STORAGE_PROVIDERS.map((provider) => [provider, 'remote'] as const),
|
||||
...LOCAL_STORAGE_PROVIDERS.map((provider) => [provider, 'local'] as const),
|
||||
])
|
||||
|
||||
export class StorageFactory {
|
||||
private static providers = new Map<string, StorageProviderFactory>()
|
||||
private static providers = new Map<string, StorageProviderRegistration>()
|
||||
|
||||
/**
|
||||
* Register or override a storage provider factory.
|
||||
*/
|
||||
static registerProvider(provider: string, factory: StorageProviderFactory): void {
|
||||
StorageFactory.providers.set(provider, factory)
|
||||
static registerProvider(
|
||||
provider: string,
|
||||
factory: StorageProviderFactory,
|
||||
options?: StorageProviderRegistrationOptions,
|
||||
): void {
|
||||
StorageFactory.providers.set(provider, {
|
||||
factory,
|
||||
category: StorageFactory.resolveCategory(provider, options),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,16 +40,36 @@ export class StorageFactory {
|
||||
* @returns 存储提供商实例
|
||||
*/
|
||||
static createProvider(config: StorageConfig): StorageProvider {
|
||||
const factory = StorageFactory.providers.get(config.provider)
|
||||
const registration = StorageFactory.providers.get(config.provider)
|
||||
|
||||
if (!factory) {
|
||||
if (!registration) {
|
||||
throw new Error(`Unsupported storage provider: ${config.provider as string}`)
|
||||
}
|
||||
|
||||
return factory(config)
|
||||
return registration.factory(config)
|
||||
}
|
||||
|
||||
static getRegisteredProviders(): string[] {
|
||||
return Array.from(StorageFactory.providers.keys())
|
||||
static getRegisteredProviders(category?: StorageProviderCategory): string[] {
|
||||
const entries = Array.from(StorageFactory.providers.entries())
|
||||
if (!category) {
|
||||
return entries.map(([provider]) => provider)
|
||||
}
|
||||
|
||||
return entries.filter(([, registration]) => registration.category === category).map(([provider]) => provider)
|
||||
}
|
||||
|
||||
static getProviderCategory(provider: string): StorageProviderCategory | null {
|
||||
return StorageFactory.providers.get(provider)?.category ?? BUILTIN_PROVIDER_CATEGORY.get(provider) ?? null
|
||||
}
|
||||
|
||||
private static resolveCategory(
|
||||
provider: string,
|
||||
options?: StorageProviderRegistrationOptions,
|
||||
): StorageProviderCategory {
|
||||
if (options?.category) {
|
||||
return options.category
|
||||
}
|
||||
|
||||
return BUILTIN_PROVIDER_CATEGORY.get(provider) ?? 'remote'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
import './providers/register.js'
|
||||
|
||||
// 导出接口
|
||||
export type { ProgressCallback, ScanProgress, StorageConfig, StorageObject, StorageProvider } from './interfaces.js'
|
||||
export type {
|
||||
LocalStorageConfig,
|
||||
LocalStorageProviderName,
|
||||
ProgressCallback,
|
||||
RemoteStorageConfig,
|
||||
RemoteStorageProviderName,
|
||||
ScanProgress,
|
||||
StorageConfig,
|
||||
StorageObject,
|
||||
StorageProvider,
|
||||
StorageProviderCategory,
|
||||
} from './interfaces.js'
|
||||
export { LOCAL_STORAGE_PROVIDERS, REMOTE_STORAGE_PROVIDERS } from './interfaces.js'
|
||||
|
||||
// 导出工厂类
|
||||
export type { StorageProviderFactory } from './factory.js'
|
||||
export type { StorageProviderFactory, StorageProviderRegistrationOptions } from './factory.js'
|
||||
export { StorageFactory } from './factory.js'
|
||||
|
||||
// 导出管理器
|
||||
|
||||
@@ -216,3 +216,14 @@ export interface CustomStorageConfig {
|
||||
}
|
||||
|
||||
export type StorageConfig = S3Config | B2Config | GitHubConfig | EagleConfig | LocalConfig | CustomStorageConfig
|
||||
|
||||
export const REMOTE_STORAGE_PROVIDERS = ['s3', 'b2', 'github'] as const
|
||||
export const LOCAL_STORAGE_PROVIDERS = ['eagle', 'local'] as const
|
||||
|
||||
export type RemoteStorageProviderName = (typeof REMOTE_STORAGE_PROVIDERS)[number]
|
||||
export type LocalStorageProviderName = (typeof LOCAL_STORAGE_PROVIDERS)[number]
|
||||
|
||||
export type StorageProviderCategory = 'remote' | 'local'
|
||||
|
||||
export type RemoteStorageConfig = Extract<StorageConfig, { provider: RemoteStorageProviderName }>
|
||||
export type LocalStorageConfig = Extract<StorageConfig, { provider: LocalStorageProviderName }>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { StorageFactory } from './factory.js'
|
||||
import type { StorageConfig, StorageObject, StorageProvider, StorageUploadOptions } from './interfaces.js'
|
||||
|
||||
export class StorageManager {
|
||||
private provider: StorageProvider
|
||||
protected provider: StorageProvider
|
||||
private readonly excludeFilters: Array<(key: string) => boolean> = []
|
||||
|
||||
constructor(config: StorageConfig) {
|
||||
|
||||
59
packages/builder/src/storage/providers/register.ts
Normal file
59
packages/builder/src/storage/providers/register.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { StorageProviderFactory } from '../factory.js'
|
||||
import { StorageFactory } from '../factory.js'
|
||||
import type {
|
||||
B2Config,
|
||||
EagleConfig,
|
||||
GitHubConfig,
|
||||
LocalConfig,
|
||||
S3Config,
|
||||
StorageProviderCategory,
|
||||
} from '../interfaces.js'
|
||||
import { B2StorageProvider } from './b2-provider.js'
|
||||
import { EagleStorageProvider } from './eagle-provider.js'
|
||||
import { GitHubStorageProvider } from './github-provider.js'
|
||||
import { LocalStorageProvider } from './local-provider.js'
|
||||
import { S3StorageProvider } from './s3-provider.js'
|
||||
|
||||
type BuiltinProviderRegistration = {
|
||||
name: string
|
||||
factory: StorageProviderFactory
|
||||
category: StorageProviderCategory
|
||||
}
|
||||
|
||||
const BUILTIN_PROVIDER_REGISTRATIONS: BuiltinProviderRegistration[] = [
|
||||
{
|
||||
name: 's3',
|
||||
category: 'remote',
|
||||
factory: (config) => new S3StorageProvider(config as S3Config),
|
||||
},
|
||||
{
|
||||
name: 'b2',
|
||||
category: 'remote',
|
||||
factory: (config) => new B2StorageProvider(config as B2Config),
|
||||
},
|
||||
{
|
||||
name: 'github',
|
||||
category: 'remote',
|
||||
factory: (config) => new GitHubStorageProvider(config as GitHubConfig),
|
||||
},
|
||||
{
|
||||
name: 'local',
|
||||
category: 'local',
|
||||
factory: (config) => new LocalStorageProvider(config as LocalConfig),
|
||||
},
|
||||
{
|
||||
name: 'eagle',
|
||||
category: 'local',
|
||||
factory: (config) => new EagleStorageProvider(config as EagleConfig),
|
||||
},
|
||||
]
|
||||
|
||||
for (const registration of BUILTIN_PROVIDER_REGISTRATIONS) {
|
||||
StorageFactory.registerProvider(registration.name, registration.factory, {
|
||||
category: registration.category,
|
||||
})
|
||||
}
|
||||
|
||||
export function getBuiltinStorageProviders(): readonly BuiltinProviderRegistration[] {
|
||||
return BUILTIN_PROVIDER_REGISTRATIONS
|
||||
}
|
||||
Reference in New Issue
Block a user