File: unpack.lua

package info (click to toggle)
vifm 0.14.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 14,252 kB
  • sloc: ansic: 179,567; sh: 5,445; makefile: 723; perl: 347; python: 76; xml: 26
file content (252 lines) | stat: -rw-r--r-- 7,518 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
local function get_common_unpack_prefix(archive, format) -- <<<
   -- cmd should output the list of contents in the archive
   -- directory must contain '/' at end. adjust cmd accordingly
   local cmd
   if format == 'tar' then
      -- this is slow in comparison to 7z. noticably slow
      -- when large archives or archives with lots of files
      cmd = string.format("tar --force-local -tf %s", vifm.escape(archive))
   elseif format == 'zip' or format == 'rar' or format == '7z' then
      cmd = string.format("7z -ba l %s | awk %s",
                          vifm.escape(archive),
                          vifm.escape('{($3 ~ /^D/) ? $0=$0"/" : $0; print substr($0,54) }'))
   else
      return nil, 'unsupported format: '..format
   end

   local job = vifm.startjob { cmd = cmd }
   local prefix
   local prefix_len
   for line in job:stdout():lines() do
      -- this conversion is really just for Windows, but there is currently no
      -- way of checking whether we're running on Windows
      --
      -- Unix systems can allow slashes in file names in which case we might end
      -- up computing incorrect prefix, but it's a highly unlikely corner case
      line = line:gsub('\\', '/')

      vifm.sb.quick("Checking: "..line)
      if prefix == nil then
         prefix = line
         local top = prefix:match("(.-/)")
         if top ~= nil then
            prefix = top
         end
         prefix_len = #prefix
      end
      if line:sub(1, prefix_len) ~= prefix then
         job:wait()
         return nil
      end
   end

   if prefix ~= nil then
      if prefix:sub(-1) ~= '/' then
         prefix = nil
      else
         prefix = prefix:sub(1, #prefix - 1)
         if prefix == '.' then
            prefix = nil
         end
      end
   end

   local exitcode = job:exitcode()
   if exitcode ~= 0 then
      print('Listing failed with exit code: '..exitcode)
      return prefix, 'errors: '..job:errors()
   end

   return prefix
end -- >>>

local function unpack_archive(archive, target, onexit) -- <<<
   local fpath = archive
   local fname = vifm.fnamemodify(fpath, ':t')
   local fdir = vifm.fnamemodify(fpath, ':h')

   local ext
   local cmp
   ext = vifm.fnamemodify(fname, ':e')
   -- if tarball; ext -> 'tar'
   if ext == 'tgz' or
         ext == 'txz' or
         ext == 'tbz2' or
         ext == 'tzst' or
         vifm.fnamemodify(fname, ':r:e') == 'tar' then
      cmp = ext
      ext = 'tar'
   end

   local outdir = target or fdir
   if not vifm.exists(outdir) then
      vifm.errordialog(":Unpack", "Error: output directory does not exists")
      return
   end

   local prefix, err = get_common_unpack_prefix(fpath, ext)
   if err ~= nil then
      vifm.sb.error(err)
      return
   end

   if prefix == nil then
      if ext == 'tar' then
         outdir = string.format("%s/%s", outdir, vifm.fnamemodify(fname, ':r:r'))
      else
         outdir = string.format("%s/%s", outdir, vifm.fnamemodify(fname, ':r'))
      end
      if vifm.exists(outdir) then
         local msg = string.format("Output directory already exists:\n \n\"%s\"", outdir)
         vifm.errordialog(":Unpack", msg)
         return
      end
      if not vifm.makepath(outdir) then
         local msg = string.format('Failed to create output directory:\n \n\"%s\"', outdir)
         vifm.errordialog(":Unpack", msg)
         return
      end
   else
      if vifm.exists(string.format("%s/%s", outdir, prefix)) then
         local msg = string.format("Prefix directory already exists:\n \n\"%s/%s\"", outdir, prefix)
         vifm.errordialog(":Unpack", msg)
         return
      end
   end

   local eoutdir = vifm.escape(outdir)
   local efpath = vifm.escape(fpath)

   local cmd
   if ext == 'tar' then
      if cmp == "tgz" or cmp == "gz" then
         cmd = string.format('tar --force-local -C %s -vxzf %s', eoutdir, efpath)
      elseif cmp == "tbz2" or cmp == "bz2" then
         cmd = string.format('tar --force-local -C %s -vxjf %s', eoutdir, efpath)
      elseif cmp == "txz" or cmp == "xz" then
         cmd = string.format('tar --force-local -C %s -vxJf %s', eoutdir, efpath)
      elseif cmp == "tzst" or cmp == "zst" then
         cmd = string.format("tar --force-local -C %s -I 'zstd -d' -vxf %s", eoutdir, efpath)
      else
         vifm.sb.error("Error: unknown compression format '"..cmp.."'")
         return
      end
   elseif ext == 'zip' or ext == 'rar' or ext == '7z' or ext == 'lz4' then
      cmd = string.format("cd %s && 7z -bd x %s", eoutdir, efpath)
   end

   return vifm.startjob {
      cmd = cmd,
      description = "Unpacking: "..fname,
      -- don't show on the job bar if running in foreground
      visible = onexit ~= nil,
      -- ignore output to not block a background task
      iomode = onexit and '' or 'r',
      onexit = onexit,
   }
end -- >>>

local function add_to_selection(selection, entry)
   if entry.type == 'exe' or entry.type == 'reg' or entry.type == 'link' then
      local path = string.format('%s/%s', entry.location, entry.name)
      table.insert(selection, path)
   end
end

local function get_selected_paths(view)
   local selection = { }
   local has_selection = false

   for i = 1, view.entrycount do
      local entry = view:entry(i)
      if entry.selected then
         has_selection = true
         add_to_selection(selection, entry)
      end
   end

   if not has_selection then
      add_to_selection(selection, view:entry(view.currententry))
   end

   return selection
end

local function report_result(job)
   if job:exitcode() ~= 0 then
      local errors = job:errors()
      -- TODO: need to report archive name here
      if #errors == 0 then
         vifm.errordialog('Unpacking failed', 'Error message is not available.')
      else
         vifm.errordialog('Unpacking failed', errors)
      end
   end
end

function unpack_next_in_bg(state)
   if state.current < #state.archives then
      state.current = state.current + 1
      unpack_in_bg(state)
   end
end

function unpack_in_bg(state)
   local onexit = function(job)
      report_result(job)
      unpack_next_in_bg(state)
   end

   local archive = state.archives[state.current]
   if not unpack_archive(archive, state.target, onexit) then
      -- the callback won't run due to an error, so schedule next task right
      -- away
      unpack_next_in_bg(state)
   end
end

function unpack(info) -- <<<
   local archives = get_selected_paths(vifm.currview())
   if #archives == 0 then
      vifm.sb.error('Error: no file object under cursor')
      return
   end

   local targetidx = 1
   local bg = false
   if info.argv[1] == '-b' then
      bg = true
      targetidx = 2
   elseif #info.argv == 2 then
      local msg = string.format('Error: unexpected option: %s', info.argv[1])
      vifm.sb.error(msg)
      return
   end

   local target
   if targetidx <= #info.argv then
      -- TODO: vifm.expand() doesn't expand '~', find way to fix
      target = vifm.fnamemodify(unescape_name(vifm.expand(info.argv[targetidx])), ':p')
   end

   if bg then
      local state = {
         archives = archives,
         target = target,
         current = 1,
      }
      unpack_in_bg(state)
   else
      for _, archive in ipairs(archives) do
         local job = unpack_archive(archive, target, nil)
         if job then
            for line in job:stdout():lines() do
               vifm.sb.quick("Extracting: "..line)
            end
            report_result(job)
         end
      end
   end
end -- >>>

-- vim: set et ts=3 sts=3 sw=3: