From a0de7f95bc7525b99b2c2e16f566e0ee367e9c3c Mon Sep 17 00:00:00 2001 From: hut Date: Sat, 9 May 2009 00:00:00 +0200 Subject: lots of changes. version 0.2.1 --- code/action.rb | 69 ++ code/bars.rb | 134 ++++ code/debug.rb | 24 +- code/directory.rb | 350 +++++++++ code/draw.rb | 194 +++-- code/extensions.rb | 573 -------------- code/extensions/basic.rb | 149 ++++ code/extensions/fileutils.rb | 1726 ++++++++++++++++++++++++++++++++++++++++++ code/fm.rb | 57 +- code/keys.rb | 238 +++++- code/screensaver/clock.rb | 10 + code/types.rb | 106 ++- 12 files changed, 2940 insertions(+), 690 deletions(-) create mode 100644 code/action.rb create mode 100644 code/bars.rb create mode 100644 code/directory.rb delete mode 100644 code/extensions.rb create mode 100644 code/extensions/basic.rb create mode 100644 code/extensions/fileutils.rb create mode 100644 code/screensaver/clock.rb (limited to 'code') diff --git a/code/action.rb b/code/action.rb new file mode 100644 index 00000000..e463e852 --- /dev/null +++ b/code/action.rb @@ -0,0 +1,69 @@ +module Action + def self.copy(files, path) + files = [files] unless files.is_a? Array + unless files.empty? + CopyBar2.new(files, path) + end + end + + def self.move(files, path) + files = [files] unless files.is_a? Array + unless files.empty? + MoveBar2.new(files, path) + end + end + + def self.run(hash = {}) + unless OpenStruct === hash + hash = OpenStruct.new(hash) + end + + cf = Fm.currentfile + + all = hash.all.or true + files = hash.files.or(all ? Fm.selection : [cf]) + mode = hash.mode.or 0 + newway = hash.newway.or false + + return false if files.nil? + + if newway + hash = Fm.filehandler(files, hash) + handler = hash.exec + else + handler, wait = Fm.getfilehandler(*files) + end + + return false unless handler + + wait = hash.wait.or wait + new_term = hash.new_term.or false + detach = hash.detach.or false + + log handler + if detach + run_detached(handler, new_term) + else + run_inside(handler, wait) + end + return true + end + + def self.run_detached(what, new_term) + if new_term + p = fork { exec('x-terminal-emulator', '-e', 'bash', '-c', what) } +# Process.detach(p) + else + p = fork { exec "#{what} 2>> /dev/null >> /dev/null" } + Process.detach(p) + end + end + + def self.run_inside(what, wait) + closei + system(*what) + gets if wait + starti + end +end + diff --git a/code/bars.rb b/code/bars.rb new file mode 100644 index 00000000..61d14572 --- /dev/null +++ b/code/bars.rb @@ -0,0 +1,134 @@ +class Bar + def initialize( text = '' ) + @text = text + @text_prefix = nil + @max = 0 + @done = 0 + @counter = 0 + @thread = nil + @update_proc = nil + Fm.bar_add(self) + end + + def kill(evil = true) + Fm.bar_del(self) + Fm.force_update + + @thread.kill + end + + def update(&block) + if block + @update_proc = block + elsif @update_proc + @update_proc.call(self) + end + end + + def set_text_prefix(text) + @text_prefix = text + end + def set_text(text) + @text_prefix = nil + @text = text + end + alias text= set_text + + attr_accessor :thread, :counter, :max +end + +class CopyBar < Bar + def initialize( text = '' ) + super + + @update_proc = proc do |b| + begin + b.done = File.size(fname).to_f / finished + rescue + b.done = 0 + end + end + end +end + +class Bar2 + def kill(evil = true) + Fm.bar_del(self) + Fm.force_update + + @thread.kill + end + + def set_text_prefix(text) + @text_prefix = text + end + def set_text(text) + @text_prefix = nil + @text = text + end + alias text= set_text + + attr_accessor :thread, :counter, :max + def done + if @counter.is_a? MutableNumber + @counter.value.to_f / @max + else + 0 + end + end + + def update() end + + def text + if @text_prefix + "#{@text_prefix} #{(@counter.value.to_f * 10000/ @max).round.to_f/100}%" + elsif @text + @text + else + "" + end + end +end + +class CopyOrMoveBar < Bar2 + def initialize(files, path) + path = path.path unless path.is_a? String + Fm.bar_add(self) + log([files, path]) + + + @thread = Thread.new do + begin + for file in files + file = file.path unless file.is_a? String + if File.exists?(file) + run_operation(file, path) + end + end + rescue + log $! + log $!.backtrace + ensure + kill(false) + end + end + @thread.priority = Fm::COPY_PRIORITY + end +end + +class CopyBar2 < CopyOrMoveBar + def run_operation(file, path) + if File.directory?(file) + FileUtils.cp_r_in_bar(self, file, path) + else + FileUtils.cp_in_bar(self, file, path) + end + end +end + +class MoveBar2 < CopyOrMoveBar + def run_operation(file, path) + FileUtils.mv_in_bar(self, file, path) + end +end + diff --git a/code/debug.rb b/code/debug.rb index 6261ed0e..d3a97190 100644 --- a/code/debug.rb +++ b/code/debug.rb @@ -1,4 +1,3 @@ - require 'pp' module Debug @@ -25,6 +24,28 @@ module Debug end if LOG_LEVEL > 0 + def bm(descr="benchmark", &block) + # Benchmark + t1 = Time.now + yield + dur = Time.now-t1 + + # substract the durtation of a "bm(..) do end" + dur -= bm_dummy(descr) do end + + # Format the duration + dur *= 1000 + dur = dur > 0 ? dur : 0 + dur = '%0.3f' % dur + logerr("#{descr}: #{dur}ms") + end + + def bm_dummy(descr="benchmark", &block) + t1 = Time.now + yield + return (Time.now-t1) + end + def __log__(obj, level) if level <= LOG_LEVEL obj = obj.nil? ? "checkpoint at #{Time.now}" : obj @@ -63,5 +84,6 @@ module Debug def log(a=nil) end def logpp(a=nil) end def trace() end + def bm(*args, &block) yield end end end diff --git a/code/directory.rb b/code/directory.rb new file mode 100644 index 00000000..c42392aa --- /dev/null +++ b/code/directory.rb @@ -0,0 +1,350 @@ +class Directory + BAD_TIME = Time.at(0) + MOVIE_EXTENSIONS = %w(avi mpg mpeg mp4 mp5 ogv ogm wmv mkv flv fid vob div divx) + class Entry #{{{ + # Let's just cache every shit, because i don't want + # to call File methods all the time + + + def initialize(dirname, basename=nil) + if basename + @path = File.join(dirname, basename) + @dirname = dirname + @basename = basename + else + @path = dirname + @dirname = File.dirname(dirname) + @basename = File.basename(dirname) + end + @name, @ext = @basename.split_at_last_dot +# @ext = @basename.from_last('.') || '' + @movie = MOVIE_EXTENSIONS.include?(@ext) + @size = 0 + @exists = false + @rights = '----------' + @readlink = '' + @symlink = false + @writable = false + @infostring = '' + @executable = false + @type = :nonexistent + @mtime = BAD_TIME + @ctime = BAD_TIME + @marked = false + end + + attr_reader *%w( + basename mtime rights type path ext + infostring readlink basename size ctime name + ) + + attr_accessor(:marked) + + def to_s() @path end + def exists?() @exists end + def marked?() @marked end + def symlink?() @symlink end + def movie?() @movie end + def broken_symlink?() @symlink and !@exists end + def dir?() @type == :dir end + def file?() @type == :file end + def writable?() @writable end + def executable?() @executable end + def mimetype() + if @type == :dir + nil + else + Fm::MIMETYPES[@ext] + end + end + + def delete! + if @type == :dir + Dir.delete(@path) rescue nil + else + File.delete(@path) rescue nil + end + end + + def refresh + if File.exists?(@path) + if File.ctime(@path) != @ctime + get_data + end + else + get_data + end + end + + def sh + @path.sh + end + + def in? path + to_s[0, path.size] == path + end + + def get_data + @size = 0 + @infostring = '' + + @exists = File.exists?(@path) + if @exists + @writable = File.writable?(@path) + @symlink = File.symlink?(@path) + if @symlink + @readlink = File.readlink(@path) + end + if File.directory?(@path) + @type = :dir + begin + sz = Dir.entries(@path).size - 2 + @size = sz + rescue + sz = "?" + end + @infostring << "#{sz}" + elsif File.socket?(@path) + @type = :socket + else + @type = :file + @size = File.size(@path) + if File.size?(@path) + @infostring << " #{File.size(@path).bytes 2}" + else + @infostring << "" + end + end + @rights = File.modestr(@path) + @executable = File.executable?(@path) + @mtime = File.mtime(@path) + @ctime = File.ctime(@path) + + else + if File.symlink?(@path) + @readlink = File.readlink(@path) + @infostring = '->' + @symlink = true + else + @symlink = false + end + @executable = false + @writable = false + @type = :nonexistent + @rights = '----------' + @mtime = BAD_TIME + @ctime = BAD_TIME + end + end + end #}}} + + PLACEHOLDER = Entry.new('/', 'placeholder') + + def initialize(path, allow_delay=false) + @path = path + @pos = 0 + @files = [PLACEHOLDER] + @file_size = 0 + @pointed_file = nil + @width = 1000 + @read = false + @empty = true + @scheduled = false + + refresh + end + + def read_dir + @mtime = File.mtime(@path) + @files = Dir.new(@path).to_a + if OPTIONS['hidden'] + @files -= ['.', '..', 'lost+found'] + else + @files.reject!{|x| x[0] == ?. or x == 'lost+found'} + end + + if @files.empty? + @files = ['.'] + end + + @files_raw = @files.map{|bn| File.join(@path, bn)} + @files.map!{|basename| Entry.new(@path, basename)} + end + + attr_reader(:path, :files, :pos, :width, :files_raw, + :file_size, :read) + attr_accessor(:scheduled) + + def scheduled?() @scheduled end + def read?() @read end + + def pos=(x) +# if @files.size <= 1 or x < 0 +# x = 0 +# elsif x > @files.size +# x = @files.size - 1 +# end + @pos = x + make_sure_cursor_is_in_range() + @pointed_file = @files[x] + resize + end + + def recheck_stuff() +# log "pointed file: #@pointed_file" +# log @files_raw +# log "" + if test = @files_raw.index(@pointed_file) +# log("if") + @pos = test + else +# log("else") + make_sure_cursor_is_in_range() + end + end + + def make_sure_cursor_is_in_range() + if @files.size <= 1 or @pos < 0 + @pos = 0 + elsif @pos > @files.size + @pos = @files.size - 1 + end + end + + def find_file(x) + x = File.basename(x) + + files.each_with_index do |file, i| + if file.basename == x + self.pos = i + end + end + end + + def empty?() + Dir.entries(@path).size <= 2 + end + + def restore() + for f in @files + f.marked = false + end + end + + def pointed_file=(x) + if @files_raw.include?(x) + @pointed_file = x + @pos = @files_raw.index(x) + else + self.pos = 0 + end + resize + end + + def size() @files.size end + + def resize() + pos = Fm.get_offset(self, lines) + if @files.empty? + @width = 0 + else + @width = 0 + @files[pos, lines-2].each_with_index do |fn, ix| + ix += pos + sz = fn.basename.size + fn.infostring.size + 2 + @width = sz if @width < sz + end +# @width = @files[pos,lines-2].map{|x| File.basename(x).size}.max + end + end + + def get_file_info() + @file_size = 0 + @files.each do |f| + f.refresh + @file_size += f.size if f.file? + end + @read = true + end + +# def refresh() +# @files = Dir.new(@path).to_a +# if OPTIONS['hidden'] +# @files -= ['.', '..', 'lost+found'] +# else +# @files.reject!{|x| x[0] == ?. or x == 'lost+found'} +# end +# if @files.empty? +# @files = ['.'] +# end +# @files.map!{|basename| Entry.new(@path, basename)} +# +# if @pos >= @files.size +# @pos = @files.size - 1 +# elsif @files.include?(@pointed_file) +# @pos = @files.index(@pointed_file) +# end +# end + def refresh(info=false) + if File.mtime(@path) != @mtime + read_dir + end + if info + log("getting file info of #{@path}") + get_file_info + end + sort + end + + def schedule() + @scheduled = true + Fm.schedule(self) + end + + def refresh!() + oldfile = @pointed_file + read_dir + get_file_info + sort + + if @files.include? oldfile + self.pointed_file = oldfile + end + end + + def sort_sub(x, y) + case OPTIONS['sort'] + when :name + x.basename <=> y.basename + when :ext + x.ext <=> y.ext + when :type + x.ext.filetype <=> y.ext.filetype + when :size + x.size <=> y.size + when :ctime + x.ctime <=> y.ctime + when :mtime + x.mtime <=> y.mtime + else + x.basename <=> y.basename + end + end + + def sort() + @files = @files.sort {|x,y| + if OPTIONS['dir_first'] + if x.dir? + if y.dir? then sort_sub(x, y) else -1 end + else + if y.dir? then 1 else sort_sub(x, y) end + end + else + sort_sub(x, y) + end + } + @files.reverse! if OPTIONS['sort_reverse'] + @files_raw = @files.map{|x| x.to_s} + end +end + diff --git a/code/draw.rb b/code/draw.rb index 1a2236cc..dac6c25c 100644 --- a/code/draw.rb +++ b/code/draw.rb @@ -1,5 +1,5 @@ module Fm - DONT_PREVIEW_THESE_FILES = /\.(avi|[mj]pe?g|iso|mp\d|og[gmv]|wm[av]|mkv|torrent|so|class|flv|png|bmp|zip|rar|7z|tar|gz|vob|divx?)$/i + DONT_PREVIEW_THESE_FILES = /\.(avi|[mj]pe?g|iso|mp\d|og[gmv]|wm[av]|mkv|torrent|so|class|flv|png|bmp|vob|divx?)$/i def self.column_put_file(n, file) i = 0 @@ -7,85 +7,140 @@ module Fm m = lines - 2 color 7 bold false - File.open(file.path, 'r') do |f| - check = true - left, wid = get_boundaries(n) - f.lines.each do |l| - if check - check = false - break unless l.each_char.all? {|x| x[0] > 0 and x[0] < 128} - end - puti i+1, left, l.gsub("\t"," ")[0, wid-1].ljust(wid) + left, wid = get_boundaries(n) + if file.ext =~ /(?:rar|zip|7z|tar|gz)$/ and file.size < 10485760 + text = `aunpack -l #{file.sh} 2>> /dev/null` + text.each_line do |l| + puti i+1, left, l[0, wid-1].ljust(wid) i += 1 break if i == m end + else + File.open(file.path, 'r') do |f| + check = true + left, wid = get_boundaries(n) + f.lines.each do |l| + if check + check = false + break unless l.each_char.all? {|x| x[0] > 0 and x[0] < 128} + end + puti i+1, left, l.gsub("\t"," ")[0, wid-1].ljust(wid) + i += 1 + break if i == m + end + end end end column_clear(n, i) end def self.put_directory(c, d) - l = 0 + l = 1 if d infos = (c == COLUMNS - 2) left, wid = get_boundaries(c) - offset = get_offset(d, lines) - (lines - 1).times do |l| - lpo = l + offset - bg = -1 - break if (f = d.files[lpo]) == nil - - dir = false - if f.symlink? - bld = true - if f.broken_symlink? - clr = [1, bg] + if d.read? and not d.empty? + + offset = get_offset(d, lines) + (lines - 1).times do |l| + lpo = l + offset + bg = -1 + break if (f = d.files[lpo]) == nil +# log f + + dir = false + if f.symlink? + bld = true + if f.broken_symlink? + clr = [1, bg] + else + clr = [6, bg] + end + dir = f.dir? + elsif f.dir? + bld = true + dir = true + clr = [4, bg] + elsif f.movie? + bld = true + clr = [5, bg] + elsif f.executable? + bld = true + clr = [2, bg] else - clr = [6, bg] + bld = false + clr = [7, bg] end - dir = f.dir? - elsif f.dir? - bld = true - dir = true - clr = [4, bg] - elsif f.executable? - bld = true - clr = [2, bg] - else - bld = false - clr = [7, bg] - end - fn = f.basename - if f.marked? - fn = "*#{fn}" - end - if infos - myinfo = " #{f.infostring} " - str = fn[0, wid-1].ljust(wid+1) - if str.size > myinfo.size - str[-myinfo.size..-1] = myinfo - yes = true + fn = f.basename + if f.marked? + fn = "* #{fn}" + end + if infos + myinfo = " #{f.infostring} " + str = fn[0, wid-1].ljust(wid+1) + if str.size > myinfo.size + str[-myinfo.size..-1] = myinfo + yes = true + else + yes = false + end + puti l+1, left, str + if dir and yes + args = l+1, left+wid-myinfo.size, myinfo.size, *clr + color_bold_at(*args) + end else - yes = false + puti l+1, left, fn[0, wid-1].ljust(wid+1) end - puti l+1, left, str - if dir and yes - args = l+1, left+wid-myinfo.size, myinfo.size, *clr - color_bold_at(*args) + + args = l+1, left, fn.size.limit(wid-1), *clr + + if d.pos == lpo + if c == COLUMNS - 2 +# puti l+1, left-1, '^' +# puti l+1, left+args[2], '$' + + args[4] = 0 +# args[1] -= 1 +# if args[2] < 5 +# args[2] = 7 +# else +# args[2] += 1 +# end +# color_bold_at(l+1, left-1, 1, 0, 0) +# color_bold_at(l+1, left+args[2], 1, 0, 0) + color_reverse_bold_at(*args) + + # ... + args[1] -= 1; args[2] += 2 + color_bold_at(*args) + args[1] += 1; args[2] -= 2 + color_reverse_bold_at(*args) + else + color_reverse_at(*args) + end +# if f.marked? +# args[1] += 1 +# args[2] = 1 +# args[3] = 1 +# color_reverse_at(*args) +# end + else + if bld then color_at(*args) else color_at(*args) end +# if bld then color_bold_at(*args) else color_at(*args) end end - else - puti l+1, left, fn[0, wid-1].ljust(wid+1) end + elsif d.read? and d.empty? + puti l, left, 'empty'.ljust(wid+1) - args = l+1, left, fn.size.limit(wid), *clr + elsif not d.read? + puti l, left, 'reading...'.ljust(wid+1) + d.schedule unless d.scheduled? + else + puti l, left, 'ERROR'.ljust(wid+1) - if d.pos == lpo - color_reverse_at(*args) - else - if bld then color_bold_at(*args) else color_at(*args) end - end end end @@ -140,7 +195,9 @@ module Fm bold false @cur_y = get_boundaries(COLUMNS-2)[0] - if @buffer == '?' + if @buffer =~ /^block/ + screensaver + elsif @buffer == '?' cleari puti 0, " - - - Help - - -" puti 2, " h/j/k/l: Movement J/K: fast Movement" @@ -177,12 +234,22 @@ module Fm puti 8, " gh: go to ~/" puti 9, " gt: go to ~/.trash/" else + @pwd.recheck_stuff() + cf = currentfile + + if cf and s0 = cf.mimetype + puti 0, cols-s0.size, s0 + end + s1 = " " s2 = "#{@path.last.path}#{"/" unless @path.size == 1}" - cf = currentfile s3 = "#{cf ? cf.basename : ''}" - puti 0, (s1 + s2 + s3).ljust(cols) + if s0 + puti 0, (s1 + s2 + s3).ljust(cols-s0.size) + else + puti 0, (s1 + s2 + s3).ljust(cols) + end bg = -1 color_at 0, 0, -1, 7, bg @@ -227,6 +294,7 @@ module Fm puti btm, "Toggle (h)idden_files (d)irs_first (c)olor (f)ilepreview" else # log(@pwd) +# log "Buffer: #{@buffer}" puti btm, "#@buffer #{@pwd.file_size.bytes(false)},#{@pwd.size},#{@pwd.pos+1} ".rjust(cols) more = '' if cf.symlink? @@ -255,10 +323,10 @@ module Fm done = bar.done c = (done * cols).to_i unless done == 0 - color_at l, 0, c, 0, 2 + color_at l, 0, c, 0, 4 end unless done == cols - color_at l, c, -1, 0, 3 + color_at l, c, -1, 0, 6 end end end diff --git a/code/extensions.rb b/code/extensions.rb deleted file mode 100644 index a15fc952..00000000 --- a/code/extensions.rb +++ /dev/null @@ -1,573 +0,0 @@ -class Bar - def initialize( text = '' ) - @text = text - @done = 0 - @thread = nil - @update_proc = nil - Fm.bar_add(self) - end - - def kill(evil = true) - Fm.bar_del(self) - Fm.force_update - - @thread.kill - end - - def update(&block) - if block - @update_proc = block - elsif @update_proc - @update_proc.call(self) - end - end - - attr_accessor :text, :done, :thread -end - -class CopyBar < Bar - def initialize( text = '' ) - super - - @update_proc = proc do |b| - begin - b.done = File.size(fname).to_f / finished - rescue - b.done = 0 - end - end - end -end - -module Action -# def self.get_all_files(path) -# glob = Dir.new(path).to_a -# end - - def self.make_a_bar_for_one(text, command) - bar = CopyBar.new(test) - - finished = File.size(from[0]).to_f - fname = File.join(to, File.basename(from[0])) - - bar.thread = Thread.new do - begin - system('ionice', '-c3', command, *(from + [to])) - ensure - bar.kill(false) - end - end - end - - def self.copy(from, to) -# log [from, to] - -# if String === from[0] -# from[0] = Directory::Entry.new(from[0]) -# end - - if from.size == 1 and from[0].file? - from = from[0] - bar = Bar.new("Copying...") - finished = from.size.to_f - fname = File.join(to, from.basename) - - bar.update do |b| - begin - b.done = File.size(fname).to_f / finished - rescue - b.done = 0 - end - end - - bar.thread = Thread.new do - begin - system('cp', from.to_s, to) - ensure - bar.kill(false) - end - end - - else - bar = Bar.new("Copying...") - from = from.dup - from = [from] unless Array === from - finished = Dir.number_of_files(*from.map{|x| x.to_s}) - count = 0 - - bar.update do |b| - begin - b.done = count / finished - rescue - b.done = 0 - end - end - - from.map!{|x| x.to_s} - bar.thread = Thread.new do - begin - system('cp', '-r', *(from + [to.to_s])) -# IO.popen("cp -vr #{from.join(' ')} #{to.sh}") do |f| -# IO.popen(['cp', '-vr', *(from + [to])]) do |f| -# count += 1 while f.gets =~ /' -> `/ -# end - ensure - bar.kill(false) - end - end - end - end - - def self.move(from, to) -# log [from, to] - -# if String === from[0] -# from[0] = Directory::Entry.new(from[0]) -# end - - if from.size == 1 and from[0].file? - from = from[0] - bar = Bar.new("Moving...") - finished = from.size.to_f - fname = File.join(to, from.basename) - - bar.update do |b| - begin - b.done = File.size(fname).to_f / finished - rescue - b.done = 0 - end - end - - bar.thread = Thread.new do - begin - system('mv', from.to_s, to) - ensure - bar.kill(false) - end - end - - else - bar = Bar.new("Moving...") - from = from.dup - from = [from] unless Array === from - finished = Dir.number_of_files(*from.map{|x| x.to_s}) - count = 0 - - bar.update do |b| - begin - b.done = count / finished - rescue - b.done = 0 - end - end - - from.map!{|x| x.to_s} - bar.thread = Thread.new do - begin - system('mv', *(from + [to.to_s])) -# IO.popen("mv -v #{from.join(' ')} #{to.sh}") do |f| -# count += 1 while f.gets =~ /' -> `/ -# end - ensure - bar.kill(false) - end - end - end - end -end - -class Directory - BAD_TIME = Time.at(0) - class Entry #{{{ - # Let's just cache every shit, because i don't want - # to call File methods all the time - - - def initialize(dirname, basename=nil) - if basename - @path = File.join(dirname, basename) - @dirname = dirname - @basename = basename - else - @path = dirname - @dirname = File.dirname(dirname) - @basename = File.basename(dirname) - end - @size = 0 - @exists = false - @rights = '----------' - @readlink = '' - @symlink = false - @writable = false - @infostring = '' - @executable = false - @type = :nonexistent - @mtime = BAD_TIME - @ctime = BAD_TIME - @marked = false - end - - attr_reader(:basename, :mtime, :rights, :type, :path, - :infostring, :readlink, :basename, :size, :ctime) - - attr_accessor(:marked) - - def to_s() @path end - def exists?() @exists end - def marked?() @marked end - def symlink?() @symlink end - def broken_symlink?() @symlink and !@exists end - def dir?() @type == :dir end - def file?() @type == :file end - def writable?() @writable end - def executable?() @executable end - - def delete! - if @type == :dir - Dir.delete(@path) rescue nil - else - File.delete(@path) rescue nil - end - end - - def refresh - if File.exists?(@path) - if File.ctime(@path) != @ctime - get_data - end - else - get_data - end - end - - def sh - res = @path.dup - res.gsub!('\\\\', "\000") - res.gsub!(' ', '\\ ') - res.gsub!('(', '\\(') - res.gsub!('&', '\\&') - res.gsub!(')', '\\)') - res.gsub!('*', '\\*') - res.gsub!('\'', '\\\'') - res.gsub!('"', '\\"') - res.gsub!("\000", '\\\\') - return res - end - - def in? path - to_s[0, path.size] == path - end - - def get_data - @size = 0 - @infostring = '' - - @exists = File.exists?(@path) - if @exists - @writable = File.writable?(@path) - @symlink = File.symlink?(@path) - if @symlink - @readlink = File.readlink(@path) - end - if File.directory?(@path) - @type = :dir - begin - sz = Dir.entries(@path).size - 2 - @size = sz - rescue - sz = "?" - end - @infostring << "#{sz}" - elsif File.socket?(@path) - @type = :socket - else - @type = :file - @size = File.size(@path) - if File.size?(@path) - @infostring << " #{File.size(@path).bytes 2}" - else - @infostring << "" - end - end - @rights = File.modestr(@path) - @executable = File.executable?(@path) - @mtime = File.mtime(@path) - @ctime = File.ctime(@path) - - else - if File.symlink?(@path) - @readlink = File.readlink(@path) - @infostring = '->' - @symlink = true - else - @symlink = false - end - @executable = false - @writable = false - @type = :nonexistent - @rights = '----------' - @mtime = BAD_TIME - @ctime = BAD_TIME - end - end - end #}}} - - PLACEHOLDER = Entry.new('/', 'placeholder') - - def initialize(path, allow_delay=false) - @path = path - @pos = 0 - @files = [PLACEHOLDER] - @file_size = 0 - @pointed_file = nil - @width = 1000 - - refresh - end - - def read_dir - @mtime = File.mtime(@path) - @files = Dir.new(@path).to_a - if OPTIONS['hidden'] - @files -= ['.', '..', 'lost+found'] - else - @files.reject!{|x| x[0] == ?. or x == 'lost+found'} - end - if @files.empty? - @files = ['.'] - end - - @files_raw = @files.map{|bn| File.join(@path, bn)} - @files.map!{|basename| Entry.new(@path, basename)} - end - - attr_reader(:path, :files, :pos, :width, :files_raw, :file_size) - - def pos=(x) - @pos = x - @pointed_file = @files[x] - resize - end - - def restore() - for f in @files - f.marked = false - end - end - - def pointed_file=(x) - if @files_raw.include?(x) - @pointed_file = x - @pos = @files_raw.index(x) - else - self.pos = 0 - end - resize - end - - def size() @files.size end - - def resize() - pos = Fm.get_offset(self, lines) - if @files.empty? - @width = 0 - else - @width = 0 - @files[pos, lines-2].each_with_index do |fn, ix| - ix += pos - sz = fn.basename.size + fn.infostring.size + 2 - @width = sz if @width < sz - end -# @width = @files[pos,lines-2].map{|x| File.basename(x).size}.max - end - end - - def get_file_info() - @file_size = 0 - @files.each do |f| - f.refresh - @file_size += f.size if f.file? - end - end - -# def refresh() -# @files = Dir.new(@path).to_a -# if OPTIONS['hidden'] -# @files -= ['.', '..', 'lost+found'] -# else -# @files.reject!{|x| x[0] == ?. or x == 'lost+found'} -# end -# if @files.empty? -# @files = ['.'] -# end -# @files.map!{|basename| Entry.new(@path, basename)} -# -# if @pos >= @files.size -# @pos = @files.size - 1 -# elsif @files.include?(@pointed_file) -# @pos = @files.index(@pointed_file) -# end -# end - def refresh(info=false) - if File.mtime(@path) != @mtime - read_dir - end - if info - log("getting file info of #{@path}") - get_file_info - end - sort - end - - def schedule() - Fm.schedule(self) - end - - def refresh!() - read_dir - get_file_info - sort - end - - def sort_sub(x, y) - case OPTIONS['sort'] - when :name - x.basename <=> y.basename - when :size - x.size <=> y.size - when :ctime - x.ctime <=> y.ctime - when :mtime - x.mtime <=> y.mtime - else - x.basename <=> y.basename - end - end - - def sort() - @files = @files.sort {|x,y| - if OPTIONS['dir_first'] - if x.dir? - if y.dir? then sort_sub(x, y) else -1 end - else - if y.dir? then 1 else sort_sub(x, y) end - end - else - sort_sub(x, y) - end - } - @files.reverse! if OPTIONS['sort_reverse'] - @files_raw = @files.map{|x| x.to_s} - end -end - - -class File - MODES_HASH = { - '0' => '---', - '1' => '--x', - '2' => '-w-', - '3' => '-wx', - '4' => 'r--', - '5' => 'r-x', - '6' => 'rw-', - '7' => 'rwx' - } - def self.modestr(f) - unless exists?(f) - return '----------' - end - - if symlink?(f) - result = 'l' - elsif directory?(f) - result = 'd' - else - result = '-' - end - - s = ("%o" % File.stat(f).mode)[-3..-1] - for m in s.each_char - result << MODES_HASH[m] - end - - result - end -end - -class Dir - def self.number_of_files(*dirs) - n = 0 - dirs.each do |entry| - if File.directory?(entry) - n += 1 + number_of_files(*(Dir.new(entry).to_a - ['.', '..']).map\ - {|x| File.join entry, x } ) - else - n += 1 - end - end - return n - end -end - -class Numeric - def limit(max, min = 0) - self < min ? min : (self > max ? max : self) - end - - def bytes space = true, n_round = 2 - n = 1024 - a = %w(B K M G T Y) - - i = 0 - flt = self.to_f - - while flt > n and i < a.length - 1 - flt /= n - i += 1 - end - -# flt = flt.round(n_round) - r = 10 ** n_round - flt *= r - flt = flt.round.to_f / r - int = flt.to_i - flt = int if int == flt - - return flt.to_s + (space ? ' ' + a[i] : a[i]) - end -end - -class Array - def wrap(n) - n.times { push shift } - end -end - -class String - def clear - replace String.new - end - if RUBY_VERSION < '1.9' - def ord - self[0] - end - end - - def sh - res = self.dup - res.gsub!('\\\\', "\000") - res.gsub!(' ', '\\ ') - res.gsub!('(', '\\(') - res.gsub!('&', '\\&') - res.gsub!(')', '\\)') - res.gsub!('*', '\\*') - res.gsub!('\'', '\\\'') - res.gsub!('"', '\\"') - res.gsub!("\000", '\\\\') - return res - end - -end - diff --git a/code/extensions/basic.rb b/code/extensions/basic.rb new file mode 100644 index 00000000..c822e9f6 --- /dev/null +++ b/code/extensions/basic.rb @@ -0,0 +1,149 @@ +# Generic extensions of the language + +class MutableNumber + attr_accessor :value + + def initialize(n=0) + @value = n + end + def add(n=1) @value += n end + def sub(n=1) @value -= n end +end + +class Array + def wrap(n) + # TODO: this can be done better... + n.times { push shift } + end +end + +class String + def clear + self.replace("") + end + + if RUBY_VERSION < '1.9' + def ord + self[0] + end + end + + def from_first(str) + self.include?(str) ? self [ self.index(str) + str.size .. -1 ] : nil + end + + def from_last(str) + self.include?(str) ? self [ self.rindex(str) + str.size .. -1 ] : nil + end + + def split_at_last_dot() + if ix = self.rindex('.') + return self[0...ix], self[ix+1..-1] + else + return self, '' + end + end + + def before_last(str) + self.include?(str) ? self [ 0 .. rindex(str) - str.size ] : self + end + + def filetype() + Fm::MIMETYPES[self] || 'unknown' + end + + def sh + res = self.dup + res.gsub!('\\\\', "\000") + res.gsub!(' ', '\\ ') + res.gsub!('(', '\\(') + res.gsub!('&', '\\\&') + res.gsub!(')', '\\)') + res.gsub!('*', '\\*') + res.gsub!('\'', "\\\\'") + res.gsub!('"', '\\"') + res.gsub!("\000", '\\\\') + return res + end +end + +class Numeric + def limit(max, min = 0) + self < min ? min : (self > max ? max : self) + end + + def bytes space = true, n_round = 2 + n = 1024 + a = %w(B K M G T Y) + + i = 0 + flt = self.to_f + + while flt > n and i < a.length - 1 + flt /= n + i += 1 + end + +# flt = flt.round(n_round) + r = 10 ** n_round + flt *= r + flt = flt.round.to_f / r + int = flt.to_i + flt = int if int == flt + + return flt.to_s + (space ? ' ' + a[i] : a[i]) + end +end + +class Dir + def self.number_of_files(*dirs) + n = 0 + dirs.each do |entry| + if File.directory?(entry) + n += 1 + number_of_files(*(Dir.new(entry).to_a - ['.', '..']).map\ + {|x| File.join entry, x } ) + else + n += 1 + end + end + return n + end +end + +class File + MODES_HASH = { + '0' => '---', + '1' => '--x', + '2' => '-w-', + '3' => '-wx', + '4' => 'r--', + '5' => 'r-x', + '6' => 'rw-', + '7' => 'rwx' + } + def self.modestr(f) + unless exists?(f) + return '----------' + end + + if symlink?(f) + result = 'l' + elsif directory?(f) + result = 'd' + else + result = '-' + end + + s = ("%o" % File.stat(f).mode)[-3..-1] + for m in s.each_char + result << MODES_HASH[m] + end + + result + end +end + + +class Object; def or(value) self end end +class NilClass; def or(value) value end end + diff --git a/code/extensions/fileutils.rb b/code/extensions/fileutils.rb new file mode 100644 index 00000000..701fcde7 --- /dev/null +++ b/code/extensions/fileutils.rb @@ -0,0 +1,1726 @@ +# +# = fileutils.rb +# +# Copyright (c) 2000-2006 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the same terms of ruby. +# +# == module FileUtils +# +# Namespace for several file utility methods for copying, moving, removing, etc. +# +# === Module Functions +# +# cd(dir, options) +# cd(dir, options) {|dir| .... } +# pwd() +# mkdir(dir, options) +# mkdir(list, options) +# mkdir_p(dir, options) +# mkdir_p(list, options) +# rmdir(dir, options) +# rmdir(list, options) +# ln(old, new, options) +# ln(list, destdir, options) +# ln_s(old, new, options) +# ln_s(list, destdir, options) +# ln_sf(src, dest, options) +# cp(src, dest, options) +# cp(list, dir, options) +# cp_r(src, dest, options) +# cp_r(list, dir, options) +# mv(src, dest, options) +# mv(list, dir, options) +# rm(list, options) +# rm_r(list, options) +# rm_rf(list, options) +# install(src, dest, mode = , options) +# chmod(mode, list, options) +# chmod_R(mode, list, options) +# chown(user, group, list, options) +# chown_R(user, group, list, options) +# touch(list, options) +# +# The options parameter is a hash of options, taken from the list +# :force, :noop, :preserve, and :verbose. +# :noop means that no changes are made. The other two are obvious. +# Each method documents the options that it honours. +# +# All methods that have the concept of a "source" file or directory can take +# either one file or a list of files in that argument. See the method +# documentation for examples. +# +# There are some `low level' methods, which do not accept any option: +# +# copy_entry(src, dest, preserve = false, dereference = false) +# copy_file(src, dest, preserve = false, dereference = true) +# copy_stream(srcstream, deststream) +# remove_entry(path, force = false) +# remove_entry_secure(path, force = false) +# remove_file(path, force = false) +# compare_file(path_a, path_b) +# compare_stream(stream_a, stream_b) +# uptodate?(file, cmp_list) +# +# == module FileUtils::Verbose +# +# This module has all methods of FileUtils module, but it outputs messages +# before acting. This equates to passing the :verbose flag to methods +# in FileUtils. +# +# == module FileUtils::NoWrite +# +# This module has all methods of FileUtils module, but never changes +# files/directories. This equates to passing the :noop flag to methods +# in FileUtils. +# +# == module FileUtils::DryRun +# +# This module has all methods of FileUtils module, but never changes +# files/directories. This equates to passing the :noop and +# :verbose flags to methods in FileUtils. +# + +module FileUtils + + def self.private_module_function(name) #:nodoc: + module_function name + private_class_method name + end + + # This hash table holds command options. + OPT_TABLE = {} #:nodoc: internal use only + + # + # Options: (none) + # + # Returns the name of the current directory. + # + def pwd + Dir.pwd + end + module_function :pwd + + alias getwd pwd + module_function :getwd + + # + # Options: verbose + # + # Changes the current directory to the directory +dir+. + # + # If this method is called with block, resumes to the old + # working directory after the block execution finished. + # + # FileUtils.cd('/', :verbose => true) # chdir and report it + # + def cd(dir, options = {}, &block) # :yield: dir + fu_check_options options, OPT_TABLE['cd'] + fu_output_message "cd #{dir}" if options[:verbose] + Dir.chdir(dir, &block) + fu_output_message 'cd -' if options[:verbose] and block + end + module_function :cd + + alias chdir cd + module_function :chdir + + OPT_TABLE['cd'] = + OPT_TABLE['chdir'] = [:verbose] + + # + # Options: (none) + # + # Returns true if +newer+ is newer than all +old_list+. + # Non-existent files are older than any file. + # + # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ + # system 'make hello.o' + # + def uptodate?(new, old_list, options = nil) + raise ArgumentError, 'uptodate? does not accept any option' if options + + return false unless File.exist?(new) + new_time = File.mtime(new) + old_list.each do |old| + if File.exist?(old) + return false unless new_time > File.mtime(old) + end + end + true + end + module_function :uptodate? + + # + # Options: mode noop verbose + # + # Creates one or more directories. + # + # FileUtils.mkdir 'test' + # FileUtils.mkdir %w( tmp data ) + # FileUtils.mkdir 'notexist', :noop => true # Does not really create. + # FileUtils.mkdir 'tmp', :mode => 0700 + # + def mkdir(list, options = {}) + fu_check_options options, OPT_TABLE['mkdir'] + list = fu_list(list) + fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] + return if options[:noop] + + list.each do |dir| + fu_mkdir dir, options[:mode] + end + end + module_function :mkdir + + OPT_TABLE['mkdir'] = [:mode, :noop, :verbose] + + # + # Options: mode noop verbose + # + # Creates a directory and all its parent directories. + # For example, + # + # FileUtils.mkdir_p '/usr/local/lib/ruby' + # + # causes to make following directories, if it does not exist. + # * /usr + # * /usr/local + # * /usr/local/lib + # * /usr/local/lib/ruby + # + # You can pass several directories at a time in a list. + # + def mkdir_p(list, options = {}) + fu_check_options options, OPT_TABLE['mkdir_p'] + list = fu_list(list) + fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] + return *list if options[:noop] + + list.map {|path| path.sub(%r, '') }.each do |path| + # optimize for the most common case + begin + fu_mkdir path, options[:mode] + next + rescue SystemCallError + next if File.directory?(path) + end + + stack = [] + until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" + stack.push path + path = File.dirname(path) + end + stack.reverse_each do |path| + begin + fu_mkdir path, options[:mode] + rescue SystemCallError => err + raise unless File.directory?(path) + end + end + end + + return *list + end + module_function :mkdir_p + + alias mkpath mkdir_p + alias makedirs mkdir_p + module_function :mkpath + module_function :makedirs + + OPT_TABLE['mkdir_p'] = + OPT_TABLE['mkpath'] = + OPT_TABLE['makedirs'] = [:mode, :noop, :verbose] + + def fu_mkdir(path, mode) #:nodoc: + path = path.sub(%r, '') + if mode + Dir.mkdir path, mode + File.chmod mode, path + else + Dir.mkdir path + end + end + private_module_function :fu_mkdir + + # + # Options: noop, verbose + # + # Removes one or more directories. + # + # FileUtils.rmdir 'somedir' + # FileUtils.rmdir %w(somedir anydir otherdir) + # # Does not really remove directory; outputs message. + # FileUtils.rmdir 'somedir', :verbose => true, :noop => true + # + def rmdir(list, options = {}) + fu_check_options options, OPT_TABLE['rmdir'] + list = fu_list(list) + fu_output_message "rmdir #{list.join ' '}" if options[:verbose] + return if options[:noop] + list.each do |dir| + Dir.rmdir dir.sub(%r, '') + end + end + module_function :rmdir + + OPT_TABLE['rmdir'] = [:noop, :verbose] + + # + # Options: force noop verbose + # + # ln(old, new, options = {}) + # + # Creates a hard link +new+ which points to +old+. + # If +new+ already exists and it is a directory, creates a link +new/old+. + # If +new+ already exists and it is not a directory, raises Errno::EEXIST. + # But if :force option is set, overwrite +new+. + # + # FileUtils.ln 'gcc', 'cc', :verbose => true + # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' + # + # ln(list, destdir, options = {}) + # + # Creates several hard links in a directory, with each one pointing to the + # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. + # + # include FileUtils + # cd '/sbin' + # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. + # + def ln(src, dest, options = {}) + fu_check_options options, OPT_TABLE['ln'] + fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest0(src, dest) do |s,d| + remove_file d, true if options[:force] + File.link s, d + end + end + module_function :ln + + alias link ln + module_function :link + + OPT_TABLE['ln'] = + OPT_TABLE['link'] = [:force, :noop, :verbose] + + # + # Options: force noop verbose + # + # ln_s(old, new, options = {}) + # + # Creates a symbolic link +new+ which points to +old+. If +new+ already + # exists and it is a directory, creates a symbolic link +new/old+. If +new+ + # already exists and it is not a directory, raises Errno::EEXIST. But if + # :force option is set, overwrite +new+. + # + # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' + # FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true + # + # ln_s(list, destdir, options = {}) + # + # Creates several symbolic links in a directory, with each one pointing to the + # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. + # + # If +destdir+ is not a directory, raises Errno::ENOTDIR. + # + # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin' + # + def ln_s(src, dest, options = {}) + fu_check_options options, OPT_TABLE['ln_s'] + fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest0(src, dest) do |s,d| + remove_file d, true if options[:force] + File.symlink s, d + end + end + module_function :ln_s + + alias symlink ln_s + module_function :symlink + + OPT_TABLE['ln_s'] = + OPT_TABLE['symlink'] = [:force, :noop, :verbose] + + # + # Options: noop verbose + # + # Same as + # #ln_s(src, dest, :force) + # + def ln_sf(src, dest, options = {}) + fu_check_options options, OPT_TABLE['ln_sf'] + options = options.dup + options[:force] = true + ln_s src, dest, options + end + module_function :ln_sf + + OPT_TABLE['ln_sf'] = [:noop, :verbose] + + # + # Options: preserve noop verbose + # + # Copies a file content +src+ to +dest+. If +dest+ is a directory, + # copies +src+ to +dest/src+. + # + # If +src+ is a list of files, then +dest+ must be a directory. + # + # FileUtils.cp 'eval.c', 'eval.c.org' + # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' + # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true + # FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink + # + def cp(src, dest, options = {}) + fu_check_options options, OPT_TABLE['cp'] + fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + copy_file s, d, options[:preserve] + end + end + module_function :cp + + # changed + def cp_in_bar(bar, src, dest, options = {}) + fu_check_options options, OPT_TABLE['cp'] + fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + copy_file_in_bar bar, s, d, options[:preserve] + end + end + module_function :cp_in_bar + + alias copy cp + module_function :copy + + OPT_TABLE['cp'] = + OPT_TABLE['copy'] = [:preserve, :noop, :verbose] + + # + # Options: preserve noop verbose dereference_root remove_destination + # + # Copies +src+ to +dest+. If +src+ is a directory, this method copies + # all its contents recursively. If +dest+ is a directory, copies + # +src+ to +dest/src+. + # + # +src+ can be a list of files. + # + # # Installing ruby library "mylib" under the site_ruby + # FileUtils.rm_r site_ruby + '/mylib', :force + # FileUtils.cp_r 'lib/', site_ruby + '/mylib' + # + # # Examples of copying several files to target directory. + # FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' + # FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true + # + # # If you want to copy all contents of a directory instead of the + # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, + # # use following code. + # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest, + # # but this doesn't. + # + def cp_r(src, dest, options = {}) + fu_check_options options, OPT_TABLE['cp_r'] + fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + options[:dereference_root] = true unless options.key?(:dereference_root) + fu_each_src_dest(src, dest) do |s, d| + copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination] + end + end + module_function :cp_r + + #changed + def cp_r_in_bar(bar, src, dest, options = {}) + fu_check_options options, OPT_TABLE['cp_r'] + fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + options[:dereference_root] = true unless options.key?(:dereference_root) + fu_each_src_dest(src, dest) do |s, d| + copy_entry_in_bar bar, s, d, options[:preserve], options[:dereference_root], options[:remove_destination] + end + end + module_function :cp_r_in_bar + + OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose, + :dereference_root, :remove_destination] + + # + # Copies a file system entry +src+ to +dest+. + # If +src+ is a directory, this method copies its contents recursively. + # This method preserves file types, c.f. symlink, directory... + # (FIFO, device files and etc. are not supported yet) + # + # Both of +src+ and +dest+ must be a path name. + # +src+ must exist, +dest+ must not exist. + # + # If +preserve+ is true, this method preserves owner, group, permissions + # and modified time. + # + # If +dereference_root+ is true, this method dereference tree root. + # + # If +remove_destination+ is true, this method removes each destination file before copy. + # + def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) + Entry_.new(src, nil, dereference_root).traverse do |ent| + destent = Entry_.new(dest, ent.rel, false) + File.unlink destent.path if remove_destination && File.file?(destent.path) + ent.copy destent.path + ent.copy_metadata destent.path if preserve + end + end + module_function :copy_entry + + #changed + def copy_entry_in_bar(bar, src, dest, preserve = false, dereference_root = false, remove_destination = false) + Entry_.new(src, nil, dereference_root).traverse do |ent| + destent = Entry_.new(dest, ent.rel, false) + File.unlink destent.path if remove_destination && File.file?(destent.path) + ent.copy_in_bar bar, destent.path + ent.copy_metadata destent.path if preserve + end + end + module_function :copy_entry_in_bar + + # + # Copies file contents of +src+ to +dest+. + # Both of +src+ and +dest+ must be a path name. + # + def copy_file(src, dest, preserve = false, dereference = true) + ent = Entry_.new(src, nil, dereference) + ent.copy_file dest + ent.copy_metadata dest if preserve + end + module_function :copy_file + + # changed + def copy_file_in_bar(bar, src, dest, preserve = false, dereference = true) + ent = Entry_.new(src, nil, dereference) + ent.copy_file_in_bar bar, dest + ent.copy_metadata dest if preserve + end + module_function :copy_file_in_bar + + # + # Copies stream +src+ to +dest+. + # +src+ must respond to #read(n) and + # +dest+ must respond to #write(str). + # + def copy_stream(src, dest) + fu_copy_stream0 src, dest, fu_stream_blksize(src, dest) + end + module_function :copy_stream + + # + # Options: force noop verbose + # + # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different + # disk partition, the file is copied instead. + # + # FileUtils.mv 'badname.rb', 'goodname.rb' + # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error + # + # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/' + # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true + # + def mv(src, dest, options = {}) + fu_check_options options, OPT_TABLE['mv'] + fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + destent = Entry_.new(d, nil, true) + begin + if destent.exist? + if destent.directory? + raise Errno::EEXIST, dest + else + destent.remove_file if rename_cannot_overwrite_file? + end + end + begin + File.rename s, d + rescue Errno::EXDEV + copy_entry s, d, true + if options[:secure] + remove_entry_secure s, options[:force] + else + remove_entry s, options[:force] + end + end + rescue SystemCallError + raise unless options[:force] + end + end + end + module_function :mv + + def mv_in_bar(bar, src, dest, options = {}) + fu_check_options options, OPT_TABLE['mv'] + fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + destent = Entry_.new(d, nil, true) + begin + if destent.exist? + if destent.directory? + raise Errno::EEXIST, dest + else + destent.remove_file if rename_cannot_overwrite_file? + end + end + begin + File.rename s, d + rescue Errno::EXDEV + copy_entry_in_bar bar, s, d, true + if options[:secure] + remove_entry_secure s, options[:force] + else + remove_entry s, options[:force] + end + end + rescue SystemCallError + raise unless options[:force] + end + end + end + module_function :mv_in_bar + + alias move mv + module_function :move + + OPT_TABLE['mv'] = + OPT_TABLE['move'] = [:force, :noop, :verbose, :secure] + + def rename_cannot_overwrite_file? #:nodoc: + /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM + end + private_module_function :rename_cannot_overwrite_file? + + # + # Options: force noop verbose + # + # Remove file(s) specified in +list+. This method cannot remove directories. + # All StandardErrors are ignored when the :force option is set. + # + # FileUtils.rm %w( junk.txt dust.txt ) + # FileUtils.rm Dir.glob('*.so') + # FileUtils.rm 'NotExistFile', :force => true # never raises exception + # + def rm(list, options = {}) + fu_check_options options, OPT_TABLE['rm'] + list = fu_list(list) + fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose] + return if options[:noop] + + list.each do |path| + remove_file path, options[:force] + end + end + module_function :rm + + alias remove rm + module_function :remove + + OPT_TABLE['rm'] = + OPT_TABLE['remove'] = [:force, :noop, :verbose] + + # + # Options: noop verbose + # + # Equivalent to + # + # #rm(list, :force => true) + # + def rm_f(list, options = {}) + fu_check_options options, OPT_TABLE['rm_f'] + options = options.dup + options[:force] = true + rm list, options + end + module_function :rm_f + + alias safe_unlink rm_f + module_function :safe_unlink + + OPT_TABLE['rm_f'] = + OPT_TABLE['safe_unlink'] = [:noop, :verbose] + + # + # Options: force noop verbose secure + # + # remove files +list+[0] +list+[1]... If +list+[n] is a directory, + # removes its all contents recursively. This method ignores + # StandardError when :force option is set. + # + # FileUtils.rm_r Dir.glob('/tmp/*') + # FileUtils.rm_r '/', :force => true # :-) + # + # WARNING: This method causes local vulnerability + # if one of parent directories or removing directory tree are world + # writable (including /tmp, whose permission is 1777), and the current + # process has strong privilege such as Unix super user (root), and the + # system has symbolic link. For secure removing, read the documentation + # of #remove_entry_secure carefully, and set :secure option to true. + # Default is :secure=>false. + # + # NOTE: This method calls #remove_entry_secure if :secure option is set. + # See also #remove_entry_secure. + # + def rm_r(list, options = {}) + fu_check_options options, OPT_TABLE['rm_r'] + # options[:secure] = true unless options.key?(:secure) + list = fu_list(list) + fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose] + return if options[:noop] + list.each do |path| + if options[:secure] + remove_entry_secure path, options[:force] + else + remove_entry path, options[:force] + end + end + end + module_function :rm_r + + OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure] + + # + # Options: noop verbose secure + # + # Equivalent to + # + # #rm_r(list, :force => true) + # + # WARNING: This method causes local vulnerability. + # Read the documentation of #rm_r first. + # + def rm_rf(list, options = {}) + fu_check_options options, OPT_TABLE['rm_rf'] + options = options.dup + options[:force] = true + rm_r list, options + end + module_function :rm_rf + + alias rmtree rm_rf + module_function :rmtree + + OPT_TABLE['rm_rf'] = + OPT_TABLE['rmtree'] = [:noop, :verbose, :secure] + + # + # This method removes a file system entry +path+. +path+ shall be a + # regular file, a directory, or something. If +path+ is a directory, + # remove it recursively. This method is required to avoid TOCTTOU + # (time-of-check-to-time-of-use) local security vulnerability of #rm_r. + # #rm_r causes security hole when: + # + # * Parent directory is world writable (including /tmp). + # * Removing directory tree includes world writable directory. + # * The system has symbolic link. + # + # To avoid this security hole, this method applies special preprocess. + # If +path+ is a directory, this method chown(2) and chmod(2) all + # removing directories. This requires the current process is the + # owner of the removing whole directory tree, or is the super user (root). + # + # WARNING: You must ensure that *ALL* parent directories are not + # world writable. Otherwise this method does not work. + # Only exception is temporary directory like /tmp and /var/tmp, + # whose permission is 1777. + # + # WARNING: Only the owner of the removing directory tree, or Unix super + # user (root) should invoke this method. Otherwise this method does not + # work. + # + # For details of this security vulnerability, see Perl's case: + # + # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 + # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 + # + # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. + # + def remove_entry_secure(path, force = false) + unless fu_have_symlink? + remove_entry path, force + return + end + fullpath = File.expand_path(path) + st = File.lstat(fullpath) + unless st.directory? + File.unlink fullpath + return + end + # is a directory. + parent_st = File.stat(File.dirname(fullpath)) + unless fu_world_writable?(parent_st) + remove_entry path, force + return + end + unless parent_st.sticky? + raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})" + end + # freeze tree root + euid = Process.euid + File.open(fullpath + '/.') {|f| + unless fu_stat_identical_entry?(st, f.stat) + # symlink (TOC-to-TOU attack?) + File.unlink fullpath + return + end + f.chown euid, -1 + f.chmod 0700 + } + # ---- tree root is frozen ---- + root = Entry_.new(path) + root.preorder_traverse do |ent| + if ent.directory? + ent.chown euid, -1 + ent.chmod 0700 + end + end + root.postorder_traverse do |ent| + begin + ent.remove + rescue + raise unless force + end + end + rescue + raise unless force + end + module_function :remove_entry_secure + + def fu_world_writable?(st) + (st.mode & 0002) != 0 + end + private_module_function :fu_world_writable? + + def fu_have_symlink? #:nodoc + File.symlink nil, nil + rescue NotImplementedError + return false + rescue + return true + end + private_module_function :fu_have_symlink? + + def fu_stat_identical_entry?(a, b) #:nodoc: + a.dev == b.dev and a.ino == b.ino + end + private_module_function :fu_stat_identical_entry? + + # + # This method removes a file system entry +path+. + # +path+ might be a regular file, a directory, or something. + # If +path+ is a directory, remove it recursively. + # + # See also #remove_entry_secure. + # + def remove_entry(path, force = false) + Entry_.new(path).postorder_traverse do |ent| + begin + ent.remove + rescue + raise unless force + end + end + rescue + raise unless force + end + module_function :remove_entry + + # + # Removes a file +path+. + # This method ignores StandardError if +force+ is true. + # + def remove_file(path, force = false) + Entry_.new(path).remove_file + rescue + raise unless force + end + module_function :remove_file + + # + # Removes a directory +dir+ and its contents recursively. + # This method ignores StandardError if +force+ is true. + # + def remove_dir(path, force = false) + remove_entry path, force # FIXME?? check if it is a directory + end + module_function :remove_dir + + # + # Returns true if the contents of a file A and a file B are identical. + # + # FileUtils.compare_file('somefile', 'somefile') #=> true + # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false + # + def compare_file(a, b) + return false unless File.size(a) == File.size(b) + File.open(a, 'rb') {|fa| + File.open(b, 'rb') {|fb| + return compare_stream(fa, fb) + } + } + end + module_function :compare_file + + alias identical? compare_file + alias cmp compare_file + module_function :identical? + module_function :cmp + + # + # Returns true if the contents of a stream +a+ and +b+ are identical. + # + def compare_stream(a, b) + bsize = fu_stream_blksize(a, b) + sa = sb = nil + while sa == sb + sa = a.read(bsize) + sb = b.read(bsize) + unless sa and sb + if sa.nil? and sb.nil? + return true + end + end + end + false + end + module_function :compare_stream + + # + # Options: mode preserve noop verbose + # + # If +src+ is not same as +dest+, copies it and changes the permission + # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. + # This method removes destination before copy. + # + # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true + # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true + # + def install(src, dest, options = {}) + fu_check_options options, OPT_TABLE['install'] + fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + return if options[:noop] + fu_each_src_dest(src, dest) do |s, d| + unless File.exist?(d) and compare_file(s, d) + remove_file d, true + st = File.stat(s) if options[:preserve] + copy_file s, d + File.utime st.atime, st.mtime, d if options[:preserve] + File.chmod options[:mode], d if options[:mode] + end + end + end + module_function :install + + OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose] + + # + # Options: noop verbose + # + # Changes permission bits on the named files (in +list+) to the bit pattern + # represented by +mode+. + # + # FileUtils.chmod 0755, 'somecommand' + # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) + # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true + # + def chmod(mode, list, options = {}) + fu_check_options options, OPT_TABLE['chmod'] + list = fu_list(list) + fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose] + return if options[:noop] + list.each do |path| + Entry_.new(path).chmod mode + end + end + module_function :chmod + + OPT_TABLE['chmod'] = [:noop, :verbose] + + # + # Options: noop verbose force + # + # Changes permission bits on the named files (in +list+) + # to the bit pattern represented by +mode+. + # + # FileUtils.chmod_R 0700, "/tmp/app.#{$$}" + # + def chmod_R(mode, list, options = {}) + fu_check_options options, OPT_TABLE['chmod_R'] + list = fu_list(list) + fu_output_message sprintf('chmod -R%s %o %s', + (options[:force] ? 'f' : ''), + mode, list.join(' ')) if options[:verbose] + return if options[:noop] + list.each do |root| + Entry_.new(root).traverse do |ent| + begin + ent.chmod mode + rescue + raise unless options[:force] + end + end + end + end + module_function :chmod_R + + OPT_TABLE['chmod_R'] = [:noop, :verbose, :force] + + # + # Options: noop verbose + # + # Changes owner and group on the named files (in +list+) + # to the user +user+ and the group +group+. +user+ and +group+ + # may be an ID (Integer/String) or a name (String). + # If +user+ or +group+ is nil, this method does not change + # the attribute. + # + # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' + # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true + # + def chown(user, group, list, options = {}) + fu_check_options options, OPT_TABLE['chown'] + list = fu_list(list) + fu_output_message sprintf('chown %s%s', + [user,group].compact.join(':') + ' ', + list.join(' ')) if options[:verbose] + return if options[:noop] + uid = fu_get_uid(user) + gid = fu_get_gid(group) + list.each do |path| + Entry_.new(path).chown uid, gid + end + end + module_function :chown + + OPT_TABLE['chown'] = [:noop, :verbose] + + # + # Options: noop verbose force + # + # Changes owner and group on the named files (in +list+) + # to the user +user+ and the group +group+ recursively. + # +user+ and +group+ may be an ID (Integer/String) or + # a name (String). If +user+ or +group+ is nil, this + # method does not change the attribute. + # + # FileUtils.chown_R 'www', 'www', '/var/www/htdocs' + # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true + # + def chown_R(user, group, list, options = {}) + fu_check_options options, OPT_TABLE['chown_R'] + list = fu_list(list) + fu_output_message sprintf('chown -R%s %s%s', + (options[:force] ? 'f' : ''), + [user,group].compact.join(':') + ' ', + list.join(' ')) if options[:verbose] + return if options[:noop] + uid = fu_get_uid(user) + gid = fu_get_gid(group) + return unless uid or gid + list.each do |root| + Entry_.new(root).traverse do |ent| + begin + ent.chown uid, gid + rescue + raise unless options[:force] + end + end + end + end + module_function :chown_R + + OPT_TABLE['chown_R'] = [:noop, :verbose, :force] + + begin + require 'etc' + + def fu_get_uid(user) #:nodoc: + return nil unless user + user = user.to_s + if /\A\d+\z/ =~ user + then user.to_i + else Etc.getpwnam(user).uid + end + end + private_module_function :fu_get_uid + + def fu_get_gid(group) #:nodoc: + return nil unless group + if /\A\d+\z/ =~ group + then group.to_i + else Etc.getgrnam(group).gid + end + end + private_module_function :fu_get_gid + + rescue LoadError + # need Win32 support??? + + def fu_get_uid(user) #:nodoc: + user # FIXME + end + private_module_function :fu_get_uid + + def fu_get_gid(group) #:nodoc: + group # FIXME + end + private_module_function :fu_get_gid + end + + # + # Options: noop verbose + # + # Updates modification time (mtime) and access time (atime) of file(s) in + # +list+. Files are created if they don't exist. + # + # FileUtils.touch 'timestamp' + # FileUtils.touch Dir.glob('*.c'); system 'make' + # + def touch(list, options = {}) + fu_check_options options, OPT_TABLE['touch'] + list = fu_list(list) + created = nocreate = options[:nocreate] + t = options[:mtime] + if options[:verbose] + fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}" + end + return if options[:noop] + list.each do |path| + created = nocreate + begin + File.utime(t, t, path) + rescue Errno::ENOENT + raise if created + File.open(path, 'a') { + ; + } + created = true + retry if t + end + end + end + module_function :touch + + OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate] + + private + + module StreamUtils_ + private + + def fu_windows? + /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM + end + + # changed + def fu_copy_stream0_in_bar(bar, src, dest, blksize) #:nodoc: + report = false + if File.size?(src) + report = true + ticks = File.size(src) / blksize + bar.max = ticks + bar.counter = i = MutableNumber.new(0) + log('.') + end + txt = "cp #{File.basename(src.path)} ..." + bar.set_text_prefix(txt) + + while s = src.read(blksize) + dest.write s + i.add 1 + end + end + + def fu_copy_stream0(src, dest, blksize) #:nodoc: + # FIXME: readpartial? + while s = src.read(blksize) + dest.write s + end + end + + def fu_stream_blksize(*streams) + streams.each do |s| + next unless s.respond_to?(:stat) + size = fu_blksize(s.stat) + return size if size + end + fu_default_blksize() + end + + def fu_blksize(st) + s = st.blksize + return nil unless s + return nil if s == 0 + s + end + + def fu_default_blksize + 1024 + end + end + + include StreamUtils_ + extend StreamUtils_ + + class Entry_ #:nodoc: internal use only + include StreamUtils_ + + def initialize(a, b = nil, deref = false) + @prefix = @rel = @path = nil + if b + @prefix = a + @rel = b + else + @path = a + end + @deref = deref + @stat = nil + @lstat = nil + end + + def inspect + "\#<#{self.class} #{path()}>" + end + + def path + if @path + @path.to_str + else + join(@prefix, @rel) + end + end + + def prefix + @prefix || @path + end + + def rel + @rel + end + + def dereference? + @deref + end + + def exist? + lstat! ? true : false + end + + def file? + s = lstat! + s and s.file? + end + + def directory? + s = lstat! + s and s.directory? + end + + def symlink? + s = lstat! + s and s.symlink? + end + + def chardev? + s = lstat! + s and s.chardev? + end + + def blockdev? + s = lstat! + s and s.blockdev? + end + + def socket? + s = lstat! + s and s.socket? + end + + def pipe? + s = lstat! + s and s.pipe? + end + + S_IF_DOOR = 0xD000 + + def door? + s = lstat! + s and (s.mode & 0xF000 == S_IF_DOOR) + end + + def entries + Dir.entries(path())\ + .reject {|n| n == '.' or n == '..' }\ + .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) } + end + + def stat + return @stat if @stat + if lstat() and lstat().symlink? + @stat = File.stat(path()) + else + @stat = lstat() + end + @stat + end + + def stat! + return @stat if @stat + if lstat! and lstat!.symlink? + @stat = File.stat(path()) + else + @stat = lstat! + end + @stat + rescue SystemCallError + nil + end + + def lstat + if dereference? + @lstat ||= File.stat(path()) + else + @lstat ||= File.lstat(path()) + end + end + + def lstat! + lstat() + rescue SystemCallError + nil + end + + def chmod(mode) + if symlink? + File.lchmod mode, path() if have_lchmod? + else + File.chmod mode, path() + end + end + + def chown(uid, gid) + if symlink? + File.lchown uid, gid, path() if have_lchown? + else + File.chown uid, gid, path() + end + end + + #changed + def copy_in_bar(bar, dest) + case + when file? + copy_file_in_bar bar, dest + when directory? + begin + Dir.mkdir dest + rescue + raise unless File.directory?(dest) + end + when symlink? + File.symlink File.readlink(path()), dest + when chardev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?c, 0666, lstat().rdev + when blockdev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?b, 0666, lstat().rdev + when socket? + raise "cannot handle socket" unless File.respond_to?(:mknod) + mknod dest, nil, lstat().mode, 0 + when pipe? + raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) + mkfifo dest, 0666 + when door? + raise "cannot handle door: #{path()}" + else + raise "unknown file type: #{path()}" + end + end + + def copy(dest) + case + when file? + copy_file dest + when directory? + begin + Dir.mkdir dest + rescue + raise unless File.directory?(dest) + end + when symlink? + File.symlink File.readlink(path()), dest + when chardev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?c, 0666, lstat().rdev + when blockdev? + raise "cannot handle device file" unless File.respond_to?(:mknod) + mknod dest, ?b, 0666, lstat().rdev + when socket? + raise "cannot handle socket" unless File.respond_to?(:mknod) + mknod dest, nil, lstat().mode, 0 + when pipe? + raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) + mkfifo dest, 0666 + when door? + raise "cannot handle door: #{path()}" + else + raise "unknown file type: #{path()}" + end + end + + def copy_file(dest) + st = stat() + File.open(path(), 'rb') {|r| + File.open(dest, 'wb', st.mode) {|w| + fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize()) + } + } + end + + # changed + def copy_file_in_bar(bar, dest) + st = stat() + File.open(path(), 'rb') {|r| + File.open(dest, 'wb', st.mode) {|w| + fu_copy_stream0_in_bar bar, r, w, (fu_blksize(st) || fu_default_blksize()) + } + } + end + + def copy_metadata(path) + st = lstat() + File.utime st.atime, st.mtime, path + begin + File.chown st.uid, st.gid, path + rescue Errno::EPERM + # clear setuid/setgid + File.chmod st.mode & 01777, path + else + File.chmod st.mode, path + end + end + + def remove + if directory? + remove_dir1 + else + remove_file + end + end + + def remove_dir1 + platform_support { + Dir.rmdir path().sub(%r, '') + } + end + + def remove_file + platform_support { + File.unlink path + } + end + + def platform_support + return yield unless fu_windows? + first_time_p = true + begin + yield + rescue Errno::ENOENT + raise + rescue => err + if first_time_p + first_time_p = false + begin + File.chmod 0700, path() # Windows does not have symlink + retry + rescue SystemCallError + end + end + raise err + end + end + + def preorder_traverse + stack = [self] + while ent = stack.pop + yield ent + stack.concat ent.entries.reverse if ent.directory? + end + end + + alias traverse preorder_traverse + + def postorder_traverse + if directory? + entries().each do |ent| + ent.postorder_traverse do |e| + yield e + end + end + end + yield self + end + + private + + $fileutils_rb_have_lchmod = nil + + def have_lchmod? + # This is not MT-safe, but it does not matter. + if $fileutils_rb_have_lchmod == nil + $fileutils_rb_have_lchmod = check_have_lchmod? + end + $fileutils_rb_have_lchmod + end + + def check_have_lchmod? + return false unless File.respond_to?(:lchmod) + File.lchmod 0 + return true + rescue NotImplementedError + return false + end + + $fileutils_rb_have_lchown = nil + + def have_lchown? + # This is not MT-safe, but it does not matter. + if $fileutils_rb_have_lchown == nil + $fileutils_rb_have_lchown = check_have_lchown? + end + $fileutils_rb_have_lchown + end + + def check_have_lchown? + return false unless File.respond_to?(:lchown) + File.lchown nil, nil + return true + rescue NotImplementedError + return false + end + + def join(dir, base) + return dir.to_str if not base or base == '.' + return base.to_str if not dir or dir == '.' + File.join(dir, base) + end + end # class Entry_ + + def fu_list(arg) #:nodoc: + [arg].flatten.map {|path| path.to_str } + end + private_module_function :fu_list + + def fu_each_src_dest(src, dest) #:nodoc: + fu_each_src_dest0(src, dest) do |s, d| + raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d) + yield s, d + end + end + private_module_function :fu_each_src_dest + + def fu_each_src_dest0(src, dest) #:nodoc: + if src.is_a?(Array) + src.each do |s| + s = s.to_str + yield s, File.join(dest, File.basename(s)) + end + else + src = src.to_str + if File.directory?(dest) + yield src, File.join(dest, File.basename(src)) + else + yield src, dest.to_str + end + end + end + private_module_function :fu_each_src_dest0 + + def fu_same?(a, b) #:nodoc: + if fu_have_st_ino? + st1 = File.stat(a) + st2 = File.stat(b) + st1.dev == st2.dev and st1.ino == st2.ino + else + File.expand_path(a) == File.expand_path(b) + end + rescue Errno::ENOENT + return false + end + private_module_function :fu_same? + + def fu_have_st_ino? #:nodoc: + not fu_windows? + end + private_module_function :fu_have_st_ino? + + def fu_check_options(options, optdecl) #:nodoc: + h = options.dup + optdecl.each do |opt| + h.delete opt + end + raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? + end + private_module_function :fu_check_options + + def fu_update_option(args, new) #:nodoc: + if args.last.is_a?(Hash) + args[-1] = args.last.dup.update(new) + else + args.push new + end + args + end + private_module_function :fu_update_option + + @fileutils_output = $stderr + @fileutils_label = '' + + def fu_output_message(msg) #:nodoc: + @fileutils_output ||= $stderr + @fileutils_label ||= '' + @fileutils_output.puts @fileutils_label + msg + end + private_module_function :fu_output_message + + # + # Returns an Array of method names which have any options. + # + # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] + # + def FileUtils.commands + OPT_TABLE.keys + end + + # + # Returns an Array of option names. + # + # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] + # + def FileUtils.options + OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } + end + + # + # Returns true if the method +mid+ have an option +opt+. + # + # p FileUtils.have_option?(:cp, :noop) #=> true + # p FileUtils.have_option?(:rm, :force) #=> true + # p FileUtils.have_option?(:rm, :perserve) #=> false + # + def FileUtils.have_option?(mid, opt) + li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" + li.include?(opt) + end + + # + # Returns an Array of option names of the method +mid+. + # + # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"] + # + def FileUtils.options_of(mid) + OPT_TABLE[mid.to_s].map {|sym| sym.to_s } + end + + # + # Returns an Array of method names which have the option +opt+. + # + # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] + # + def FileUtils.collect_method(opt) + OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } + end + + METHODS = singleton_methods() - %w( private_module_function + commands options have_option? options_of collect_method ) + + # + # This module has all methods of FileUtils module, but it outputs messages + # before acting. This equates to passing the :verbose flag to + # methods in FileUtils. + # + module Verbose + include FileUtils + @fileutils_output = $stderr + @fileutils_label = '' + ::FileUtils.collect_method(:verbose).each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args) + super(*fu_update_option(args, :verbose => true)) + end + private :#{name} + EOS + end + extend self + class << self + ::FileUtils::METHODS.each do |m| + public m + end + end + end + + # + # This module has all methods of FileUtils module, but never changes + # files/directories. This equates to passing the :noop flag + # to methods in FileUtils. + # + module NoWrite + include FileUtils + @fileutils_output = $stderr + @fileutils_label = '' + ::FileUtils.collect_method(:noop).each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args) + super(*fu_update_option(args, :noop => true)) + end + private :#{name} + EOS + end + extend self + class << self + ::FileUtils::METHODS.each do |m| + public m + end + end + end + + # + # This module has all methods of FileUtils module, but never changes + # files/directories, with printing message before acting. + # This equates to passing the :noop and :verbose flag + # to methods in FileUtils. + # + module DryRun + include FileUtils + @fileutils_output = $stderr + @fileutils_label = '' + ::FileUtils.collect_method(:noop).each do |name| + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args) + super(*fu_update_option(args, :noop => true, :verbose => true)) + end + private :#{name} + EOS + end + extend self + class << self + ::FileUtils::METHODS.each do |m| + public m + end + end + end +end + +# vi: sts=2 ts=2 sw=2 diff --git a/code/fm.rb b/code/fm.rb index e5b66aad..d6c8cb85 100644 --- a/code/fm.rb +++ b/code/fm.rb @@ -9,6 +9,9 @@ OPTIONS = { module Fm + SCHEDULER_PRIORITY = -1 + COPY_PRIORITY = -2 + SCHEDULED = [] COLUMNS = 4 UPDATE_SIGNAL = 31 @@ -53,9 +56,26 @@ module Fm draw end +# for i in 1..20 +# eval "Signal.trap(#{i}) do +# log #{i} +# exit if #{i} == 9 end" +# end + + boot_up(pwd) + end + + attr_reader(:dirs, :pwd) + + def self.pwd() @pwd end + def self.boot_up(pwd=nil) + pwd ||= @pwd.path || Dir.getwd # This thread inspects directories @scheduler_active = false + if defined? @scheduler and Thread === @scheduler + @scheduler.kill + end @scheduler = Thread.new do while true Thread.stop @@ -70,11 +90,12 @@ module Fm end end end + @scheduler.priority = SCHEDULER_PRIORITY @dirs = Hash.new() do |hash, key| hash[key] = newdir = Directory.new(key) - schedule newdir +# newdir.schedule newdir end @@ -85,8 +106,6 @@ module Fm @scheduler.run end - attr_reader(:dirs, :pwd) - def self.force_update # Send a signal to this process Process.kill(UPDATE_SIGNAL, PID) @@ -110,6 +129,7 @@ module Fm end def self.main_loop + bool = false while true if @pwd.size == 0 or @pwd.pos < 0 @pwd.pos = 0 @@ -119,17 +139,24 @@ module Fm begin # @mutex.synchronize { + log "drawing" draw() # } rescue Interrupt on_interrupt rescue Exception -# log($!) -# log(caller) + log($!) + log(caller) end begin - key = geti +# unless bool +# bool = true + key = geti +# else +# key = geti +# key = 'j' +# end # @mutex.synchronize { press(key) # } @@ -141,6 +168,8 @@ module Fm def self.current_path() @pwd.path end + def self.reset_title() set_title("fm: #{@pwd.path}") end + def self.enter_dir_safely(dir) dir = File.expand_path(dir) if File.exists?(dir) and File.directory?(dir) @@ -149,6 +178,9 @@ module Fm enter_dir(dir) return true rescue + log("NIGGER" * 100) + log($!) + log(caller) enter_dir(olddir) return false end @@ -157,7 +189,7 @@ module Fm def self.enter_dir(dir) @pwd.restore if @pwd - @marks = 0 + @marked = [] dir = File.expand_path(dir) oldpath = @path.dup @@ -176,12 +208,13 @@ module Fm end @pwd = @path.last + @pwd.pos = @pwd.pos @pwd.files_raw.dup.each do |x| @dirs[x] if File.directory?(x) end - set_title "fm: #{@pwd.path}" + reset_title() if @path.size < oldpath.size @pwd.pos = @pwd.files_raw.index(oldpath.last.path) || 0 @@ -200,8 +233,16 @@ module Fm end def self.currentfile() @pwd.files[@pwd.pos] end + def self.selection() + if @marked.empty? + [currentfile] + else + @marked.dup + end + end def self.schedule(dir) + dir.scheduled = true SCHEDULED << dir @scheduler.run end diff --git a/code/keys.rb b/code/keys.rb index d6ae01ea..edcbab53 100644 --- a/code/keys.rb +++ b/code/keys.rb @@ -2,11 +2,13 @@ module Fm # ALL combinations of multiple keys (but without the last letter) # or regexps which match combinations need to be in here! COMBS = %w( - g d y c Z delet cu - t S ? ?g ?f + g d df y c Z delet cu + ter ta S ? ?g ?f :q - /[m`']/ /[fF/!].*/ + /[m`']/ /[fF/!q].*/ + /[ri]\d*\w*[^ri]/ /(cw|cd|mv).*/ + /b(l(o(c(k(.*)?)?)?)?)?/ /m(k(d(i(r(.*)?)?)?)?)?/ /r(e(n(a(m(e(.*)?)?)?)?)?)?/ ) @@ -54,6 +56,16 @@ module Fm end end + def self.find_newest() + newest = nil + for f in @pwd.files + if newest.nil? or newest.ctime < f.ctime + newest = f + end + end + @pwd.pointed_file = newest.path + end + def self.hints(str) begin rx = Regexp.new(str, Regexp::IGNORECASE) @@ -70,6 +82,7 @@ module Fm g = File.basename(f) if g =~ rx unless pointed + log "point at #{f}" @pwd.pointed_file = f pointed = true end @@ -99,6 +112,8 @@ module Fm else @buffer.slice! -1 end + elsif key == '' + @buffer = '' else @buffer << key end @@ -123,26 +138,59 @@ module Fm starti - when /S(.)/ + when /^S(.)$/ OPTIONS['sort_reverse'] = $1.ord.between?(65, 90) case $1 when 'n' OPTIONS['sort'] = :name + when 'e' + OPTIONS['sort'] = :ext + when 't' + OPTIONS['sort'] = :type when 's' OPTIONS['sort'] = :size - when 'e' - OPTIONS['sort'] = :extension when 'm' OPTIONS['sort'] = :mtime - when 'c', 't' + when 'c' OPTIONS['sort'] = :ctime end @pwd.schedule - when 'r', 'R' + when 'tar' + closei + system('tar', 'cvvf', 'pack.tar', *selection.map{|x| x.basename}) + @pwd.refresh! + starti + + when 'R' @pwd.refresh! + when '@', '.' + if defined? @record + @buffer = '' + memo = '' + @record.each_char do |c| + if memo.empty? + if c == '<' + memo << c + else + press c + end + else + memo << c + if c == '>' + press memo + memo.clear + end + end + end + end + + when /^q.+q$/ + @record = @buffer[1...-1] + @buffer = '' + when 'x' @bars.first.kill unless @bars.empty? @@ -156,23 +204,19 @@ module Fm @pwd.pos -= lines/2 when 'cp', 'yy' - if @marked.empty? - @copy = [currentfile] - else - @copy = @marked.dup - end + @copy = selection @cut = false when 'cut' - if @marked.empty? - @copy = [currentfile] - else - @copy = @marked.dup - end + @copy = selection @cut = true when 'n' - search(@search_string, 1) + if @search_string.empty? + find_newest + else + search(@search_string, 1) + end when 'N' search(@search_string, 0, true) @@ -206,6 +250,9 @@ module Fm end end + when 'A' + @buffer = "cw #{currentfile.name}" + when /^f(.+)$/ str = $1 if str =~ /^\s?(.*)(L|;||)$/ @@ -245,6 +292,9 @@ module Fm @pwd.schedule end end + + when /^block.*stop$/ + @buffer = '' when /^!(.+)$/ str = $1 @@ -269,14 +319,41 @@ module Fm end end - when /^(?:mv|cw|rename)(.+)$/ - str = $1 + when /^(mv|cw|rename)(.+)$/ + str = $2 + if $1 == 'mv' + if str =~ /['`"]([\w\d])/ + if path = @memory[$1] + str = '' + @buffer.clear + if File.exists?(path) and File.directory?(path) + Action.move(selection, path) + end + end + end + end + log str if str =~ /^\s?(.*)(|)$/ @buffer = '' if $2 == '' - Action.move(currentfile, $1) + files = selection + if files.size == 1 + fn = $1 + log "!!! #{fn}" + unless fn.include? '.' + if ext = files.first.basename.from_last('.') + fn << ".#{ext}" + end + log "??? #{ext}" + end + Action.move(files, fn) + @pwd.refresh! + @pwd.find_file(fn) + else + Action.move(files, $1) + @pwd.refresh! + end end - @pwd.schedule end when 'tc' @@ -294,7 +371,7 @@ module Fm @pwd.schedule when 'delete' - files = @marked.empty? ? [currentfile] : @marked + files = selection @marked = [] for f in files if f and f.exists? and f.dir? @@ -310,6 +387,10 @@ module Fm else Action.copy(@copy, @pwd.path) end + @pwd.refresh! + if @copy.size == 1 + @pwd.find_file(@copy[0].basename) + end when /^[`'](.)$/ if dir = @memory[$1] and not @pwd.path == dir @@ -317,12 +398,22 @@ module Fm enter_dir_safely(dir) end - when '' + when '' if dir = @memory['`'] and not @pwd.path == dir remember_dir enter_dir_safely(dir) end + when '' + if dir = @memory['9'] and dir != '/' + unless @pwd.path == dir + enter_dir_safely(dir) + end + elsif dir = @memory['`'] and not @pwd.path == dir + remember_dir + enter_dir_safely(dir) + end + when /^m(.)$/ @memory[$1] = @pwd.path @@ -369,13 +460,16 @@ module Fm end @pwd.schedule - when 'dD' + when 'dD', 'dfd' cf = currentfile if cf and cf.exists? cf.delete! @pwd.schedule end + when 'term' + fork do exec 'x-terminal-emulator' end + when 'g0' remember_dir enter_dir('/') @@ -418,31 +512,93 @@ module Fm end when '', 'l', ';', 'L', '' - ascend(@buffer=='L') + ascend(@buffer=='L', @buffer=='l') + + # a = run all + # d or e = detach + # t = run in a terminal + # w = wait for after execution + # capital letter inverts + when /^[ri](\d*)([adetw]*)[ri]$/ + if $2.empty? + f = @marked.empty?? currentfile : @marked.first + flags = get_default_flags(f) + else + flags = $2 + end + opt = OpenStruct.new + opt.newway = true + + opt.mode = $1.to_i unless $1.empty? + + # Set options based on flags + + if flags =~ /a/ + opt.all = true + end + if flags =~ /[de]/ + opt.detach = true + end + if flags =~ /t/ + opt.new_term = true + opt.detach = true + end + if flags =~ /w/ + opt.wait = true + end - when 'q', 'ZZ', "\004" + if flags =~ /A/ + opt.all = false + end + if flags =~ /[DE]/ + opt.detach = false + end + if flags =~ /T/ + opt.new_term = false + end + if flags =~ /W/ + opt.wait = false + end + + Action.run(opt.__table__) + +# when 'ra' +# unless File.directory?(currentfile.path) +# Action.run(:all=>true) +# end + + when 'ZZ', '', ':q' exit + + when '' + Fm.boot_up + + when "-", "=" + val = "2#{key=='-' ? '-' : '+'}" + system("amixer", "-q", "set", "PCM", val, "unmute") + + else +# log key.ord end @buffer = '' unless @buffer == '' or @buffer =~ REGX end - def self.ascend(wait = false) - cf = currentfile - enter = enter_dir_safely(cf.path) - unless cf.nil? or enter - handler, wait = getfilehandler(currentfile) - if handler - closei - log handler - system(handler) - gets if wait - starti - return true + def self.ascend(wait = false, all=false) + if all and !@marked.empty? + closei + system(*['mplayer', '-fs', *@marked.map{|x| x.path}]) + starti + return true + else + cf = currentfile + enter = enter_dir_safely(cf.path) + unless enter + return Action.run(:detach=>false) end + return false end - return false end def self.descend diff --git a/code/screensaver/clock.rb b/code/screensaver/clock.rb new file mode 100644 index 00000000..6289c809 --- /dev/null +++ b/code/screensaver/clock.rb @@ -0,0 +1,10 @@ +def screensaver + cleari + + str = Time.now.to_s + s = Fm.cols - str.size + s = 1 if s < 0 + + puti((Fm.lines.to_f/2).floor, s/2, str) +# puti(rand(Fm.lines), rand(Fm.cols), 'MEDITONSIN') +end diff --git a/code/types.rb b/code/types.rb index 0fc0bf7c..96d67d06 100644 --- a/code/types.rb +++ b/code/types.rb @@ -1,5 +1,96 @@ module Fm - def self.getfilehandler_frompath(file) + MIMETYPES = Marshal.load(File.read( + File.join(FM_DIR, 'data', 'mime.dat'))) + + def self.get_default_flags(file) + case file.mimetype + when /^(?:image|video)\//; 'd' + when 'application/pdf'; 'd' + else '' end + end + + def self.filehandler(files, hash) + str = files.map{|x| x.sh}.join(' ') + type = files.first.mimetype + name = files.first.basename +# mode = hash.mode + + use = lambda do |sym| + hash.exec = App.send(sym, hash, name, str, files) + end + + case type + when /^(video|audio)\// + use.call :mplayer + when "application/pdf" + use.call :evince + when /^(image)\// + use.call :image + else + case name + when /\.(swc|smc)/ + use.call :zsnes + end + end + + return hash + end + + module App + def image(hash, name, str, file) + case hash.mode + when 4; "feh --bg-scale #{str}" + when 5; "feh --bg-tile #{str}" + when 6; "feh --bg-center #{str}" + when 2; "gimp #{str}" + when 1; "feh -F #{str}" + else "feh #{str}" + end + end + def evince(hash, name, str, file) + "evince #{str}" + end + def mplayer(*args) + hash = args[0] = args[0].dup + str = args[2] + + if hash.detach + flags = '-msglevel all=-1' + else + flags = '' + end + + case hash.mode + when nil + if name =~ /720p/ + hash.mode = 1 + else + hash.mode = 0 + end + mplayer(*args) + when 0 + return "mplayer #{flags} -fs -sid 0 #{str}" + when 1 + return "mplayer #{flags} -vm sdl -sid 0 #{str}" + end + end + def zsnes(hash, name, str, files) + case hash.mode + when 1 + return "zsnes -ad sdl -o #{str}" + else + return "zsnes -ad sdl -u -o #{str}" + end + end + + module_function *%w* + mplayer zsnes evince image + * + end + + def self.getfilehandler_frompath(*files) + file = files.first + n = files.size case file when /\.(part|avi|mpe?[g\d]|flv|fid|mkv|mov|wm[av]|vob|php|divx?|og[gmv])$/i if file =~ /720p/ @@ -8,6 +99,12 @@ module Fm return "mplayer -fs #{file.sh}", false end + when /\.java$/ + return "javac #{file.sh}", true + + when /\.class$/ + return log "java #{file.sh.before_last('.')}" + when /\.part$/ test = getfilehandler_frompath($`) if test @@ -23,10 +120,10 @@ module Fm when "Makefile" return "make" - when /\.(jpe?g|png)$/i + when /\.(jpe?g|png|gif)$/i return "feh #{file.sh}", false - when /\.html?$/i + when /\.(html?|swf)$/i return "firefox #{file.sh}" when /\.pdf$/i @@ -39,7 +136,8 @@ module Fm return "aplay -q #{file.sh}" when /\.m3u$/i - return "cmus-remote -c && cmus-remote -P #{file} && cmus-remote -C 'set play_library=false' && sleep 0.3 && cmus-remote -n", false + return "/home/hut/bin/loadplaylist #{file.sh}" +# return "cmus-remote -c && cmus-remote -P #{file} && cmus-remote -C 'set play_library=false' && sleep 0.3 && cmus-remote -n", false end -- cgit 1.4.1-2-gfad0