打印

[其它主题] 发段代码,关于SVN的管理操作的

发段代码,关于SVN的管理操作的

介绍一下背景,因为是从事.Net开发的,但是业余情况下又喜欢php?name=Ruby" onclick="tagshow(event)" class="t_tag">Ruby,所以在空闲的时候做了个这样的东西。
Ruby生了一段时间了,好多方法都是在IRB里用puts a.methods.sort这样一个一个看才知道的,呵呵

这段代码的主要作用是操作SVN服务端的ini文件来达到管理svn responstries的目的,支持几个指令:

svn_project_list, svn_group_list, svn_memeber_list
svn_project_get, svn_group_get, svn_member_get
svn_project_create, svn_group_create, svn_member_create
svn_project_update, svn_group_update, svn_member_update
svn_project_delete, svn_group_delete, svn_member_delete
exit


服务端使用scoket server,在socket之间使用json序列化来传输对象,这是因为YAML在其它语言中支持不太好,例如C#.
但是Json就不一样了,有内置的支持。

冒昧的把代码贴出来,注释在代码里有一些,写得不好,请大家指正。

Model.rb

# this file is a model file which includes several classes. SVNOperator: Module, 
# Ini: Class, Base: Class, Project: Class, Directory: Class, Group: Class
# Member: Class
#
# this file is used as converting strings in 'ini' file to objects and provides 
# a way to find, save and delete easily.
# basic operations: find(:all), find(name), instance.save, instance.delete
# this file needs 3 gems: ini, json and activesupport. run commands as following: gem install json -v 1.1.1
#
# author: ops
# email: [email]ops@ruby-lang.org.cn[/email]
# 2008-3-15

require 'ini'
require 'activesupport'

module SVNOperator
	# this need to be called before calling other functions
	# parameters: :authz and :passwd
	# for now, only clear password format be supported.
	def init opts =  {}
		@authz = opts[:authz] || 'authz.conf'
		@passwd = opts[:passwd] || 'passwd.conf'
		
		@i_authz = Ini.new @authz, :comment => '#'
		@i_passwd = Ini.new @passwd, :comment => '#'
	end
	
	public
	def authz
		@i_authz
	end
	
	def passwd
		@i_passwd
	end
end

class Ini
	# this method provides a way to modify the integrated Hash easily.
	def []=(sec,hash)
		@ini[sec] = hash
	end
end

# this is a base class to provide common methods such as name 
# accessor, json serialization etc..
class Base
	extend SVNOperator
	attr_accessor :name
	
	def initialize name
		@name = name
	end
	
	# this is an abstract method, needs to be implemented.
	def delete
		raise 'Not Implemented'
	end
	
	# since the json string generated from C# doesn't contains 'json_class' property,
	# so we provide this method so that we know which class we need to instance.
	def self.parse json_str
		json_create JSON.parse(json_str)
	end
	
	# this method notices JSON module how to recover the object from JSON string.
	def self.json_create(object)
		obj = new
		for key, value in object
			next if key == 'json_class'
			# the property name are camelized from C#.
			obj.instance_variable_set "@#{key.underscore}", value
		end
		
		obj
	end	
	
	def to_json(*a)
		result = {
			'json_class' => self.class.name
		}
		instance_variables.inject(result) do |r, name|
			# we have to camelize the variable names so that C# can deserilize it.
			r[name[1..-1].camelize] = instance_variable_get(name)
			r
		end
		result.to_json(*a)
	end		
end

# this is a class represents a project.
class Project < Base
	attr_accessor :directories
	
	#if init_parameters equals true, then the code will find 
	# the sub directories from the ini file automatically.
	def initialize name = nil, init_parameters = false
		super name
		self.directories ||= {}
		
		if init_parameters
			Base.authz.each_section do |sec|
				self.directories.merge!({sec, Directory.find(sec)}) if sec =~ /^#{self.name}\:\//i
			end
		end  
	end
	
	public
	
	# the find method provide a way to find a project easily.
	# :all parameter returns all projects.
	# a string or symbol name returns the specify project.
	# it always returns an instance with the specify name.
	def self.find opts
		case opts
		when :all
			Base.authz.sections.collect do |x| 
				Project.find $1 if x =~ /^(\w+)\:\/$/i
			end - [nil]
		when String, Symbol
			Project.new opts.to_s, true
		end
	end
	
	# save the project to ini file.
	# this method has 2 functionalities. creating and updating.
	def save		
		self.directories.each do |k, dir|
			Base.authz[k] = dir.permissions
		end
		
		Base.authz.save and 'success'
	end
	
	# this provides a simple way to access the sub directories.
	def [](name)
		name = name.to_s
		name = "#{self.name}:#{name}" if(name !~ /^#{self.name}/)
		return directories[name]
	end
	
	# project can not be deleted for now.
	def delete
		raise 'Project cannot be removed.'
	end
	
	# since project has directories objects property, so we have to 
	# override the json_create method to deserilize the directories
	# property.
	def self.json_create(object)
		obj = new
		for key, value in object
			next if key == 'json_class'
			val = case value
			when Hash # there is only one property is Hash.
				value.each do |k, v|
					value[k] = Directory.parse(v.to_json)
				end
			else
				value
			end
			obj.instance_variable_set "@#{key.underscore}", val
		end
		
		obj
	end		
end

# this is a class represents a folder in ini file.
class Directory < Base
	attr_accessor :owner, :permissions
	
	#if init_parameters equals true, then the code will fill 
	# the permissions from the ini file automatically.
	def initialize name = nil, init_parameters = false
		super name
		self.permissions ||= {}
		
		if init_parameters
			Base.authz[self.name].each do |k, v|
				self.permissions.merge!({k, v})
			end
		end
	end
	
	# this method only supports find_by_name, :all isn't supported.
	def self.find opts
		case opts
		when String,Symbol
			Directory.new opts.to_s, true
		end
	end
end

# this class represents a group.
class Group < Base
	attr_accessor :members
	
	#if init_parameters equals true, then the code will fill 
	# the members from the ini file automatically.
	def initialize name=nil, init_members = false
		super name
		self.members ||= []
		
		self.members += (Base.authz['groups'][self.name] || '').split(',').map{|x| x.strip} if init_members
	end
	
	# the find method provides a way to find a group easily.
	# :all parameter returns all groups.
	# a string or symbol name returns the specify group.
	# it always returns an instance with the specify name.
	def self.find opts
		case opts
		when :all
			Base.authz['groups'].keys.map do |k|
				Group.find k
			end
		when String, Symbol
			Group.new opts.to_s, true
		else
			raise ArgumentError
		end
	end  
	
	# save current instance to ini file.
	def save
		Base.authz['groups'][self.name] = members.join(', ')
		Base.authz.save and 'success'
	end
	
	# delete current group and related data from ini file.
	def delete
		# group section
		Base.authz['groups'].delete(self.name)
		
		# project directory sections.
		Base.authz.each_section do |sec|
			next if sec == 'groups'
			Base.authz[sec].each do |k, v|
				Base.authz[sec].delete(k) if k == '@' + self.name	
			end
		end
		
		# save changes to ini file.
		Base.authz.save and 'sucess'
	end
end

# this class represents a member in ini file.
class Member < Base
	attr_accessor :password
	
	def initialize opt = {}
		super opt[:name]
		self.password = opt[:password]
	end  
	
	# find method, by :all or by name(string, symbol).
	def self.find opt
		case opt
		when :all
			Base.passwd['users'].keys.map do |k|
				Member.find k
			end	
		when String, Symbol
			Member.new :name => opt.to_s, :password => Base.passwd["users"][opt.to_s]
		end
	end
	
	# save changes to ini file.
	def save
		Base.passwd['users'][self.name] = self.password
		Base.passwd.save and 'success'
	end
	
	# remove current user from ini file.
	# not only from the passwd also from the authz.
	def delete
		# remove from passwd.
		Base.passwd['users'].delete(self.name)
		
		# remove from the group section of authz.
		Base.authz['groups'].each do |k, v|
			# since the format is 'g_group = ops, abc, xyz', so the first step
			# is split it to Array to strip every memeber, then reject the one which
			# has the same name to self.name, then rejoin the Array by ',' and a space.
			Base.authz['groups'][k] = v.split(',').map{|x| x.strip}.reject{|item| item == self.name}.join(', ')
		end
		
		# remove from other project directories.
		Base.authz.each_section do |sec|
			next if sec == 'groups'
			Base.authz[sec].each do |k, v|
				Base.authz[sec].delete(k) if k == self.name	
			end
		end
		
		# save to ini file.
		Base.passwd.save
		Base.authz.save and nil
	end	
end

# this class is used to send to client to communicate.
class Message
	attr_accessor :message_type, :text, :refresh_required
	
	# message_type: normal or error.
	# refresh_required means if we do something to ini file and needs to refresh the data.
	# text: an array, a base64 string from JSON string.
	def initialize opts = {}
		self.message_type = opts[:message_type] || :normal
		self.refresh_required = opts[:refresh_required] || false
		self.text = opts[:text] || []
	end
	
	# since we need all messages into one line, so we use base64 encoding and replace all '\r\n' to none.
	def to_s
		"#{self.message_type.to_s} #{self.refresh_required.to_s} #{Base64.encode64(self.text.to_json).gsub(/\n/,'')}\n"
	end
end

if __FILE__ == $0
	Base.init :authz => 'conf/authz.conf', :passwd => 'conf/passwd.conf'
	Project.find "Project1"
	Member.find :ops
end


TOP

SVN 服务端:

svn_server.rb

# this file is a socket server file. default listen on port 1680.
# it supports some commands such as: svn_(create|update|list|get|delete)_(project|group|member) parameters.
#
# author: ops
# email: [email]ops@ruby-lang.org.cn[/email]
# 2008-3-15

require "socket"
require 'base64'
require 'model'
require 'json/add/rails'

# SVN server class, only contains one static method: start.
class SVNServer
	def self.start port = 1680
		gs = TCPServer.open port
		printf("server is on %s\n", port)
		while true
			Thread.start(gs.accept) do |s| # save to dynamic variable
				print(s, " is accepted\n")
				while s.gets
					break if (yield s, $_.chomp) # exit while when it gets exit comamnd
				end
				print(s, " is gone\n")
				s.close and break
			end
		end	
	end
end

# SVN Helper class handles commands.
class SVNHelper
	def initialize authz, passwd
		Base.init :authz => authz, :passwd => passwd
	end
	
	def process_cmd cmd
		puts cmd
		begin
			case cmd
			when /^svn_(.+)_(project|group|member)(.*)/i
				act, name, paras = $1, $2, Base64.decode64($3)
				case act
				when /list/i
					Message.new :text => eval(name).find(:all)	
				when /get/i
					Message.new :text => [eval(name).find(paras)]
				when /update|create/i
					Message.new :text => [eval(name).parse(paras).save]
				when /delete/i
					Message.new :text => [eval(name).find(paras).delete]
				end
			when /exit/i
				Message.new :message_type => :exit, :text => ['good bye']
			else
				Message.new :text => ["unknown command"]
			end
		rescue => exc
			Message.new :text => [exc.message], :message_type => :error
		end
	end
end

if __FILE__ == $0
	helper = SVNHelper.new 'conf/authz.conf', 'conf/passwd.conf'
	
	SVNServer.start do |ns, cmd|
		msg = helper.process_cmd cmd
		ns << msg
		msg.message_type == :exit		
	end
end


本帖最近评分记录
  • drive2me R币 +10 谢谢分享!多多益善! 2008-3-27 11:44

TOP

还有些功能需要完善,例如在创建Project的时候调用system命令来创建responsitry,最好还能创建标准的目录。

最好还能获取到svn的目录列表,这些都没有空去完成。

用C#写了个客户端,发上来给大家年看。
附件: 您所在的用户组无法下载或查看附件
本帖最近评分记录
  • mewleo R币 +5 原创内容 2008-3-27 10:43

TOP

感谢分享!

粘下来慢慢看
[qq]205135[/qq]

TOP

能贴一下svn.ini 吗,对 win server 上的 svnadmin 不太熟悉

TOP

yudi,你回来了,哈哈。
我们没有把你丢了。

欢迎回家!
Flying Piggy...! 
天地人合一!

TOP

呵呵,我从来没离开过,一直瞎忙,不好意思

TOP

引用:
原帖由 yudi 于 2008-3-27 11:43 发表
呵呵,我从来没离开过,一直瞎忙,不好意思
哦,哈哈。大家都很忙呢,理解。
Flying Piggy...! 
天地人合一!

TOP

引用:
原帖由 yudi 于 2008-3-27 11:34 发表
能贴一下svn.ini 吗,对 win server 上的 svnadmin 不太熟悉
格式就是普通的ini格式,以#为注释符号。

passwd.conf

### This file is an example password file for svnserve.
### Its format is similar to that of svnserve.conf. As shown in the
### example below it contains one section labelled [users].
### The name and password for each user follow, one account per line.


[users]
boss = test
PM1 = abc
PM2 = abc123
ops = ops


authz.conf

[groups]
g_Manager = boss, PM1, PM2
g_Project1 = PM1, ops

[Project1:/]
@g_Manager = rw
@g_Project1 = r
* = 

[Project1:/src/trunk]
@g_Manager = rw
@g_Project1 = rw
* = 

[Project1:/src/tags]
@g_Manager = rw
@g_Project1 = r
* = 

[Project1:/doc/trunk]
@g_Manager = rw
@g_Project1 = rw
* = 

[Project1:/doc/tags]
@g_Manager = rw
@g_Project1 = r
* = 

[Project1:/src/branches/ops]
@g_Manager = rw
@g_Project1 = r
ops = rw
* = 


TOP

2008-11-23 23:30 Crawled by CCBot/1.0 (+http://www.commoncrawl.org/bot.html) @38.103.63.61