summaryrefslogtreecommitdiffstats
path: root/philesight/philesight.rb
diff options
context:
space:
mode:
Diffstat (limited to 'philesight/philesight.rb')
-rw-r--r--philesight/philesight.rb458
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
-#