Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39551
1
###
2
(c) SageMath, Inc. 2016-2017
3
AGPLv3
4
###
5
6
{React, ReactDOM, rclass, rtypes, Redux, Actions, Store} = require('./smc-react')
7
{Button, Panel, Row, Col} = require('react-bootstrap')
8
{ErrorDisplay, Icon} = require('./r_misc')
9
{webapp_client} = require('./webapp_client')
10
{filename_extension} = require('smc-util/misc')
11
async = require('async')
12
misc = require('smc-util/misc')
13
14
COMMANDS =
15
zip :
16
list :
17
command : 'unzip'
18
args : ['-l']
19
extract :
20
command : 'unzip'
21
args : ['-B']
22
tar :
23
list :
24
command : 'tar'
25
args : ['-tf']
26
extract :
27
command : 'tar'
28
args : ['-xvf']
29
gz :
30
list :
31
command : 'gzip'
32
args : ['-l']
33
extract :
34
command : 'gunzip'
35
args : ['-vf']
36
bzip2 :
37
list :
38
command : 'ls'
39
args : ['-l']
40
extract :
41
command : 'bunzip2'
42
args : ['-vf']
43
lzip :
44
list :
45
command : 'ls'
46
args : ['-l']
47
extract :
48
command : 'lzip'
49
args : ['-vfd']
50
xz :
51
list :
52
command : 'xz'
53
args : ['-l']
54
extract :
55
command : 'xz'
56
args : ['-vfd']
57
58
COMMANDS.bz2 = COMMANDS.bzip2
59
60
redux_name = (project_id, path) ->
61
return "editor-#{project_id}-#{path}"
62
63
init_redux = (path, redux, project_id) ->
64
name = redux_name(project_id, path)
65
if redux.getActions(name)?
66
return # already initialized
67
actions = redux.createActions(name, ArchiveActions)
68
store = redux.createStore(name)
69
return name
70
71
remove_redux = (path, redux, project_id) ->
72
name = redux_name(project_id, path)
73
redux.removeActions(name)
74
redux.removeStore(name)
75
return name
76
77
class ArchiveActions extends Actions
78
parse_file_type: (file_info) =>
79
if file_info.indexOf('Zip archive data') != -1
80
return 'zip'
81
else if file_info.indexOf('tar archive') != -1
82
return 'tar'
83
else if file_info.indexOf('gzip compressed data') != -1
84
return 'gz'
85
else if file_info.indexOf('bzip2 compressed data') != -1
86
return 'bzip2'
87
else if file_info.indexOf('lzip compressed data') != -1
88
return 'lzip'
89
else if file_info.indexOf('XZ compressed data') != -1
90
return 'xz'
91
return undefined
92
93
clear_error: =>
94
@setState(error: undefined)
95
96
set_unsupported: (ext) =>
97
@setState
98
error : <span> <b>WARNING:</b> Support for decompressing {ext} archives is not yet implemented (see <a href='https://github.com/sagemathinc/cocalc/issues/1720' target='_blank'>https://github.com/sagemathinc/cocalc/issues/1720</a>).</span>
99
contents : ''
100
type : ext
101
102
set_archive_contents: (project_id, path) =>
103
ext = filename_extension(path)?.toLowerCase()
104
if not COMMANDS[ext]?.list?
105
@set_unsupported(ext)
106
return
107
108
{command, args} = COMMANDS[ext].list
109
110
webapp_client.exec
111
project_id : project_id
112
command : command
113
args : args.concat([path])
114
err_on_exit: true
115
cb : (err, output) =>
116
@setState
117
error : if err then <pre>{err}</pre>
118
contents : output?.stdout
119
type : ext
120
121
extract_archive_files: (project_id, path, type, contents) =>
122
if not COMMANDS[type]?.extract?
123
@set_unsupported(type)
124
return
125
{command, args} = COMMANDS[type].extract
126
path_parts = misc.path_split(path)
127
extra_args = post_args = []
128
output = undefined
129
@setState(loading: true)
130
async.series([
131
(cb) =>
132
if not contents?
133
cb("Archive not loaded yet")
134
else if type == 'zip'
135
# special case for zip files: if heuristically it looks like not everything is contained
136
# in a subdirectory with name the zip file, then create that subdirectory.
137
base = path_parts.tail.slice(0, path_parts.tail.length - 4)
138
if contents.indexOf(base+'/') == -1
139
extra_args = ['-d', base]
140
cb()
141
else if type == 'tar'
142
# special case for tar files: if heuristically it looks like not everything is contained
143
# in a subdirectory with name the tar file, then create that subdirectory.
144
i = path_parts.tail.lastIndexOf('.t') # hopefully that's good enough.
145
base = path_parts.tail.slice(0, i)
146
if contents.indexOf(base+'/') == -1
147
post_args = ['-C', base]
148
webapp_client.exec
149
project_id : project_id
150
path : path_parts.head
151
command : "mkdir"
152
args : ['-p', base]
153
error_on_exit : true
154
cb : cb
155
else
156
cb()
157
else
158
cb()
159
(cb) =>
160
args = args.concat(extra_args ? []).concat([path_parts.tail]).concat(post_args)
161
args_str = ((if x.indexOf(' ')!=-1 then "'#{x}'" else x) for x in args).join(' ')
162
cmd = "cd \"#{path_parts.head}\" ; #{command} #{args_str}" # ONLY for info purposes -- not actually run!
163
@setState(command: cmd)
164
webapp_client.exec
165
project_id : project_id
166
path : path_parts.head
167
command : command
168
args : args
169
err_on_exit: true
170
timeout : 120
171
cb : (err, _output) =>
172
output = _output
173
cb(err)
174
], (err) =>
175
@setState
176
error : err
177
extract_output : output?.stdout
178
loading : false
179
)
180
181
ArchiveContents = rclass
182
propTypes:
183
path : rtypes.string.isRequired
184
project_id : rtypes.string.isRequired
185
actions : rtypes.object.isRequired
186
contents : rtypes.string
187
188
render: ->
189
if not @props.contents?
190
@props.actions.set_archive_contents(@props.project_id, @props.path)
191
<pre>{@props.contents}</pre>
192
193
194
Archive = rclass ({name}) ->
195
reduxProps:
196
"#{name}" :
197
contents : rtypes.string
198
info : rtypes.string
199
type : rtypes.string
200
loading : rtypes.bool
201
command : rtypes.string
202
error : rtypes.any
203
extract_output : rtypes.string
204
205
propTypes:
206
actions : rtypes.object.isRequired
207
path : rtypes.string.isRequired
208
project_id : rtypes.string.isRequired
209
210
title: ->
211
<tt><Icon name="file-zip-o" /> {@props.path}</tt>
212
213
extract_archive_files: ->
214
@props.actions.extract_archive_files(@props.project_id, @props.path, @props.type, @props.contents)
215
216
render_button_icon: ->
217
if @props.loading
218
<Icon name='cc-icon-cocalc-ring' spin={true} />
219
else
220
<Icon name='folder'/>
221
222
render_button: ->
223
<Button
224
disabled = {!!@props.error or @props.loading}
225
bsSize = 'large'
226
bsStyle = 'success'
227
onClick = {@extract_archive_files}>
228
{@render_button_icon()} Extract Files...
229
</Button>
230
231
render_error: ->
232
if @props.error
233
<div>
234
<br />
235
<ErrorDisplay
236
error_component = {@props.error}
237
style = {maxWidth: '100%'}
238
onClose = {@props.actions.clear_error}
239
/>
240
</div>
241
242
render_contents: ->
243
if @props.error
244
return
245
<div>
246
<h2>Contents</h2>
247
248
{@props.info}
249
<ArchiveContents path={@props.path} contents={@props.contents} actions={@props.actions} project_id={@props.project_id} />
250
</div>
251
252
render_command: ->
253
if @props.command
254
<pre style={marginTop:'15px'}>{@props.command}</pre>
255
256
render_extract_output: ->
257
if @props.extract_output
258
<pre style={marginTop:'15px'}>{@props.extract_output}</pre>
259
260
render: ->
261
<Panel header={@title()}>
262
{@render_button()}
263
{@render_command()}
264
{@render_extract_output()}
265
{@render_error()}
266
{@render_contents()}
267
</Panel>
268
269
# TODO: change ext below to use misc.keys(COMMANDS). We don't now, since there are a
270
# ton of extensions that shoud open in the archive editor, but aren't implemented
271
# yet and we don't want to open those in codemirror -- see https://github.com/sagemathinc/cocalc/issues/1720
272
TODO_TYPES = misc.split('z lz lzma tgz tbz tbz2 tb2 taz tz tlz txz')
273
require('project_file').register_file_editor
274
ext : misc.keys(COMMANDS).concat(TODO_TYPES)
275
icon : 'file-archive-o'
276
init : init_redux
277
component : Archive
278
remove : remove_redux
279
280