Module: Rfd::FileOps
- Included in:
- Controller
- Defined in:
- lib/rfd/file_ops.rb
Constant Summary collapse
- SORT_KEYS =
{ 's' => :size, 'S' => :size, 't' => :mtime, 'c' => :ctime, 'u' => :atime, 'e' => :extname }.freeze
Instance Method Summary collapse
-
#cd(dir = '~', pushd: true) ⇒ Object
Change the current directory.
-
#chmod(mode = nil) ⇒ Object
Change the file permission of the selected files and directories.
-
#chown(user_and_group) ⇒ Object
Change the file owner of the selected files and directories.
-
#clipboard ⇒ Object
Copy selected files and directories’ path into clipboard.
-
#cp(dest) ⇒ Object
Copy selected files and directories to the destination.
-
#delete ⇒ Object
Delete selected files and directories.
-
#fetch_items_from_filesystem_or_zip ⇒ Object
Fetch files from current directory or current .zip file.
-
#grep(pattern = '.*') ⇒ Object
Search files and directories from the current directory, and update the screen.
-
#ls ⇒ Object
Fetch files from current directory.
-
#mkdir(dir) ⇒ Object
Create a new directory.
-
#mv(dest) ⇒ Object
Move selected files and directories to the destination.
-
#paste ⇒ Object
Paste yanked files / directories here.
-
#popd ⇒ Object
cd to the previous directory.
-
#rename(pattern) ⇒ Object
Rename selected files and directories.
-
#sort(direction = nil) ⇒ Object
Sort the whole files and directories in the current directory, then refresh the screen.
-
#sort_items_according_to_current_direction ⇒ Object
Sort the loaded files and directories in already given sort order.
-
#symlink(name) ⇒ Object
Create a symlink to the current file or directory.
-
#touch(filename) ⇒ Object
Create a new empty file.
-
#touch_t(timestamp) ⇒ Object
Change the timestamp of the selected files and directories.
-
#trash ⇒ Object
Soft delete selected files and directories.
-
#unarchive ⇒ Object
Unarchive .zip and .tar.gz files within selected files and directories into current_directory.
-
#yank ⇒ Object
Yank selected file / directory names.
-
#zip(zipfile_name) ⇒ Object
Archive selected files and directories into a .zip file.
Instance Method Details
#cd(dir = '~', pushd: true) ⇒ Object
Change the current directory.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/rfd/file_ops.rb', line 6 def cd(dir = '~', pushd: true) dir = load_item path: (dir) unless dir.is_a? Item unless dir.zip? Dir.chdir dir @current_zip = nil else @current_zip = dir end if current_dir && pushd @dir_history << current_dir @dir_history.shift if @dir_history.size > 100 end @current_dir, @current_page, @current_row = dir, 0, nil main.activate_pane 0 ls @current_dir rescue Errno::EACCES, Errno::ENOENT => e command_line.show_error e. nil end |
#chmod(mode = nil) ⇒ Object
Change the file permission of the selected files and directories.
Parameters
-
mode- Unix chmod string (e.g. +w, g-r, 755, 0644)
75 76 77 78 79 80 81 82 83 84 |
# File 'lib/rfd/file_ops.rb', line 75 def chmod(mode = nil) return unless mode begin Integer mode mode = Integer mode.size == 3 ? "0#{mode}" : mode rescue ArgumentError end FileUtils.chmod mode, selected_items.map(&:path) ls end |
#chown(user_and_group) ⇒ Object
Change the file owner of the selected files and directories.
Parameters
-
user_and_group- user name and group name separated by : (e.g. alice, nobody:nobody, :admin)
90 91 92 93 94 95 |
# File 'lib/rfd/file_ops.rb', line 90 def chown(user_and_group) return unless user_and_group user, group = user_and_group.split(':').map {|s| s == '' ? nil : s} FileUtils.chown user, group, selected_items.map(&:path) ls end |
#clipboard ⇒ Object
Copy selected files and directories’ path into clipboard.
340 341 342 343 |
# File 'lib/rfd/file_ops.rb', line 340 def clipboard cmd = clipboard_command IO.popen(cmd, 'w') {|f| f << selected_items.map(&:path).join(' ')} if cmd end |
#cp(dest) ⇒ Object
Copy selected files and directories to the destination.
168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/rfd/file_ops.rb', line 168 def cp(dest) unless in_zip? src = (m = marked_items).any? ? m.map(&:path) : current_item FileUtils.cp_r src, (dest) else raise 'cping multiple items in .zip is not supported.' if selected_items.size > 1 Zip::File.open(current_zip) do |zip| entry = zip.find_entry(selected_items.first.name).dup entry.name, entry.name_length = dest, dest.size zip.instance_variable_get(:@entry_set) << entry end end ls end |
#delete ⇒ Object
Delete selected files and directories.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/rfd/file_ops.rb', line 250 def delete unless in_zip? FileUtils.rm_rf selected_items.map(&:path) else Zip::File.open(current_zip) do |zip| zip.select {|e| selected_items.map(&:name).include? e.to_s}.each do |entry| if entry.name_is_directory? zip.dir.delete entry.to_s else zip.file.delete entry.to_s end end end end @current_row -= selected_items.count {|i| i.index <= current_row} ls end |
#fetch_items_from_filesystem_or_zip ⇒ Object
Fetch files from current directory or current .zip file.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/rfd/file_ops.rb', line 98 def fetch_items_from_filesystem_or_zip unless in_zip? dot = load_item(dir: current_dir, name: '.') dotdot = load_item(dir: current_dir, name: '..') @items = [dot, dotdot, *Dir.children(current_dir).map { |fn| load_item dir: current_dir, name: fn }] else @items = [load_item(dir: current_dir, name: '.', stat: File.stat(current_dir)), load_item(dir: current_dir, name: '..', stat: File.stat(File.dirname(current_dir)))] Zip::File.open(current_dir) do |zf| zf.each do |entry| next if entry.name_is_directory? stat = zf.file.stat entry.name @items << load_item(dir: current_dir, name: entry.name, stat: stat) end end end rescue Errno::EACCES => e command_line.show_error e. @items ||= [] rescue Zip::Error => e command_line.show_error "ZIP error: #{e.message}" end |
#grep(pattern = '.*') ⇒ Object
Search files and directories from the current directory, and update the screen.
-
pattern- Search pattern against file names in Ruby Regexp string.
Example
a : Search files that contains the letter “a” in their file name .*.pdf$ : Search PDF files
153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/rfd/file_ops.rb', line 153 def grep(pattern = '.*') regexp = Regexp.new(pattern) fetch_items_from_filesystem_or_zip @items = items.shift(2) + items.select {|i| i.name =~ regexp} sort_items_according_to_current_direction draw_items draw_total_items move_cursor rescue RegexpError => e command_line.show_error "Invalid regex: #{e.message}" switch_page 0 move_cursor end |
#ls ⇒ Object
Fetch files from current directory. Then update each windows reflecting the newest information.
34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/rfd/file_ops.rb', line 34 def ls fetch_items_from_filesystem_or_zip sort_items_according_to_current_direction @current_page ||= 0 draw_items move_cursor (current_row ? [current_row, items.size - 1].min : nil) draw_marked_items draw_total_items true end |
#mkdir(dir) ⇒ Object
Create a new directory.
269 270 271 272 273 274 275 276 277 278 |
# File 'lib/rfd/file_ops.rb', line 269 def mkdir(dir) unless in_zip? FileUtils.mkdir_p current_dir.join(dir) else Zip::File.open(current_zip) do |zip| zip.dir.mkdir dir end end ls end |
#mv(dest) ⇒ Object
Move selected files and directories to the destination.
184 185 186 187 188 189 190 191 192 193 |
# File 'lib/rfd/file_ops.rb', line 184 def mv(dest) unless in_zip? src = (m = marked_items).any? ? m.map(&:path) : current_item FileUtils.mv src, (dest) else raise 'mving multiple items in .zip is not supported.' if selected_items.size > 1 rename "#{selected_items.first.name}/#{dest}" end ls end |
#paste ⇒ Object
Paste yanked files / directories here.
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/rfd/file_ops.rb', line 316 def paste if @yanked_items if current_item.directory? FileUtils.cp_r @yanked_items.map(&:path), current_item else @yanked_items.each do |item| if items.include? item i = 0 while i += 1 new_path = current_dir.join("#{item.basename}_#{i}#{item.extname}") break unless File.exist? new_path end new_item = new_path FileUtils.cp_r item, new_item else FileUtils.cp_r item, current_dir end end end ls end end |
#popd ⇒ Object
cd to the previous directory.
28 29 30 |
# File 'lib/rfd/file_ops.rb', line 28 def popd cd @dir_history.pop, pushd: false if @dir_history.any? end |
#rename(pattern) ⇒ Object
Rename selected files and directories.
Parameters
-
pattern- new filename, or a shash separated Regexp like string
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/rfd/file_ops.rb', line 199 def rename(pattern) from, to = pattern.sub(/^\//, '').sub(/\/$/, '').split '/' if to.nil? from, to = current_item.name, from else from = Regexp.new from end unless in_zip? selected_items.each do |item| name = item.name.gsub from, to FileUtils.mv item, current_dir.join(name) if item.name != name end else Zip::File.open(current_zip) do |zip| selected_items.each do |item| name = item.name.gsub from, to zip.rename item.name, name end end end ls rescue RegexpError => e command_line.show_error "Invalid regex: #{e.message}" end |
#sort(direction = nil) ⇒ Object
Sort the whole files and directories in the current directory, then refresh the screen.
Parameters
-
direction- Sort order in a String.nil : order by name r : reverse order by name s, S : order by file size sr, Sr: reverse order by file size t : order by mtime tr : reverse order by mtime c : order by ctime cr : reverse order by ctime u : order by atime ur : reverse order by atime e : order by extname er : reverse order by extname
63 64 65 66 67 68 69 |
# File 'lib/rfd/file_ops.rb', line 63 def sort(direction = nil) @direction, @current_page = direction, 0 sort_items_according_to_current_direction draw_items switch_page 0 move_cursor end |
#sort_items_according_to_current_direction ⇒ Object
Sort the loaded files and directories in already given sort order.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/rfd/file_ops.rb', line 127 def sort_items_according_to_current_direction dots = items.shift(2) reverse = @direction&.end_with?('r') key = SORT_KEYS[@direction&.sub(/r$/, '')] sorted = items.partition(&:directory?).flat_map do |arr| if key sorted_arr = arr.sort_by(&key) reverse ? sorted_arr : sorted_arr.reverse else reverse ? arr.sort.reverse : arr.sort end end @items = dots + sorted items.each.with_index {|item, index| item.index = index} end |
#symlink(name) ⇒ Object
Create a symlink to the current file or directory.
296 297 298 299 |
# File 'lib/rfd/file_ops.rb', line 296 def symlink(name) FileUtils.ln_s current_item, name ls end |
#touch(filename) ⇒ Object
Create a new empty file.
281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/rfd/file_ops.rb', line 281 def touch(filename) unless in_zip? FileUtils.touch current_dir.join(filename) else Zip::File.open(current_zip) do |zip| # zip.file.open(filename, 'w') {|_f| } #HAXX this code creates an unneeded temporary file zip.instance_variable_get(:@entry_set) << Zip::Entry.new(current_zip, filename) end end ls move_cursor items.index {|i| i.name == filename} end |
#touch_t(timestamp) ⇒ Object
Change the timestamp of the selected files and directories.
Parameters
-
timestamp- A string that can be parsed withTime.parse. Note that this parameter is not compatible with UNIX ‘touch -t`.
305 306 307 308 |
# File 'lib/rfd/file_ops.rb', line 305 def touch_t() FileUtils.touch selected_items, mtime: Time.parse() ls end |
#trash ⇒ Object
Soft delete selected files and directories.
If the OS is not OSX, performs the same as delete command. On macOS, tries the trash command first, then falls back to manual mv to ~/.Trash/.
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/rfd/file_ops.rb', line 228 def trash unless in_zip? if osx? paths = selected_items.map(&:path) # Try the trash command first (available via Homebrew or other package managers) unless system('trash', *paths) # Fall back to manual mv to ~/.Trash/ FileUtils.mv paths, File.('~/.Trash/') end else #TODO support other OS FileUtils.rm_rf selected_items.map(&:path) end else return unless ask %Q[Trashing zip entries is not supported. Actually the files will be deleted. Are you sure want to proceed? (y/n)] delete end @current_row -= selected_items.count {|i| i.index <= current_row} ls end |
#unarchive ⇒ Object
Unarchive .zip and .tar.gz files within selected files and directories into current_directory.
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/rfd/file_ops.rb', line 366 def unarchive unless in_zip? zips, gzs = selected_items.partition(&:zip?).tap {|z, others| break [z, *others.partition(&:gz?)]} zips.each do |item| dest_dir = current_dir.join(item.basename) FileUtils.mkdir_p dest_dir Zip::File.open(item) do |zip| zip.each do |entry| dest_path = File.join(dest_dir.to_s, entry.name) FileUtils.mkdir_p File.dirname(dest_path) zip_extract(zip, entry, dest_dir.to_s, dest_path) end end end gzs.each do |item| Zlib::GzipReader.open(item) do |gz| Gem::Package::TarReader.new(gz) do |tar| dest_dir = current_dir.join (gz.orig_name || item.basename).sub(/\.tar$/, '') tar.each do |entry| dest = nil if entry.full_name == '././@LongLink' dest = safe_extract_path(dest_dir, entry.read.strip) next end dest ||= safe_extract_path(dest_dir, entry.full_name) if entry.directory? FileUtils.mkdir_p dest, mode: entry.header.mode elsif entry.file? FileUtils.mkdir_p File.dirname(dest) File.open(dest, 'wb') {|f| f.print entry.read} FileUtils.chmod entry.header.mode, dest elsif entry.header.typeflag == '2' # symlink File.symlink entry.header.linkname, dest end unless Dir.exist? dest_dir FileUtils.mkdir_p dest_dir File.open(File.join(dest_dir, gz.orig_name || item.basename), 'wb') {|f| f.print gz.read} end end end end end else dest_dir = File.join(current_zip.dir, current_zip.basename) FileUtils.mkdir_p dest_dir Zip::File.open(current_zip) do |zip| zip.select {|e| selected_items.map(&:name).include? e.to_s}.each do |entry| dest_path = File.join(dest_dir, entry.name) FileUtils.mkdir_p File.dirname(dest_path) zip_extract(zip, entry, dest_dir, dest_path) end end end ls end |
#yank ⇒ Object
Yank selected file / directory names.
311 312 313 |
# File 'lib/rfd/file_ops.rb', line 311 def yank @yanked_items = selected_items end |
#zip(zipfile_name) ⇒ Object
Archive selected files and directories into a .zip file.
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/rfd/file_ops.rb', line 346 def zip(zipfile_name) return unless zipfile_name zipfile_name += '.zip' unless zipfile_name.end_with? '.zip' zip_file_open_for_create(zipfile_name) do |zipfile| selected_items.each do |item| next if item.symlink? if item.directory? Dir[item.join('**/**')].each do |file| zipfile.add file.sub("#{current_dir}/", ''), file end else zipfile.add item.name, item end end end ls end |