ripnews/news/newsrc.rb

530 lines
12 KiB
Ruby
Raw Permalink Normal View History

2005-05-09 11:53:18 +00:00
# $Dwarf: newsrc.rb,v 1.13 2004/06/16 08:16:58 ward Exp $
2002-04-27 20:34:15 +00:00
# $Source$
2003-07-20 20:32:24 +00:00
2002-04-27 20:34:15 +00:00
#
2003-07-20 20:32:24 +00:00
# Copyright (c) 2002, 2003 Ward Wouts <ward@wouts.nl>
2002-04-27 20:31:59 +00:00
#
2003-07-20 20:32:24 +00:00
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2002-04-27 20:31:59 +00:00
#
require Pathname.new(__FILE__).dirname + "../set/intspan"
2008-02-12 15:19:13 +00:00
#require "thread"
2002-04-27 20:31:59 +00:00
module News
class Newsrc
2008-02-12 15:19:13 +00:00
#@@save_lock = Mutex.new
2002-04-27 20:31:59 +00:00
def initialize(file=nil)
@newsrc = { "group" => Hash.new, "list" => Array.new }
if file
unless load(file)
2008-02-12 15:19:13 +00:00
puts "Can't load #{file}"
2002-04-27 20:31:59 +00:00
exit
end
end
end
def load(file=nil)
file = "#{ENV['HOME']}/.newsrc" unless file
@newsrc["file"] = file
@newsrc["group"] = {}
@newsrc["list"] = []
if FileTest.file?( "#{file}" ) and FileTest.readable?( "#{file}" )
lines = IO.readlines("#{file}")
import_rc(lines)
end
2002-04-27 20:31:59 +00:00
return true
end
def import_rc(lines)
@newsrc["group"] = {}
@newsrc["list"] = []
linenumber = 1
2008-02-06 13:11:57 +00:00
lines.each{|line|
2002-04-27 20:31:59 +00:00
parse(line)
2008-02-06 13:11:57 +00:00
}
2002-04-27 20:31:59 +00:00
end
def parse(line)
unless line =~ /^([^!:]+)([!:])\s(.*)$/x
2008-02-12 15:19:13 +00:00
puts "Newsrc.parse: Bad newsrc line: #{line}"
2002-04-27 20:31:59 +00:00
exit
end
name = $1
mark = $2
articles = $3
unless Set::IntSpan.valid(articles)
2008-02-12 15:19:13 +00:00
puts "Newsrc.parse: Bad article list: #{line}"
2002-04-27 20:31:59 +00:00
end
group = { "name" => name, "subscribed" => (mark == ":"),
"articles" => Set::IntSpan.new(articles)}
@newsrc["group"][name] = group
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
2002-04-27 20:31:59 +00:00
end
def save
unless @newsrc.has_key?("file")
@newsrc["file"] = "#{$ENV['HOME']}/.newsrc"
end
save_as(@newsrc["file"])
end
2008-02-12 15:19:13 +00:00
# this is not thread safe! (well, it should be now)
2002-04-27 20:31:59 +00:00
def save_as(file)
2008-02-12 15:19:13 +00:00
# @@save_lock.synchronize{
2002-04-27 20:31:59 +00:00
if FileTest.exists?("#{file}")
begin
FileUtils.mv(file, "#{file}.bak")
2002-04-27 20:31:59 +00:00
rescue
2008-02-12 15:19:13 +00:00
puts "Can't rename #{file}, #{file}.bak: #{$!}"
2002-04-27 20:31:59 +00:00
exit
end
end
begin
newsrc = File.new(file, "w")
2008-02-12 15:19:13 +00:00
newsrc.flock(File::LOCK_EX)
2002-04-27 20:31:59 +00:00
rescue
2008-02-12 15:19:13 +00:00
puts "Can't open #{file}: #{$!}"
2002-04-27 20:31:59 +00:00
exit
end
@newsrc["file"] = file
2008-02-06 13:11:57 +00:00
@newsrc["list"].each{|group|
2002-04-27 20:31:59 +00:00
newsrc.print format(group)
2008-02-06 13:11:57 +00:00
}
2008-02-12 15:19:13 +00:00
newsrc.sync
newsrc.flock(File::LOCK_UN) # what's the right order here?
2002-04-30 21:12:16 +00:00
newsrc.close
2008-02-12 15:19:13 +00:00
# }
2002-04-27 20:31:59 +00:00
end
2008-02-12 15:19:13 +00:00
# Here 'group' is a group structure. It'd probably be much more useful if
# it could just be a group_name_; which it can now.
2003-07-20 20:32:24 +00:00
def save_group(group)
unless @newsrc.has_key?("file")
@newsrc["file"] = "#{$ENV['HOME']}/.newsrc"
end
2008-02-12 15:19:13 +00:00
if group.class.to_s == "String"
groupname = group.dup
@newsrc["list"].each{|g|
if g["name"] == groupname
group = g.dup
break
end
}
end
save_group_as(@newsrc["file"], group)
2003-07-20 20:32:24 +00:00
end
2008-02-12 15:19:13 +00:00
# This should be thread safe, but may not be. It needs testing!
# If not, mutexes are needed.
2003-07-20 20:32:24 +00:00
def save_group_as(file, group)
2008-02-12 15:19:13 +00:00
# @@save_lock.synchronize{
2008-02-24 18:20:07 +00:00
#p Time.now
#p "copy file"
2003-07-20 20:32:24 +00:00
if FileTest.exists?("#{file}")
begin
FileUtils.copy(file, "#{file}.bak")
rescue
2008-02-12 15:19:13 +00:00
puts "Can't copy #{file} to #{file}.bak: #{$!}"
2003-07-20 20:32:24 +00:00
end
end
2008-02-24 18:20:07 +00:00
#p Time.now
#p "open & lock file"
2003-07-20 20:32:24 +00:00
begin
2011-05-20 14:03:33 +00:00
if FileTest.exists?("#{file}")
newsrc = File.new(file, "r+")
else
newsrc = File.new(file, "w")
end
newsrc.flock(File::LOCK_EX)
2003-07-20 20:32:24 +00:00
rescue
2011-05-20 14:03:33 +00:00
puts "Can't open #{file}: #{$!}"
2003-07-20 20:32:24 +00:00
exit
end
2008-02-24 18:20:07 +00:00
#p Time.now
#p "opened & locked"
2003-07-20 20:32:24 +00:00
# read file
lines = newsrc.readlines
# pointer -> 0
newsrc.rewind
2008-02-24 18:20:07 +00:00
group_saved = false
2003-07-20 20:32:24 +00:00
# write read stuff & replace group
2008-02-06 13:11:57 +00:00
lines.each{|line|
2008-02-12 15:19:13 +00:00
# same parsing as the parse method uses
unless line =~ /^([^!:]+)([!:])\s(.*)$/x
puts "Newsrc.parse: Bad newsrc line: #{line}"
# restore backup on failure, it'll contain the flaw too, but it'll
# be complete
begin
FileUtils.copy("#{file}.bak", file)
rescue
2008-02-12 15:19:13 +00:00
puts "Can't copy #{file}.bak to #{file}: #{$!}"
end
exit
end
linegroup = $1
if linegroup == group["name"]
2003-07-20 20:32:24 +00:00
newsrc.print format(group)
2008-02-24 18:20:07 +00:00
group_saved = true
2008-02-12 15:19:13 +00:00
else
newsrc.print line
2003-07-20 20:32:24 +00:00
end
2008-02-06 13:11:57 +00:00
}
2008-02-24 18:20:07 +00:00
if ! group_saved
newsrc.print format(group)
end
2003-07-20 20:32:24 +00:00
2008-02-24 18:20:07 +00:00
#p Time.now
#p "truncate, sync, unlock & close file"
2008-02-12 15:19:13 +00:00
# sometimes the file grows and then shrinks
# this is because a 'read' line van become shorter when more
# articles have been read (1,3,5 vs 1-5)
# when this happens the file needs to be truncated
pos = newsrc.pos
newsrc.truncate(pos)
newsrc.sync
2003-07-20 20:32:24 +00:00
newsrc.flock(File::LOCK_UN) # what's the right order here?
newsrc.close
2008-02-24 18:20:07 +00:00
#p Time.now
#p "garbage collect"
#p Time.now
2008-02-12 15:19:13 +00:00
GC.start
# }
2003-07-20 20:32:24 +00:00
end
2002-04-27 20:31:59 +00:00
def format(group)
name = group["name"]
sub = group["subscribed"] ? ':' : '!'
articles = group["articles"].run_list
2002-04-28 22:05:20 +00:00
return "#{name}#{sub} #{articles}\n"
2002-04-27 20:31:59 +00:00
end
def export_rc
lines = @newsrc["list"].collect{ |group|
name = group["name"]
sub = group["subscribed"] ? ':' : '!'
articles = group["articles"].run_list
space = articles ? ' ' : ''
"#{name}#{sub}#{space}#{articles}\n" }
return lines
end
def add_group(name, options)
if @newsrc["group"].has_key?(name)
options.has_key?("replace") or return false
del_group(name)
end
group = {"name" => name,
"subscribed" => true,
2002-04-27 20:31:59 +00:00
"articles" => Set::IntSpan.new }
@newsrc["group"][name] = group
_insert(group, options)
return true
end
def move_group(name, options)
if @newsrc["group"].has_key?(name)
group = @newsrc["group"][name]
else
return false
end
@newsrc["list"] = @newsrc["list"].delete_if{|x| x["name"] == name}
_insert(group, options)
return true
end
def _insert(group, options)
list = @newsrc["list"]
where = ""
arg = ""
if options.has_key?("where")
where = options["where"]
end
2004-06-16 08:17:48 +00:00
arg = where.slice!(1) if where.class.to_s == "Array"
2002-04-27 20:31:59 +00:00
case where.to_s
when "first"
@newsrc["list"].unshift(group)
when "last"
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
2002-04-27 20:31:59 +00:00
when ""
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group) # default
2002-04-27 20:31:59 +00:00
when "alpha"
alpha(group)
when "before"
before(group, arg)
when "after"
after(group, arg)
when "number"
number(group, arg)
end
end
def alpha (group)
name = group["name"]
2008-02-06 13:11:57 +00:00
(0...@newsrc["list"].length).each{|i|
2002-04-27 20:31:59 +00:00
if ((name <=> @newsrc["list"][i]["name"]) == -1)
upper = @newsrc["list"].slice!(i..@newsrc["list"].length)
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
@newsrc["list"].push(upper)
2002-04-27 20:31:59 +00:00
return;
end
2008-02-06 13:11:57 +00:00
}
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
2002-04-27 20:31:59 +00:00
end
def before(group, before)
name = group["name"]
2008-02-06 13:11:57 +00:00
(0...@newsrc["list"].length).each{|i|
2002-04-27 20:31:59 +00:00
if (@newsrc["list"][i]["name"] == before.to_s)
upper = @newsrc["list"].slice!(i..@newsrc["list"].length)
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
@newsrc["list"].push(upper)
2002-04-27 20:31:59 +00:00
return;
end
2008-02-06 13:11:57 +00:00
}
2002-04-27 20:31:59 +00:00
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
2002-04-27 20:31:59 +00:00
end
def after(group, after)
name = group["name"]
2008-02-06 13:11:57 +00:00
(0...@newsrc["list"].length).each{|i|
2002-04-27 20:31:59 +00:00
if (@newsrc["list"][i]["name"] == after.to_s)
upper = @newsrc["list"].slice!((i+1)..@newsrc["list"].length)
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
@newsrc["list"].push(upper)
2002-04-27 20:31:59 +00:00
return;
end
2008-02-06 13:11:57 +00:00
}
2002-04-27 20:31:59 +00:00
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
2002-04-27 20:31:59 +00:00
end
def number(group, offset)
offset = @newsrc["list"].length if offset[0] > @newsrc["list"].length
upper = @newsrc["list"].slice!(offset..@newsrc["list"].length)
2002-08-01 09:23:15 +00:00
@newsrc["list"].push(group)
@newsrc["list"].push(upper)
2002-04-27 20:31:59 +00:00
end
def del_group(name)
if @newsrc["group"].has_key?(name)
group = @newsrc["group"][name]
else
return false
end
@newsrc["group"].delete(name)
@newsrc["list"] = @newsrc["list"].delete_if{|x| x["name"] == name}
return true
end
def subscribe(name, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
@newsrc["group"][name]["subscribed"] = true
end
def unsubscribe(name, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
@newsrc["group"][name]["subscribed"] = false
end
def mark(name, article, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
2008-02-12 15:19:13 +00:00
@newsrc["group"][name]["articles"].insert!(article)
end
def mark_list(name, list, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
articles = @newsrc["group"][name]["articles"].union(list)
@newsrc["group"][name]["articles"] = articles
end
def mark_range(name, from, to, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
range = Set::IntSpan.new("#{from}-#{to}")
articles = @newsrc["group"][name]["articles"].union(range)
@newsrc["group"][name]["articles"] = articles
end
def unmark(name, article, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
@newsrc["group"][name]["articles"].remove(article)
end
def unmark_list(name, list, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
articles = @newsrc["group"][name]["articles"].diff(list)
@newsrc["group"][name]["articles"] = articles
end
def unmark_range(name, from, to, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
range = Set::IntSpan.new("#{from}-#{to}")
articles = @newsrc["group"][name]["articles"].diff(range)
@newsrc["group"][name]["articles"] = articles
end
def exists(name)
return @newsrc["group"].has_key?(name) ? true : false
end
def subscribed(name)
exists(name) and @newsrc["group"][name]["subscribed"]
end
def marked(name, article)
2005-05-09 11:53:18 +00:00
exists(name) and @newsrc["group"][name]["articles"].member?(article)
end
def num_groups
return @newsrc["list"].length
end
def groups
2002-04-28 15:04:19 +00:00
list = @newsrc["list"].dup
list.collect!{|x| x["name"]}
end
def sub_groups
2002-04-28 15:04:19 +00:00
list = @newsrc["list"].dup
list.collect!{|x| x["subscribed"] ? x["name"] : nil}.compact!
end
def unsub_groups
2002-04-28 15:04:19 +00:00
list = @newsrc["list"].dup
list.collect!{|x| x["subscribed"] ? nil : x["name"]}.compact!
end
def marked_articles(name, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
return @newsrc["group"][name]["articles"].elements
end
def unmarked_articles(name, from, to, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
range = Set::IntSpan.new("#{from}-#{to}")
2002-04-28 15:04:19 +00:00
return range.diff(@newsrc["group"][name]["articles"]).elements
end
def get_articles(name, options = {"where" => ""})
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
@newsrc["group"][name]["articles"].run_list
end
def set_articles(name, articles, options = {"where" => ""})
Set::IntSpan.valid(articles) or return false
set = Set::IntSpan.new(articles)
set.finite or return false
min = set.min
min != nil and min < 0 and return false
unless @newsrc["group"].has_key?(name)
add_group(name, options)
end
@newsrc["group"][name]["articles"] = set
return true
end
2002-04-27 20:31:59 +00:00
end # class
end # module
# TODO
# Do not kill an item until it's tested!
# [x] new
# [x] load
# [ ] _scan # Initializes a Newsrc object from a string. Used for testing.
# [x] import_rc
# [x] parse # parses a single line from a newsrc file
# [x] save
# [x] save_as
2003-07-20 20:32:24 +00:00
# [ ] save_group
# [ ] save_group_as
2002-04-27 20:31:59 +00:00
# [x] format
# [x] export_rc
# [ ] _dump # Formats a Newsrc object to a string. Used for testing
# [x] add_group
# [x] move_group
# [x] Splice(\@$$@) # heet nu number en is simpeler
# [x] _insert
# [x] Alpha
# [x] Before
# [x] After
# [x] del_group
# [x] subscribe
# [x] unsubscribe
# [x] mark
# [x] mark_list
# [x] mark_range
# [x] unmark
# [x] unmark_list
# [x] unmark_range
# [x] exists
# [x] subscribed
# [x] marked
# [x] num_groups
# [x] groups
# [x] sub_groups
# [x] unsub_groups
# [x] marked_articles
2002-04-28 15:04:19 +00:00
# [x] unmarked_articles
# [x] get_articles
# [x] set_articles