-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
568084b
commit 048100f
Showing
12 changed files
with
264 additions
and
428 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { S3Client } from '@aws-sdk/client-s3'; | ||
import { gunzipSync } from 'fflate'; | ||
import { appendFileSync, writeFileSync } from 'fs'; | ||
import pLimit from 'p-limit'; | ||
import { AsyncFn, IMetricsRepository, IStorageRepository, Operation } from './interface'; | ||
import { DirectoryString, PMTilesService } from './pmtiles/pmtiles.service'; | ||
import { Compression, Directory, Header } from './pmtiles/types'; | ||
import { deserializeIndex } from './pmtiles/utils'; | ||
import { CloudflareD1Repository, MemCacheRepository, S3StorageRepository } from './repository'; | ||
|
||
class FakeMetricsRepository implements IMetricsRepository { | ||
constructor() {} | ||
|
||
monitorAsyncFunction<T extends AsyncFn>( | ||
operation: Operation, | ||
call: T, | ||
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> { | ||
return call; | ||
} | ||
push(): void {} | ||
} | ||
|
||
export function decompress(buf: ArrayBuffer, compression: Compression): ArrayBuffer { | ||
if (compression !== Compression.Gzip) { | ||
throw new Error('Compression method not supported'); | ||
} | ||
const result = gunzipSync(new Uint8Array(buf)); | ||
return result; | ||
} | ||
|
||
const getDirectory = async (length: number, offset: number, source: IStorageRepository, header: Header) => { | ||
const resp = await source.getRange({ offset, length }); | ||
const data = decompress(resp, header.internalCompression); | ||
const entries = deserializeIndex(await new Response(data).arrayBuffer()); | ||
if (entries.length === 0) { | ||
throw new Error('Empty directory is invalid'); | ||
} | ||
const directory: Directory = { offsetStart: entries[0].offset, tileIdStart: entries[0].tileId, entries }; | ||
return directory; | ||
}; | ||
|
||
const handler = async () => { | ||
const { | ||
S3_ACCESS_KEY, | ||
S3_SECRET_KEY, | ||
S3_ENDPOINT, | ||
KV_API_KEY, | ||
CLOUDFLARE_ACCOUNT_ID, | ||
KV_NAMESPACE_ID, | ||
BUCKET_KEY, | ||
DEPLOYMENT_KEY, | ||
} = process.env; | ||
if ( | ||
!S3_ACCESS_KEY || | ||
!S3_SECRET_KEY || | ||
!S3_ENDPOINT || | ||
!KV_API_KEY || | ||
!CLOUDFLARE_ACCOUNT_ID || | ||
!KV_NAMESPACE_ID || | ||
!BUCKET_KEY || | ||
!DEPLOYMENT_KEY | ||
) { | ||
throw new Error('Missing environment variables'); | ||
} | ||
|
||
console.log('Starting S3'); | ||
const client = new S3Client({ | ||
region: 'auto', | ||
endpoint: S3_ENDPOINT, | ||
credentials: { | ||
accessKeyId: S3_ACCESS_KEY, | ||
secretAccessKey: S3_SECRET_KEY, | ||
}, | ||
}); | ||
|
||
const storageRepository = new S3StorageRepository(client, BUCKET_KEY, DEPLOYMENT_KEY); | ||
const memCacheRepository = new MemCacheRepository(new Map()); | ||
const metricsRepository = new FakeMetricsRepository(); | ||
const pmTilesService = await PMTilesService.init( | ||
storageRepository, | ||
memCacheRepository, | ||
metricsRepository, | ||
null as unknown as CloudflareD1Repository, | ||
); | ||
|
||
const [header, root] = await pmTilesService.getHeaderAndRootFromSource(); | ||
let countR2 = 0; | ||
let total = 0; | ||
const promises: Promise<void>[] = []; | ||
const limit = pLimit(10); | ||
|
||
let entryCount = 0; | ||
|
||
const createTableStatement = `CREATE TABLE IF NOT EXISTS cache_entries ( | ||
startTileId INTEGER NOT NULL PRIMARY KEY, | ||
entry TEXT NOT NULL | ||
) STRICT;`; | ||
|
||
writeFileSync('cache_entries.sql', createTableStatement); | ||
|
||
for (const entry of root.entries) { | ||
const call = async () => { | ||
const directory = await getDirectory( | ||
entry.length, | ||
entry.offset + header.leafDirectoryOffset, | ||
storageRepository, | ||
header, | ||
); | ||
|
||
entryCount += directory.entries.length; | ||
|
||
console.log('Entry Progress: ' + entryCount); | ||
|
||
const totalChunks = Math.ceil(directory.entries.length / 50); | ||
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { | ||
const chunkEntries = directory.entries.slice(chunkIndex * 50, (chunkIndex + 1) * 50); | ||
const directoryChunk = { | ||
offsetStart: chunkEntries[0].offset, | ||
tileIdStart: chunkEntries[0].tileId, | ||
entries: chunkEntries, | ||
}; | ||
|
||
const startTileId = chunkEntries[0].tileId; | ||
|
||
const stream = DirectoryString.fromDirectory(directoryChunk); | ||
const entryValue = await stream.toString(); | ||
|
||
const insertStatement = `\nINSERT INTO cache_entries (startTileId, entry) VALUES (${startTileId}, '${entryValue}');`; | ||
appendFileSync(`cache_entries.${Math.floor(countR2 / 50)}.sql`, insertStatement); | ||
entryCount++; | ||
} | ||
|
||
countR2++; | ||
console.log('R2 Progress: ' + countR2 + '/' + total); | ||
}; | ||
promises.push(limit(call)); | ||
total++; | ||
} | ||
|
||
await Promise.all(promises); | ||
}; | ||
|
||
process.on('uncaughtException', (e) => { | ||
console.error('UNCAUGHT EXCEPTION'); | ||
console.error('stack', e); | ||
process.exit(1); | ||
}); | ||
|
||
handler() | ||
.then(() => console.log('Done')) | ||
.catch((e) => { | ||
console.error('Error', e); | ||
throw e; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.