diff -crBN redmine_webdav/app/models/lock.rb redmine_webdav_locks/app/models/lock.rb *** redmine_webdav/app/models/lock.rb 1970-01-01 01:00:00.000000000 +0100 --- redmine_webdav_locks/app/models/lock.rb 2010-09-29 12:30:45.982164556 +0200 *************** *** 0 **** --- 1,8 ---- + class Lock < ActiveRecord::Base + unloadable + validates_presence_of :uid + validates_presence_of :timestamp + validates_presence_of :locktype + validates_presence_of :resource + + end diff -crBN redmine_webdav/db/migrate/004_create_locks.rb redmine_webdav_locks/db/migrate/004_create_locks.rb *** redmine_webdav/db/migrate/004_create_locks.rb 1970-01-01 01:00:00.000000000 +0100 --- redmine_webdav_locks/db/migrate/004_create_locks.rb 2010-09-29 12:40:41.592161185 +0200 *************** *** 0 **** --- 1,17 ---- + class CreateLocks < ActiveRecord::Migration + def self.up + create_table :locks do |t| + t.column :uid, :text + t.column :timestamp, :datetime + t.column :ipaddress, :string + t.column :locktype, :string + t.column :lockscope, :string + t.column :owner, :string + t.column :resource, :text + end + end + + def self.down + drop_table :locks + end + end diff -crBN redmine_webdav/lib/acts_as_webdav.rb redmine_webdav_locks/lib/acts_as_webdav.rb *** redmine_webdav/lib/acts_as_webdav.rb 2010-08-26 10:29:10.000000000 +0200 --- redmine_webdav_locks/lib/acts_as_webdav.rb 2010-09-30 10:52:41.292702370 +0200 *************** *** 34,39 **** --- 34,40 ---- require 'action_controller' require 'unicode' + require 'uuid' module Railsdav module Acts #:nodoc: *************** *** 151,158 **** render :nothing => true, :status => 200 end def webdav_lock - #TODO implementation for now return a 200 OK resource = find_resource_by_path(@path_info) raise NotFoundError unless resource --- 152,174 ---- render :nothing => true, :status => 200 end + def out_of_date + state=false + lock=Lock.find(:first, :conditions => ["resource = ?", request.url] ) + if not lock.nil? + current=Time.now - 36000 + logger.debug "DEBUG: lock timestamp: #{lock[:timestamp].inspect} and current: #{current}" + if lock[:timestamp] < current + logger.debug "DEBUG: Lock for #{request.url} out of date" + state=true + Lock.delete_all( :resource=> request.url ) + end + end + state + end + + def webdav_lock resource = find_resource_by_path(@path_info) raise NotFoundError unless resource *************** *** 165,194 **** owner = User.current.login end ! xml = "" ! xml << "" ! xml << "" ! xml << "" ! xml << "" ! xml << "" ! xml << "#{request.headers["Depth"]}" ! xml << "#{owner}" ! xml << "Second-345600" ! xml << "urn:uuid:e71d4fae-5dec-22df-fea5-00a0c93bd5eb1" ! xml << "#{request.url}" ! xml << "" ! xml << "" ! xml << "" ! ! response.headers["Lock-Token"] = "" ! render :text => xml, :status => 200, :layout => false, :content_type => "application/xml" ! #render :nothing => true, :status => 200 end def webdav_unlock - #TODO implementation for now return a 200 OK resource = find_resource_by_path(@path_info) raise NotFoundError unless resource render :nothing => true, :status => 200 end --- 181,221 ---- owner = User.current.login end ! if Lock.exists?( :resource=>request.url ) and not out_of_date ! render :status => 423, :nothing => true ! else ! newlock=UUID.create.guid ! mylock=Lock.create ( :uid=>newlock, :locktype=>locktype, :lockscope=>lockscope, :owner=>User.current.login, ! :resource => request.url, :timestamp=> Time.now ) ! ! xml = "" ! xml << "" ! xml << "" ! xml << "" ! xml << "" ! xml << "" ! xml << "#{request.headers["Depth"]}" ! xml << "#{owner}" ! xml << "Second-36000" ! xml << "urn:uuid:#{newlock}" ! xml << "#{request.url}" ! xml << "" ! xml << "" ! xml << "" ! ! logger.debug "DEBUG: lock create error: " + mylock.errors.full_messages.inspect ! response.headers["Lock-Token"] = "" ! render :text => xml, :status => 200, :layout => false, :content_type => "application/xml" ! end end + def webdav_unlock resource = find_resource_by_path(@path_info) raise NotFoundError unless resource + logger.debug "DEBUG: lock_tocken: " + request.headers["Lock-Token"].split(':')[2].chop.inspect + tocken=request.headers["Lock-Token"].split(':')[2].chop + Lock.delete_all( :uid=> tocken ) render :nothing => true, :status => 200 end *************** *** 254,272 **** end def webdav_delete ! check_write(@path_info) ! resource = find_resource_by_path(@path_info) ! if resource ! resource.delete! ! end render :nothing => true, :status => 204 end def webdav_put ! check_write(@path_info) ! write_content_to_path(@path_info, request.raw_post) ! render :nothing => true, :status => 201 end def with_source_and_destination_resources --- 281,307 ---- end def webdav_delete ! if Lock.exists?( :resource=>request.url ) ! raise ForbiddenError ! else ! check_write(@path_info) ! resource = find_resource_by_path(@path_info) ! if resource ! resource.delete! ! end + end render :nothing => true, :status => 204 end def webdav_put ! if not Lock.exists?( :resource=>request.url, :owner=>User.current.login ) ! raise ForbiddenError ! else ! check_write(@path_info) ! write_content_to_path(@path_info, request.raw_post) ! render :nothing => true, :status => 201 ! end end def with_source_and_destination_resources *************** *** 299,308 **** end def webdav_move ! with_source_and_destination_resources do |source_resource, dest_path| ! check_write(dest_path) ! move_to_path(source_resource, dest_path, @depth) ! end end def webdav_get --- 334,347 ---- end def webdav_move ! if Lock.exists?( :resource=>request.url ) ! raise ForbiddenError ! else ! with_source_and_destination_resources do |source_resource, dest_path| ! check_write(dest_path) ! move_to_path(source_resource, dest_path, @depth) ! end ! end end def webdav_get diff -crBN redmine_webdav/lib/uuid.rb redmine_webdav_locks/lib/uuid.rb *** redmine_webdav/lib/uuid.rb 1970-01-01 01:00:00.000000000 +0100 --- redmine_webdav_locks/lib/uuid.rb 2010-09-29 14:14:52.082237716 +0200 *************** *** 0 **** --- 1,345 ---- + #!/usr/bin/env ruby + ### http://mput.dip.jp/mput/uuid.txt + + # Copyright(c) 2005 URABE, Shyouhei. + # + # Permission is hereby granted, free of charge, to any person obtaining a copy + # of this code, to deal in the code without restriction, including without + # limitation the rights to use, copy, modify, merge, publish, distribute, + # sublicense, and/or sell copies of the code, and to permit persons to whom the + # code is furnished to do so, subject to the following conditions: + # + # The above copyright notice and this permission notice shall be + # included in all copies or substantial portions of the code. + # + # THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + # AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE CODE OR THE USE OR OTHER DEALINGS IN THE + # CODE. + + + %w[ + digest/md5 + digest/sha1 + socket + tmpdir + ].each do |f| + require f + end + + # Pure ruby UUID generator, which is compatible with RFC4122 + UUID = Struct.new "UUID", :raw_bytes + class UUID + private_class_method :new + + class << self + def mask str # :nodoc + v = str[7] + v = v & 0b00001111 + v = v | 0b01010000 + str[7] = v + r = str[8] + r = r & 0b00111111 + r = r | 0b10000000 + str[8] = r + str + end + private :mask + + # UUID generation using SHA1. Recommended over create_md5. + # Namespace object is another UUID, some of them are pre-defined below. + def create_sha1 str, namespace + sha1 = Digest::SHA1.new + sha1.update namespace.raw_bytes + sha1.update str + sum = sha1.digest + raw = mask sum[0..15] + ret = new raw + ret.raw_bytes.freeze + ret.freeze + ret + end + + # UUID generation using MD5 (for backward compat.) + def create_md5 str, namespace + md5 = Digest::MD5.new + md5.update namespace.raw_bytes + md5.update str + sum = md5.digest + raw = mask sum[0..16] + ret = new raw + ret.raw_bytes.freeze + ret.freeze + ret + end + + # UUID generation using random-number generator. From it's random + # nature, there's no warranty that the created ID is really universaly + # unique. + def create_random + rnd = [ + rand(0x100000000), + rand(0x100000000), + rand(0x100000000), + rand(0x100000000), + ].pack "N4" + raw = mask rnd + ret = new raw + ret.raw_bytes.freeze + ret.freeze + ret + end + + def read_state fp # :nodoc: + fp.rewind + Marshal.load fp.read + end + + def write_state fp, c, m # :nodoc: + fp.rewind + str = Marshal.dump [c, m] + fp.write str + end + + private :read_state, :write_state + STATE_FILE = 'ruby-uuid' + + # create the "version 1" UUID with current system clock, current UTC + # timestamp, and the IEEE 802 address (so-called MAC address). + # + # Speed notice: it's slow. It writes some data into hard drive on every + # invokation. If you want to speed this up, try remounting tmpdir with a + # memory based filesystem (such as tmpfs). STILL slow? then no way but + # rewrite it with c :) + def create clock=nil, time=nil, mac_addr=nil + c = t = m = nil + Dir.chdir Dir.tmpdir do + unless FileTest.exist? STATE_FILE then + # Generate a pseudo MAC address because we have no pure-ruby way + # to know the MAC address of the NIC this system uses. Note + # that cheating with pseudo arresses here is completely legal: + # see Section 4.5 of RFC4122 for details. + sha1 = Digest::SHA1.new + 256.times do + r = [rand(0x100000000)].pack "N" + sha1.update r + end + str = sha1.digest + r = rand 34 # 40-6 + node = str[r, 6] || str + node[0] |= 0x01 # multicast bit + k = rand 0x40000 + open STATE_FILE, 'w' do |fp| + fp.flock IO::LOCK_EX + write_state fp, k, node + fp.chmod 0o777 # must be world writable + end + end + open STATE_FILE, 'r+' do |fp| + fp.flock IO::LOCK_EX + c, m = read_state fp + c = clock % 0x4000 if clock + m = mac_addr if mac_addr + t = time + if t.nil? then + # UUID epoch is 1582/Oct/15 + tt = Time.now + t = tt.to_i*10000000 + tt.tv_usec*10 + 0x01B21DD213814000 + end + c = c.succ # important; increment here + write_state fp, c, m + end + end + + tl = t & 0xFFFF_FFFF + tm = t >> 32 + tm = tm & 0xFFFF + th = t >> 48 + th = th & 0x0FFF + th = th | 0x1000 + cl = c & 0xFF + ch = c & 0x3F00 + ch = ch >> 8 + ch = ch | 0x80 + pack tl, tm, th, cl, ch, m + end + + def string *a, &b + create(*a, &b).to_s + end + + # A simple GUID parser: just ignores unknown characters and convert + # hexadecimal dump into 16-octet object. + def parse obj + str = obj.to_s.sub %r/\Aurn:uuid:/, '' + str.gsub! %r/[^0-9A-Fa-f]/, '' + raw = str[0..31].to_a.pack 'H*' + ret = new raw + ret.raw_bytes.freeze + ret.freeze + ret + end + + # The 'primitive constructor' of this class + # Note UUID.pack(uuid.unpack) == uuid + def pack tl, tm, th, ch, cl, n + raw = [tl, tm, th, ch, cl, n].pack "NnnCCa6" + ret = new raw + ret.raw_bytes.freeze + ret.freeze + ret + end + end + + # The 'primitive deconstructor', or the dual to pack. + # Note UUID.pack(uuid.unpack) == uuid + def unpack + raw_bytes.unpack "NnnCCa6" + end + + # Generate the string representation (a.k.a GUID) of this UUID + def to_s + a = unpack + tmp = a[-1].unpack 'C*' + a[-1] = sprintf '%02x%02x%02x%02x%02x%02x', *tmp + "%08x-%04x-%04x-%02x%02x-%s" % a + end + alias guid to_s + + # Convert into a RFC4122-comforming URN representation + def to_uri + "urn:uuid:" + self.to_s + end + alias urn to_uri + + # Convert into 128-bit unsigned integer + # Typically a Bignum instance, but can be a Fixnum. + def to_int + tmp = self.raw_bytes.unpack "C*" + tmp.inject do |r, i| + r * 256 | i + end + end + alias to_i to_int + + # Two UUIDs are said to be equal if and only if their (byte-order + # canonicalized) integer representations are equivallent. Refer RFC4122 for + # details. + def == other + to_i == other.to_i + end + + include Comparable + # UUIDs are comparable (don't know what benefits are there, though). + def <=> other + to_s <=> other.to_s + end + + # Pre-defined UUID Namespaces described in RFC4122 Appendix C. + NameSpace_DNS = parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + NameSpace_URL = parse "6ba7b811-9dad-11d1-80b4-00c04fd430c8" + NameSpace_OID = parse "6ba7b812-9dad-11d1-80b4-00c04fd430c8" + NameSpace_X500 = parse "6ba7b814-9dad-11d1-80b4-00c04fd430c8" + + # The Nil UUID in RFC4122 Section 4.1.7 + Nil = parse "00000000-0000-0000-0000-000000000000" + end + + __END__ + if __FILE__ == $0 then + require 'test/unit' + + class TC_UUID < Test::Unit::TestCase + def test_v1 + u1 = UUID.create + u2 = UUID.create + assert_not_equal u1, u2 + end + + def test_v1_repeatability + u1 = UUID.create 1, 2, "345678" + u2 = UUID.create 1, 2, "345678" + assert_equal u1, u2 + end + + def test_v3 + u1 = UUID.create_md5 "foo", UUID::NameSpace_DNS + u2 = UUID.create_md5 "foo", UUID::NameSpace_DNS + u3 = UUID.create_md5 "foo", UUID::NameSpace_URL + assert_equal u1, u2 + assert_not_equal u1, u3 + end + + def test_v5 + u1 = UUID.create_sha1 "foo", UUID::NameSpace_DNS + u2 = UUID.create_sha1 "foo", UUID::NameSpace_DNS + u3 = UUID.create_sha1 "foo", UUID::NameSpace_URL + assert_equal u1, u2 + assert_not_equal u1, u3 + end + + def test_v4 + # This test is not perfect, because the random nature of version 4 + # UUID it is not always true that the three objects below really + # differ. But in real life it's enough to say we're OK when this + # passes. + u1 = UUID.create_random + u2 = UUID.create_random + u3 = UUID.create_random + assert_not_equal u1.raw_bytes, u2.raw_bytes + assert_not_equal u1.raw_bytes, u3.raw_bytes + assert_not_equal u2.raw_bytes, u3.raw_bytes + end + + def test_pack + u1 = UUID.pack 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, + "\000\300O\3240\310" + assert_equal UUID::NameSpace_DNS, u1 + end + + def test_unpack + tl, tm, th, cl, ch, m = UUID::NameSpace_DNS.unpack + assert_equal 0x6ba7b810, tl + assert_equal 0x9dad, tm + assert_equal 0x11d1, th + assert_equal 0x80, cl + assert_equal 0xb4, ch + assert_equal "\000\300O\3240\310", m + end + + def test_parse + u1 = UUID.pack 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, + "\000\300O\3240\310" + u2 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + u3 = UUID.parse "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" + assert_equal u1, u2 + assert_equal u1, u3 + end + + def test_to_s + u1 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + assert_equal "6ba7b810-9dad-11d1-80b4-00c04fd430c8", u1.to_s + end + + def test_to_i + u1 = UUID.parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + assert_equal 0x6ba7b8109dad11d180b400c04fd430c8, u1.to_i + end + end + end + + + # Local Variables: + # mode: ruby + # code: utf-8 + # indent-tabs-mode: t + # tab-width: 3 + # ruby-indent-level: 3 + # fill-column: 79 + # default-justification: full + # End: + # vi: ts=3 sw=3 + diff -crBN redmine_webdav/test/fixtures/locks.yml redmine_webdav_locks/test/fixtures/locks.yml *** redmine_webdav/test/fixtures/locks.yml 1970-01-01 01:00:00.000000000 +0100 --- redmine_webdav_locks/test/fixtures/locks.yml 2010-09-29 09:08:15.372172436 +0200 *************** *** 0 **** --- 1,17 ---- + # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + one: + id: 1 + uid: MyText + timestamp: 2010-09-29 09:08:15 + ipaddress: MyString + locktype: MyString + user: MyString + resource: MyText + two: + id: 2 + uid: MyText + timestamp: 2010-09-29 09:08:15 + ipaddress: MyString + locktype: MyString + user: MyString + resource: MyText diff -crBN redmine_webdav/test/unit/lock_test.rb redmine_webdav_locks/test/unit/lock_test.rb *** redmine_webdav/test/unit/lock_test.rb 1970-01-01 01:00:00.000000000 +0100 --- redmine_webdav_locks/test/unit/lock_test.rb 2010-09-29 09:08:15.372172436 +0200 *************** *** 0 **** --- 1,10 ---- + require File.dirname(__FILE__) + '/../test_helper' + + class LockTest < ActiveSupport::TestCase + fixtures :locks + + # Replace this with your real tests. + def test_truth + assert true + end + end