@@ -6,17 +6,20 @@ import minimist from 'minimist'
66import Client from 'bittorrent-tracker'
77import { generateKeypair } from './lib/crypto.js'
88import { createManifest , validateManifest } from './lib/manifest.js'
9+ import { ingest , reassemble } from './lib/storage.js'
910
1011const argv = minimist ( process . argv . slice ( 2 ) , {
1112 alias : {
1213 k : 'keyfile' ,
1314 t : 'tracker' ,
1415 i : 'input' ,
15- o : 'output'
16+ o : 'output' ,
17+ d : 'dir'
1618 } ,
1719 default : {
1820 keyfile : './identity.json' ,
19- tracker : 'ws://localhost:8000' // Default to local WS tracker
21+ tracker : 'ws://localhost:8000' , // Default to local WS tracker
22+ dir : './storage'
2023 }
2124} )
2225
@@ -25,12 +28,18 @@ const command = argv._[0]
2528if ( ! command ) {
2629 console . error ( `Usage:
2730 gen-key [-k identity.json]
28- publish [-k identity.json] [-t ws://tracker] -i magnet_list.txt
29- subscribe [-t ws://tracker] <public_key_hex>
31+ ingest -i <file> [-d ./storage] -> Returns FileEntry JSON
32+ publish [-k identity.json] [-t ws://tracker] -i <file_entry.json>
33+ subscribe [-t ws://tracker] <public_key_hex> [-d ./storage]
3034 ` )
3135 process . exit ( 1 )
3236}
3337
38+ // Ensure storage dir exists
39+ if ( ! fs . existsSync ( argv . dir ) ) {
40+ fs . mkdirSync ( argv . dir , { recursive : true } )
41+ }
42+
3443// 1. Generate Key
3544if ( command === 'gen-key' ) {
3645 const keypair = generateKeypair ( )
@@ -44,7 +53,27 @@ if (command === 'gen-key') {
4453 process . exit ( 0 )
4554}
4655
47- // 2. Publish
56+ // 2. Ingest
57+ if ( command === 'ingest' ) {
58+ if ( ! argv . input ) {
59+ console . error ( 'Please specify input file with -i' )
60+ process . exit ( 1 )
61+ }
62+ const fileBuf = fs . readFileSync ( argv . input )
63+ const result = ingest ( fileBuf , path . basename ( argv . input ) )
64+
65+ // Save Blobs
66+ result . blobs . forEach ( blob => {
67+ fs . writeFileSync ( path . join ( argv . dir , blob . id ) , blob . buffer )
68+ } )
69+
70+ console . log ( `Ingested ${ result . blobs . length } blobs to ${ argv . dir } ` )
71+ console . log ( 'FileEntry JSON (save this to a file to publish it):' )
72+ console . log ( JSON . stringify ( result . fileEntry , null , 2 ) )
73+ process . exit ( 0 )
74+ }
75+
76+ // 3. Publish
4877if ( command === 'publish' ) {
4978 if ( ! fs . existsSync ( argv . keyfile ) ) {
5079 console . error ( 'Keyfile not found. Run gen-key first.' )
@@ -56,23 +85,32 @@ if (command === 'publish') {
5685 secretKey : Buffer . from ( keyData . secretKey , 'hex' )
5786 }
5887
59- // Read magnet links
88+ // Read Input
6089 if ( ! argv . input ) {
61- console . error ( 'Please specify input file with -i (list of magnet links )' )
90+ console . error ( 'Please specify input file with -i (json file entry or text list )' )
6291 process . exit ( 1 )
6392 }
93+
6494 const content = fs . readFileSync ( argv . input , 'utf-8' )
65- const lines = content . split ( '\n' ) . map ( l => l . trim ( ) ) . filter ( l => l . length > 0 )
95+ let items
96+ try {
97+ // Try parsing as JSON (FileEntry)
98+ const json = JSON . parse ( content )
99+ // Wrap in our "Items" list.
100+ // In the future, "Items" can be Magnet Links OR FileEntries.
101+ items = [ json ]
102+ } catch ( e ) {
103+ // Fallback: Line-separated magnet links
104+ items = content . split ( '\n' ) . map ( l => l . trim ( ) ) . filter ( l => l . length > 0 )
105+ }
66106
67- // Create Collections structure (flat for now)
107+ // Create Collections structure
68108 const collections = [ {
69109 title : 'Default Collection' ,
70- items : lines
110+ items
71111 } ]
72112
73113 // Create Manifest
74- // TODO: Retrieve last sequence from somewhere or store it?
75- // For now, we use timestamp as sequence to be simple and monotonic
76114 const sequence = Date . now ( )
77115 const manifest = createManifest ( keypair , sequence , collections )
78116
@@ -123,7 +161,7 @@ if (command === 'publish') {
123161 } , 1000 )
124162}
125163
126- // 3 . Subscribe
164+ // 4 . Subscribe
127165if ( command === 'subscribe' ) {
128166 const pubKeyHex = argv . _ [ 1 ]
129167 if ( ! pubKeyHex ) {
@@ -166,7 +204,37 @@ if (command === 'subscribe') {
166204 if ( valid && data . manifest . publicKey === pubKeyHex ) {
167205 console . log ( 'VERIFIED UPDATE from ' + pubKeyHex )
168206 console . log ( 'Sequence:' , data . manifest . sequence )
169- console . log ( 'Items:' , data . manifest . collections [ 0 ] . items )
207+
208+ const items = data . manifest . collections [ 0 ] . items
209+ console . log ( `Received ${ items . length } items.` )
210+
211+ // Auto-Download Logic (Prototype)
212+ items . forEach ( async ( item , idx ) => {
213+ if ( typeof item === 'object' && item . chunks ) {
214+ console . log ( `Item ${ idx } : Detected Megatorrent FileEntry: ${ item . name } ` )
215+ try {
216+ const fileBuf = await reassemble ( item , async ( blobId ) => {
217+ const p = path . join ( argv . dir , blobId )
218+ if ( fs . existsSync ( p ) ) {
219+ return fs . readFileSync ( p )
220+ }
221+ // TODO: Network fetch
222+ console . log ( `Blob ${ blobId } not found locally.` )
223+ return null
224+ } )
225+
226+ if ( fileBuf ) {
227+ const outPath = path . join ( argv . dir , 'downloaded_' + item . name )
228+ fs . writeFileSync ( outPath , fileBuf )
229+ console . log ( `SUCCESS: Reassembled to ${ outPath } ` )
230+ }
231+ } catch ( err ) {
232+ console . error ( 'Failed to reassemble:' , err . message )
233+ }
234+ } else {
235+ console . log ( `Item ${ idx } : Standard Magnet/Text: ${ item } ` )
236+ }
237+ } )
170238 } else {
171239 console . error ( 'Invalid signature or wrong key!' )
172240 }
0 commit comments