diff options
Diffstat (limited to 'philesight/philesight.rb')
-rw-r--r-- | philesight/philesight.rb | 458 |
1 files changed, 0 insertions, 458 deletions
diff --git a/philesight/philesight.rb b/philesight/philesight.rb deleted file mode 100644 index b669927..0000000 --- a/philesight/philesight.rb +++ /dev/null @@ -1,458 +0,0 @@ -#!/usr/bin/ruby -# vi: ts=2 sw=2 - -require 'getoptlong' -require 'cgi' -require 'cairo' -require 'bdb' - -class PNGWriter - - def initialize(fname) - if fname != "-" then - @fd = File.open(fname, "w") - end - end - - def write(data) - if @fd then - @fd.write(data) - else - print(data) - end - return data.length - end -end - - -class Philesight - - def initialize(ringcount=4, size=800, use_gradient=true) - @max_files_per_dir = 50 - @w = size - @h = size - @cx = @w / 2 - @cy = @h / 2 - @ringcount = ringcount - @ringwidth = ((size-50)/2) / (ringcount+1) - @use_gradient = use_gradient - @find_a = 0 - @find_r = 0 - end - - - # - # Open the database. Try read-write mode first, if this fails, re-open readonly - # - - def db_open(fname) - begin - @db = BDB::Btree.open fname , nil, BDB::CREATE, 0644, "set_pagesize" => 1024, "set_cachesize" => [0, 32*1024,0] - rescue - @db = BDB::Btree.open fname , nil, BDB::RDONLY, 0644, "set_pagesize" => 1024, "set_cachesize" => [0, 32*1024,0] - end - end - - - # - # Dump database in human-readable form - # - - def dump - @db.keys.each do |f| - puts "%s %s" % [ f, Marshal::load(@db[f]).inspect ] - end - end - - - # - # Concatenate a directory and a filename - # - - def addpath(a, b) - return a + b if(a =~ /\/$/) - return a + "/" + b - end - - - # - # Read a directory and add to the database; this function is recursive - # for sub-directories - # - - def readdir(dir) - - size_file = {} - size_dir = {} - size_total = 0 - - # Traverse the directory and collect the size of all files and - # directories - - begin - Dir.foreach(dir) do |f| - - if(f != "." && f != "..") then - - f_full = addpath(dir, f) - stat = File.lstat(f_full) - - if(!stat.symlink?) then - - if(stat.file?) then - size = File.size(f_full) - size_file[f] = size - size_total += size - end - - if(stat.directory?) then - size = readdir(f_full) - if(size > 0) then - size_dir[f] = size - size_total += size - end - end - end - end - - end - rescue SystemCallError => errmsg - puts errmsg - end - - # If there are a lot of small files in this directory, group - # the smallest into one entry to avoid clutter - - if(size_file.keys.length > @max_files_per_dir) then - list = size_file.keys.select { |f| size_file[f] < size_total / @max_files_per_dir } - rest = 0 - list.each do |f| - rest += size_file[f] - size_file.delete(f) - end - size_file['rest'] = rest - end - - # Store the files in the database - - size_file.keys.each do |f| - f_full = addpath(dir, f) - @db[f_full] = Marshal::dump( [ size_file[f], [] ] ) - end - - # Store this directory with the list of children in the database - - size = size_dir.merge(size_file) - children = size.keys.sort - @db[dir] = Marshal::dump( [ size_total, children ] ) - return size_total - end - - - # - # Draw one section - # - - def draw_section(cr, ang_from, ang_to, r_from, r_to, brightness) - - ang_to, ang_from = ang_from, ang_to if (ang_to < ang_from) - - if(brightness > 0) then - r, g, b = hsv2rgb((ang_from+ang_to)*0.5 / (Math::PI*2), 1.0-brightness, brightness/2+0.5) - else - r, g, b = 0.9, 0.9, 0.9 - end - - # Instead of using the r_from and r_to for the radial pattern, the stops - # are calculated. This is to work around a bug in cairo - - r_total = ((@w - 50) / 2).to_f - pat = Cairo::RadialPattern.new(@cx, @cy, 0, @cx, @cy, r_total) - if @use_gradient then - pat.add_color_stop_rgb(r_from / r_total, r*0.8, g*0.8, b*0.8) - pat.add_color_stop_rgb(r_to / r_total, r*1.5, g*1.5, b*1.5) - else - pat.add_color_stop_rgb(0, r, g, b) - end - - cr.new_path - cr.arc(@cx, @cy, r_from, ang_from, ang_to) - cr.arc_negative(@cx, @cy, r_to, ang_to, ang_from) - cr.close_path - - cr.set_source(pat) - cr.fill_preserve - end - - - # - # Draw ring. This function is recursive for the outer rings - # - - def draw_ring(cr, level, ang_min, ang_max, path) - - ang_from = ang_min - ang_to = ang_min - ang_range = (ang_max - ang_min).to_f - r_from = level * @ringwidth - r_to = r_from + @ringwidth - - unless(@db[path]) then - return "/" - end - - total_path, child_path = Marshal::load( @db[path] ) - - # Draw a section proportional to the size of each file or subdir - - child_path.each do |f| - - f_full = addpath(path, f) - total_f, child_f = Marshal::load( @db[f_full] ) - - # Calculate start and end angles and draw section - - ang_from = ang_to - ang_to += ang_range * total_f / total_path if(total_path > 0) - brightness = r_from.to_f / @cx - brightness = 0 if(f == 'rest') - draw_section(cr, ang_from, ang_to, r_from, r_to, brightness) if(cr) - - # If we are looking for the path of an (x,y) pair, check if this section matches - - if( (@find_a >= ang_from) && (@find_a <= ang_to) && (@find_r >= r_from) && (@find_r <= r_to) ) then - @find_path = f_full - end - - # Draw outer rings - - if(level < @ringcount) then - draw_ring(cr, level+1, ang_from, ang_to, f_full) - else - draw_section(cr, ang_from, ang_to, r_to, r_to+5, 0.5) if(cr && child_f.nitems > 0) - end - - # Generate and save labels of filenames/sizes - - if(cr && (ang_to - ang_from > 0.2)) then - size = filesize_readable(total_f) - x, y = pol2xy((ang_from+ang_to)/2, (r_from+r_to)/2) - label = {} - label[:x] = x - label[:y] = y - label[:text] = "%s\n%s" % [ f, size] - @labels << label - end - end - end - - - # - # Draw graph of the given path - # - - def draw(path, fname) - - # Create drawing context, white background - - format = Cairo::FORMAT_ARGB32 - surf = Cairo::ImageSurface.new(format, @w, @h) - cr = Cairo::Context.new(surf) - writer = PNGWriter.new(fname) - - # Draw top level filename and size - - unless(@db[path]) then - draw_text(cr, @cx, @cy, "Path '#{path}' not found in database", 12) - cr.target.write_to_png(writer) - return - end - - total_path, child_path = Marshal::load( @db[path] ) - draw_text(cr, @cx, 10, "%s (%s)" % [ path, filesize_readable(total_path) ], 14, true) - draw_text(cr, @cx, @cy, "cd ..", 14, true) - - # Draw rings, recursively - - @labels = [] - draw_ring(cr, 1, 0, Math::PI*2, path) - - # Draw circles on ring borders - - cr.set_source_rgba(0, 0, 0, 0.7) - 0.upto(@ringcount+1) do | level | - cr.new_path - cr.set_line_width(0.3) - cr.arc(@cx, @cy, level * @ringwidth, 0, 2*Math::PI) - cr.stroke - end - - # Draw labels on top of graph - - @labels.each do |label| - - cr.select_font_face("Sans", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL) - cr.set_font_size(9) - - # Draw text 4 times in a dark color, one time white on top - - [[-1, 0, 0.2], [+1, 0, 0.2], [0, -1, 0.2], [0, +1, 0.2], [0, 0, 0.9]].each do |dx, dy, color| - cr.set_source_rgba(color, color, color, 1.0) - draw_text(cr, label[:x]+dx, label[:y]+dy, label[:text]) - end - - end - - # Generate PNG file - - cr.target.write_to_png(writer) - end - - - # - # List files/dir of the given path - # - - def listcontent(path) - - unless(@db[path]) then - return nil - end - - dircontent = [] - - total_path, child_path = Marshal::load( @db[path] ) - - currentdir = {} - currentdir[:path] = path - currentdir[:humansize] = filesize_readable(total_path) - - child_path.each do |f| - f_full = addpath(path, f) - total_f, child_f = Marshal::load( @db[f_full] ) - - fileinfo = {} - fileinfo[:path] = f_full - fileinfo[:size] = Integer(total_f) - fileinfo[:humansize] = filesize_readable(total_f) - dircontent << fileinfo - end - - return [currentdir, dircontent] - end - - - # - # Find the path belonging to a (x,y) position in the graph - # - - def find(path, x, y) - @find_a, @find_r = xy2pol(x, y) - @find_path = File.dirname(path) - draw_ring(nil, 1, 0, Math::PI*2, path) - @find_path - end - - - private - - - # - # Draw text on pos x,y - # - - def draw_text(cr, x, y, text, size=11, bold=false) - - lines = text.count("\n") + 1 - y -= (lines-1) * (size+2) / 2.0 - - cr.select_font_face("Sans", Cairo::FONT_SLANT_NORMAL, bold ? Cairo::FONT_WEIGHT_BOLD : Cairo::FONT_WEIGHT_NORMAL) - cr.set_font_size(size) - - text.split("\n").each do |line| - extents = cr.text_extents(line) - w = extents.width - h = extents.height - cr.move_to(x - w/2, y + h/2) - cr.show_text(line) - y += size+2 - end - end - - - # - # convert color from (h,s,v) to (r,g,b) colorspace - # - - def hsv2rgb(h, s, v) - - h = h.to_f - s = s.to_f - v = v.to_f - - h *= 6.0 - i = h.floor - f = h - i - f = 1-f if ((i & 1) == 0) - m = v * (1 - s) - n = v * (1 - s * f) - i=0 if(i<0) - i=6 if(i>6) - - case i - when 0, 6: r=v; g=n; b=m - when 1: r=n; g=v; b=m - when 2: r=m; g=v; b=n - when 3: r=m; g=n; b=v - when 4: r=n; g=m; b=v - when 5: r=v; g=m; b=n - end - - [r, g, b] - end - - - # - # Convert polair (ang,radius) coordinate to cartesian (x,y) - # - - def pol2xy(a, r) - x = Math.cos(a) * r + @cx - y = Math.sin(a) * r + @cy - [x, y] - end - - # - # Convert cartesian (x,y) coordinate to polair (ang, radius) - # - - def xy2pol(x, y) - x -= @cx; - y -= @cy; - a = Math.atan2(y, x) - a += 2*Math::PI if(a<0) - r = Math.sqrt(x*x + y*y) - [a, r] - end - - - # - # Convert a filesize in bytes to a human readable form - # - - def filesize_readable(size) - if(size > 1024*1024*1024) then - return "%.1fG" % (size / (1024.0*1024*1024)) - elsif(size > 1024*1024) then - return "%.1fM" % (size / (1024.0*1024)) - elsif(size > 1024) then - return "%.1fK" % (size / (1024.0)) - end - size - end - -end - -# -# End -# |