1
1
const { extname, basename } = require ( 'path' )
2
2
const { clipboard } = require ( 'electron' )
3
+ const { globSource } = require ( 'ipfs-http-client' )
3
4
const i18n = require ( 'i18next' )
5
+ const last = require ( 'it-last' )
6
+ const fs = require ( 'fs-extra' )
4
7
const logger = require ( './common/logger' )
5
8
const { notify, notifyError } = require ( './common/notify' )
6
- const { globSource } = require ( 'ipfs-http-client' )
7
9
8
- async function copyFile ( ipfs , cid , name ) {
10
+ async function copyFileToMfs ( ipfs , cid , filename ) {
9
11
let i = 0
10
- const ext = extname ( name )
11
- const base = basename ( name , ext )
12
+ const ext = extname ( filename )
13
+ const base = basename ( filename , ext )
12
14
13
15
while ( true ) {
14
16
const newName = ( i === 0 ? base : `${ base } (${ i } )` ) + ext
15
17
16
18
try {
17
19
await ipfs . files . stat ( `/${ newName } ` )
18
20
} catch ( err ) {
19
- name = newName
21
+ filename = newName
20
22
break
21
23
}
22
24
23
25
i ++
24
26
}
25
27
26
- return ipfs . files . cp ( `/ipfs/${ cid . toString ( ) } ` , `/${ name } ` )
28
+ return ipfs . files . cp ( `/ipfs/${ cid . toString ( ) } ` , `/${ filename } ` )
27
29
}
28
30
29
- async function makeShareableObject ( ipfs , results ) {
30
- if ( results . length === 1 ) {
31
+ async function getShareableCid ( ipfs , files ) {
32
+ if ( files . length === 1 ) {
31
33
// If it's just one object, we link it directly.
32
- return results [ 0 ]
34
+ return files [ 0 ]
33
35
}
34
36
35
- let baseCID = await ipfs . object . new ( { template : 'unixfs-dir' } )
37
+ // Note: we don't use 'object patch' here, it was deprecated.
38
+ // We are using MFS for creating CID of an ephemeral directory
39
+ // because it handles HAMT-sharding of big directories automatically
40
+ // See: https://github.com/ipfs/go-ipfs/issues/8106
41
+ const dirpath = `/zzzz_${ Date . now ( ) } `
42
+ await ipfs . files . mkdir ( dirpath , { } )
36
43
37
- for ( const { cid, path, size } of results ) {
38
- baseCID = ( await ipfs . object . patch . addLink ( baseCID , {
39
- name : path ,
40
- size,
41
- cid
42
- } ) )
44
+ for ( const { cid, filename } of files ) {
45
+ await ipfs . files . cp ( `/ipfs/${ cid } ` , `${ dirpath } /${ filename } ` )
43
46
}
44
47
45
- return { cid : baseCID , path : '' }
48
+ const stat = await ipfs . files . stat ( dirpath )
49
+
50
+ // Do not wait for this
51
+ ipfs . files . rm ( dirpath , { recursive : true } )
52
+
53
+ return { cid : stat . cid , filename : '' }
46
54
}
47
55
48
- function sendNotification ( failures , successes , launchWebUI , path ) {
56
+ function sendNotification ( launchWebUI , hasFailures , successCount , filename ) {
49
57
let link , title , body , fn
50
58
51
- if ( failures . length === 0 ) {
59
+ if ( ! hasFailures ) {
52
60
// All worked well!
53
61
fn = notify
54
62
55
- if ( successes . length === 1 ) {
56
- link = `/files/${ path } `
63
+ if ( successCount === 1 ) {
64
+ link = `/files/${ filename } `
57
65
title = i18n . t ( 'itemAddedNotification.title' )
58
66
body = i18n . t ( 'itemAddedNotification.message' )
59
67
} else {
60
68
link = '/files'
61
69
title = i18n . t ( 'itemsAddedNotification.title' )
62
- body = i18n . t ( 'itemsAddedNotification.message' , { count : successes . length } )
70
+ body = i18n . t ( 'itemsAddedNotification.message' , { count : successCount } )
63
71
}
64
72
} else {
65
73
// Some/all failed!
@@ -75,9 +83,27 @@ function sendNotification (failures, successes, launchWebUI, path) {
75
83
} )
76
84
}
77
85
86
+ async function addFileOrDirectory ( ipfs , filepath ) {
87
+ const stat = fs . statSync ( filepath )
88
+ let cid = null
89
+
90
+ if ( stat . isDirectory ( ) ) {
91
+ const files = globSource ( filepath , '**/*' , { recursive : true } )
92
+ const res = await last ( ipfs . addAll ( files , { pin : false , wrapWithDirectory : true } ) )
93
+ cid = res . cid
94
+ } else {
95
+ const readStream = fs . createReadStream ( filepath )
96
+ const res = await ipfs . add ( readStream , { pin : false } )
97
+ cid = res . cid
98
+ }
99
+
100
+ const filename = basename ( filepath )
101
+ await copyFileToMfs ( ipfs , cid , filename )
102
+ return { cid, filename }
103
+ }
104
+
78
105
module . exports = async function ( { getIpfsd, launchWebUI } , files ) {
79
106
const ipfsd = await getIpfsd ( )
80
-
81
107
if ( ! ipfsd ) {
82
108
return
83
109
}
@@ -89,23 +115,26 @@ module.exports = async function ({ getIpfsd, launchWebUI }, files) {
89
115
90
116
await Promise . all ( files . map ( async file => {
91
117
try {
92
- const result = await ipfsd . api . add ( globSource ( file , { recursive : true } ) , { pin : false } )
93
- await copyFile ( ipfsd . api , result . cid , result . path )
94
- successes . push ( result )
118
+ const res = await addFileOrDirectory ( ipfsd . api , file )
119
+ successes . push ( res )
95
120
} catch ( e ) {
96
- failures . push ( e )
121
+ failures . push ( e . toString ( ) )
97
122
}
98
123
} ) )
99
124
100
125
if ( failures . length > 0 ) {
101
- log . fail ( new Error ( failures . reduce ( ( prev , curr ) => ` ${ prev } ${ curr . toString ( ) } ` , ' ') ) )
126
+ log . fail ( new Error ( failures . join ( '\n ') ) )
102
127
} else {
103
128
log . end ( )
104
129
}
105
130
106
- const { cid, path } = await makeShareableObject ( ipfsd . api , successes )
107
- sendNotification ( failures , successes , launchWebUI , path )
108
- const filename = path ? `?filename=${ encodeURIComponent ( path . split ( '/' ) . pop ( ) ) } ` : ''
109
- const url = `https://dweb.link/ipfs/${ cid . toString ( ) } ${ filename } `
131
+ const { cid, filename } = await getShareableCid ( ipfsd . api , successes )
132
+ sendNotification ( launchWebUI , failures . length !== 0 , successes . length , filename )
133
+
134
+ const query = filename ? `?filename=${ encodeURIComponent ( filename ) } ` : ''
135
+ const url = `https://dweb.link/ipfs/${ cid . toString ( ) } ${ query } `
136
+
110
137
clipboard . writeText ( url )
138
+
139
+ return cid
111
140
}
0 commit comments