sam / do fork watch download tarball
public
Rubygem
Description: DataObjects
Homepage: http://rubyforge.org/projects/dorb
Clone URL: git://github.com/sam/do.git
Standard Connection API passes specs.
sam (author)
Tue Feb 12 11:45:13 -0800 2008
commit  1c4c931b2b1914617cb87f83cd4aeef00849c3bf
tree    31fe470ea760e580a3f354ee1f1f31e478c10da8
parent  c9b4bd75386ba82e6316f5c3e2b2a84aef6417db
...
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
 
 
 
 
13
14
15
16
17
18
19
20
 
 
 
21
22
23
...
29
30
31
32
 
33
34
35
...
41
42
43
44
 
45
46
47
...
50
51
52
53
54
55
56
57
58
 
 
 
 
 
 
59
60
61
62
 
 
 
63
64
 
65
66
67
68
69
70
71
 
72
73
74
75
 
 
 
76
77
 
78
79
80
81
82
83
 
84
85
 
86
87
88
...
1
2
3
4
5
6
7
 
 
 
 
 
8
 
 
 
 
9
10
11
12
13
14
 
 
 
 
 
 
15
16
17
18
19
20
...
26
27
28
 
29
30
31
32
...
38
39
40
 
41
42
43
44
...
47
48
49
 
 
 
 
 
 
50
51
52
53
54
55
56
 
 
 
57
58
59
60
 
61
62
 
 
 
 
 
 
63
64
 
 
 
65
66
67
68
 
69
70
 
 
 
 
 
71
72
 
73
74
75
76
0
@@ -1,23 +1,20 @@
0
+require 'uri'
0
+require 'set'
0
+require 'fastthread'
0
+require 'logger'
0
+
0
 module DataObjects
0
   class Connection
0
-
0
- STATE_OPEN = 0
0
- STATE_CLOSED = 1
0
-
0
- attr_reader :timeout, :database, :datasource, :server_version, :state
0
     
0
- attr_reader :db, :connection_string
0
-
0
- def self.new(uri)
0
- aquire(uri)
0
+ def self.inherited(base)
0
+ base.instance_variable_set('@connection_lock', Mutex.new)
0
+ base.instance_variable_set('@available_connections', Hash.new { |h,k| h[k] = [] })
0
+ base.instance_variable_set('@reserved_connections', Set.new)
0
     end
0
     
0
- @connection_lock = Mutex.new
0
- @available_connections = Hash.new { |h,k| h[k] = [] }
0
- @reserved_connections = Set.new
0
-
0
- def self.connection_lock
0
- @mutex
0
+ def self.new(uri)
0
+ uri = uri.is_a?(String) ? URI::parse(uri) : uri
0
+ DataObjects.const_get(uri.scheme.capitalize)::Connection.aquire(uri.to_s)
0
     end
0
     
0
     def self.aquire(connection_string)
0
@@ -29,7 +26,7 @@ module DataObjects
0
         else
0
           conn = allocate
0
           conn.send(:initialize, connection_string)
0
- at_exit { conn.close_socket }
0
+ at_exit { conn.real_close }
0
         end
0
         
0
         @reserved_connections << conn
0
@@ -41,7 +38,7 @@ module DataObjects
0
     def self.release(connection)
0
       @connection_lock.synchronize do
0
         if @reserved_connections.delete?(connection)
0
- @available_connections[connection.connection_string] << connection
0
+ @available_connections[connection.to_s] << connection
0
         end
0
       end
0
       return nil
0
@@ -50,38 +47,29 @@ module DataObjects
0
     def close
0
       self.class.release(self)
0
     end
0
-
0
- def initialize(connection_string)
0
- end
0
-
0
- def logger
0
- @logger || @logger = Logger.new(nil)
0
+
0
+ #####################################################
0
+ # Standard API Definition
0
+ #####################################################
0
+ def to_s
0
+ @uri
0
     end
0
-
0
- def logger=(value)
0
- @logger = value
0
+
0
+ def initialize(uri)
0
+ raise NotImplementedError.new
0
     end
0
-
0
+
0
     def begin_transaction
0
- # TODO: Hook this up
0
- Transaction.new
0
- end
0
-
0
- def change_database(database_name)
0
- raise NotImplementedError
0
+ raise NotImplementedError.new
0
     end
0
-
0
- def close
0
- raise NotImplementedError
0
+
0
+ def real_close
0
+ raise NotImplementedError.new
0
     end
0
-
0
+
0
     def create_command(text)
0
- Command.new(self, text)
0
- end
0
-
0
- def closed?
0
- @state == STATE_CLOSED
0
+ raise NotImplementedError.new
0
     end
0
-
0
+
0
   end
0
 end
0
\ No newline at end of file
...
1
2
3
4
5
6
7
8
...
 
 
 
 
 
1
2
3
0
@@ -1,8 +1,3 @@
0
-require 'set'
0
-require 'fastthread'
0
-require 'date'
0
-require 'logger'
0
-
0
 require File.dirname(__FILE__) + "/connection"
0
 require File.dirname(__FILE__) + "/transaction"
0
 require File.dirname(__FILE__) + "/command"
...
2
3
4
5
 
 
 
 
6
7
 
8
 
9
 
 
 
 
10
11
12
13
14
15
16
17
18
19
20
21
 
22
23
24
25
...
2
3
4
 
5
6
7
8
9
 
10
11
12
13
14
15
16
17
18
 
 
 
 
 
 
 
 
 
 
 
19
20
21
22
23
0
@@ -2,23 +2,21 @@ require File.dirname(__FILE__) + '/spec_helper'
0
 
0
 describe DataObjects::Connection do
0
   
0
- it "should return a new connection and add it to the available connections pool when released" do
0
+ it "should define a standard API" do
0
+ DataObjects::Connection.should respond_to(:new)
0
+ DataObjects::Connection.should respond_to(:aquire)
0
+ DataObjects::Connection.should respond_to(:release)
0
     
0
- connection = DataObjects::Connection.new('sqlite3://do_rb.db')
0
+ connection = DataObjects::Connection.new('mock://localhost')
0
     
0
+ connection.should respond_to(:close)
0
     
0
+ connection.should respond_to(:to_s)
0
+ connection.should respond_to(:begin_transaction)
0
+ connection.should respond_to(:real_close)
0
+ connection.should respond_to(:create_command)
0
     
0
- end
0
-
0
- it "should be able to be opened" do
0
- @c.should be_is_a($adapter_module::Connection)
0
- @c.state.should == 0
0
- end
0
-
0
- it "should be able to create a related command" do
0
- @c.open
0
- cmd = @c.create_command("select * from table1")
0
- cmd.connection.should == @c
0
+ connection.close
0
   end
0
   
0
 end
0
\ No newline at end of file
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
...
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
0
@@ -1,111 +1,122 @@
0
 require File.dirname(__FILE__) + '/spec_helper'
0
-
0
-describe "DO::Command" do
0
- it_should_behave_like "Connectable"
0
-
0
- def delete_3
0
- cmd = @c.create_command("DELETE from table1 where id=3")
0
- cmd.execute_non_query.should == 1
0
- end
0
-
0
- it "should be able to be executed if it's a select" do
0
- cmd = @c.create_command("select * from table1")
0
- r = cmd.execute_reader
0
- r.has_rows.should be_true
0
- r.close
0
- end
0
-
0
- it "should be able to be executed if it's not a select" do
0
- cmd = @c.create_command("INSERT into table1(#{$escaped_columns}) VALUES(3, NULL, NULL, NULL, NULL)")
0
- cmd.execute_non_query.should == 1
0
- delete_3
0
- end
0
-
0
- it "should throw an error if a select is passed to execute_non_query" do
0
- cmd = @c.create_command("SELECT * from table1")
0
- lambda { cmd.execute_non_query }.should raise_error(DataObject::QueryError)
0
- end
0
-
0
- it "should immediately close the reader and populate records_affected if a modification is passed to execute_reader" do
0
- if $adapter_module.to_s == "DataObject::Postgres"
0
- cmd = @c.create_command("INSERT into table1(#{$escaped_columns}) VALUES(3, NULL, now(), false, now())")
0
- else
0
- cmd = @c.create_command("INSERT into table1(#{$escaped_columns}) VALUES(3, NULL, CURRENT_TIME, 0, CURRENT_DATE)")
0
- end
0
- r = cmd.execute_reader
0
- r.records_affected.should == 1
0
- lambda { r.name(0) }.should raise_error(DataObject::ReaderClosed)
0
- delete_3
0
- end
0
-
0
-end
0
-
0
-describe "DO::Reader" do
0
-
0
- before :each do
0
- @c = $adapter_module::Connection.new($connection_string)
0
- @c.open
0
- cmd = @c.create_command("select * from table1")
0
- @r = cmd.execute_reader
0
- end
0
-
0
- after :each do
0
- @c.close
0
- end
0
-
0
- it "should be able to get the field count" do
0
- @r.field_count.should == 5
0
- end
0
-
0
- it "should be able to get field names" do
0
- @r.name(0).should == "id"
0
- @r.name(1).should == "int"
0
- @r.name(2).should == "time"
0
- @r.name(3).should == "bool"
0
- @r.name(4).should == "date"
0
- @r.name(5).should == nil
0
- end
0
-
0
- it "should be able to get field indexes" do
0
- @r.get_index("id").should == 0
0
- @r.get_index("int").should == 1
0
- @r.get_index("time").should == 2
0
- @r.get_index("bool").should == 3
0
- @r.get_index("date").should == 4
0
- @r.get_index("foo").should == nil
0
- end
0
-
0
- it "should be able to determine whether a particular field is null" do
0
- @r.null?(0).should == false
0
- @r.null?(1).should == true
0
- end
0
-
0
- it "should be able to get a typecasted version of a particular field" do
0
- case $adapter_module.to_s
0
- when "DataObject::Sqlite3"
0
- @r.item(0).should == 1
0
- @r.item(1).should == nil
0
- @r.item(2).class.should == String
0
- @r.item(3).should == 0
0
- @r.item(4).class.should == String
0
- when "DataObject::Mysql"
0
- @r.item(0).should == 1
0
- @r.item(1).should == nil
0
- @r.item(2).class.should == DateTime
0
- @r.item(3).should == false
0
- @r.item(4).class.should == Date
0
- end
0
- end
0
-
0
- it "should be able to get to the next row" do
0
- @r.next.should == true
0
- @r.item(0).should == 2
0
- end
0
-
0
- it "should return nil and close the reader when the cursor reaches the end" do
0
- @r.next
0
- @r.next.should == nil
0
- lambda { @r.name(0) }.should raise_error(DataObject::ReaderClosed)
0
- end
0
-
0
-end
0
\ No newline at end of file
0
+#
0
+# describe "DO::Command" do
0
+#
0
+#
0
+# def delete_3
0
+# cmd = @c.create_command("DELETE from table1 where id=3")
0
+# cmd.execute_non_query.should == 1
0
+# end
0
+#
0
+# it "should be able to be executed if it's a select" do
0
+# pending("Obsolete")
0
+# cmd = @c.create_command("select * from table1")
0
+# r = cmd.execute_reader
0
+# r.has_rows.should be_true
0
+# r.close
0
+# end
0
+#
0
+# it "should be able to be executed if it's not a select" do
0
+# pending("Obsolete")
0
+# cmd = @c.create_command("INSERT into table1(#{$escaped_columns}) VALUES(3, NULL, NULL, NULL, NULL)")
0
+# cmd.execute_non_query.should == 1
0
+# delete_3
0
+# end
0
+#
0
+# it "should throw an error if a select is passed to execute_non_query" do
0
+# pending("Obsolete")
0
+# cmd = @c.create_command("SELECT * from table1")
0
+# lambda { cmd.execute_non_query }.should raise_error(DataObject::QueryError)
0
+# end
0
+#
0
+# it "should immediately close the reader and populate records_affected if a modification is passed to execute_reader" do
0
+# pending("Obsolete")
0
+# if $adapter_module.to_s == "DataObject::Postgres"
0
+# cmd = @c.create_command("INSERT into table1(#{$escaped_columns}) VALUES(3, NULL, now(), false, now())")
0
+# else
0
+# cmd = @c.create_command("INSERT into table1(#{$escaped_columns}) VALUES(3, NULL, CURRENT_TIME, 0, CURRENT_DATE)")
0
+# end
0
+# r = cmd.execute_reader
0
+# r.records_affected.should == 1
0
+# lambda { r.name(0) }.should raise_error(DataObject::ReaderClosed)
0
+# delete_3
0
+# end
0
+#
0
+# end
0
+#
0
+# describe "DO::Reader" do
0
+#
0
+# before :each do
0
+# @c = $adapter_module::Connection.new($connection_string)
0
+# @c.open
0
+# cmd = @c.create_command("select * from table1")
0
+# @r = cmd.execute_reader
0
+# end
0
+#
0
+# after :each do
0
+# @c.close
0
+# end
0
+#
0
+# it "should be able to get the field count" do
0
+# pending("Obsolete")
0
+# @r.field_count.should == 5
0
+# end
0
+#
0
+# it "should be able to get field names" do
0
+# pending("Obsolete")
0
+# @r.name(0).should == "id"
0
+# @r.name(1).should == "int"
0
+# @r.name(2).should == "time"
0
+# @r.name(3).should == "bool"
0
+# @r.name(4).should == "date"
0
+# @r.name(5).should == nil
0
+# end
0
+#
0
+# it "should be able to get field indexes" do
0
+# pending("Obsolete")
0
+# @r.get_index("id").should == 0
0
+# @r.get_index("int").should == 1
0
+# @r.get_index("time").should == 2
0
+# @r.get_index("bool").should == 3
0
+# @r.get_index("date").should == 4
0
+# @r.get_index("foo").should == nil
0
+# end
0
+#
0
+# it "should be able to determine whether a particular field is null" do
0
+# pending("Obsolete")
0
+# @r.null?(0).should == false
0
+# @r.null?(1).should == true
0
+# end
0
+#
0
+# it "should be able to get a typecasted version of a particular field" do
0
+# pending("Obsolete")
0
+# case $adapter_module.to_s
0
+# when "DataObject::Sqlite3"
0
+# @r.item(0).should == 1
0
+# @r.item(1).should == nil
0
+# @r.item(2).class.should == String
0
+# @r.item(3).should == 0
0
+# @r.item(4).class.should == String
0
+# when "DataObject::Mysql"
0
+# @r.item(0).should == 1
0
+# @r.item(1).should == nil
0
+# @r.item(2).class.should == DateTime
0
+# @r.item(3).should == false
0
+# @r.item(4).class.should == Date
0
+# end
0
+# end
0
+#
0
+# it "should be able to get to the next row" do
0
+# pending("Obsolete")
0
+# @r.next.should == true
0
+# @r.item(0).should == 2
0
+# end
0
+#
0
+# it "should return nil and close the reader when the cursor reaches the end" do
0
+# pending("Obsolete")
0
+# @r.next
0
+# @r.next.should == nil
0
+# lambda { @r.name(0) }.should raise_error(DataObject::ReaderClosed)
0
+# end
0
+#
0
+# end
0
\ No newline at end of file
...
1
2
3
4
5
6
7
8
9
 
10
11
12
13
14
15
16
17
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
20
21
22
 
 
 
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
...
1
 
 
 
2
 
 
 
 
3
4
 
 
 
 
 
 
 
 
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
 
 
22
23
24
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
0
@@ -1,66 +1,68 @@
0
 require 'spec'
0
-
0
-adapter = (ENV["ADAPTER"] || "sqlite3").dup
0
-
0
 require File.dirname(__FILE__) + "/../lib/data_objects"
0
-require File.dirname(__FILE__) + "/../../do_#{adapter}/lib/do_#{adapter}"
0
-
0
-adapter_module = adapter.dup.capitalize
0
-$adapter_module = DataObjects.const_get(adapter_module)
0
+require File.dirname(__FILE__) + "/do_mock"
0
 
0
-$uri = case adapter
0
-when "sqlite3"
0
- "sqlite3://do_rb.db"
0
-when "mysql"
0
- "mysql://root@localhost/do_rb//tmp/mysql.sock"
0
-when "postgres"
0
- "postgres://localhost/do_rb"
0
-end
0
+# adapter = (ENV["ADAPTER"] || "sqlite3").dup
0
+#
0
+#
0
+# require File.dirname(__FILE__) + "/../../do_#{adapter}/lib/do_#{adapter}"
0
+#
0
+# adapter_module = adapter.dup.capitalize
0
+# $adapter_module = DataObjects.const_get(adapter_module)
0
+#
0
+# $uri = case adapter
0
+# when "sqlite3"
0
+# "sqlite3://do_rb.db"
0
+# when "mysql"
0
+# "mysql://root@localhost/do_rb//tmp/mysql.sock"
0
+# when "postgres"
0
+# "postgres://localhost/do_rb"
0
+# end
0
 
0
-$escape = $adapter_module::QUOTE_COLUMN
0
-$escaped_columns = ["id", "int", "time", "bool", "date"].map {|x| "#{$escape}#{x}#{$escape}"}.join(", ")
0
-$quote = quote = $adapter_module::QUOTE_STRING
0
+# $escape = $adapter_module::QUOTE_COLUMN
0
+# $escaped_columns = ["id", "int", "time", "bool", "date"].map {|x| "#{$escape}#{x}#{$escape}"}.join(", ")
0
+# $quote = quote = $adapter_module::QUOTE_STRING
0
 
0
-begin
0
- c = $adapter_module::Connection.new($connection_string)
0
- c.open
0
- cmd = c.create_command("DROP TABLE table1")
0
- cmd.execute_non_query rescue nil
0
- unless adapter == "postgres"
0
- sql = <<-SQL
0
- CREATE TABLE table1 (
0
- `id` serial NOT NULL,
0
- `int` int(11) default NULL,
0
- `time` timestamp,
0
- `bool` tinyint(1) default NULL,
0
- `date` date default NULL,
0
- PRIMARY KEY (`id`)
0
- );
0
- SQL
0
- else
0
- sql = <<-SQL
0
- CREATE TABLE table1 (
0
- "id" serial NOT NULL,
0
- "int" integer default NULL,
0
- "time" timestamp,
0
- "bool" boolean default NULL,
0
- "date" date default NULL,
0
- PRIMARY KEY ("id")
0
- );
0
- SQL
0
- end
0
- cmd2 = c.create_command(sql)
0
- cmd2.execute_non_query
0
- insert1 = adapter == "postgres" ?
0
- "INSERT into table1(#{$escaped_columns}) VALUES(1, NULL, #{quote}#{Time.now.to_s_db}#{quote}, false, #{quote}#{Date.today.to_s}#{quote})" :
0
- "INSERT into table1(#{$escaped_columns}) VALUES(1, NULL, #{quote}#{Time.now.to_s_db}#{quote}, 0, #{quote}#{Date.today.to_s}#{quote})"
0
- cmd3 = c.create_command(insert1)
0
- cmd3.execute_non_query
0
- insert2 = adapter == "postgres" ?
0
- "INSERT into table1(#{$escaped_columns}) VALUES(2, 17, #{quote}#{Time.now.to_s_db}#{quote}, true, NULL)" :
0
- "INSERT into table1(#{$escaped_columns}) VALUES(2, 17, #{quote}#{Time.now.to_s_db}#{quote}, 1, NULL)"
0
- cmd4 = c.create_command(insert2)
0
- cmd4.execute_non_query
0
-ensure
0
- c.close if defined?(c) && c
0
-end
0
\ No newline at end of file
0
+# begin
0
+# c = $adapter_module::Connection.new($connection_string)
0
+# c.open
0
+# cmd = c.create_command("DROP TABLE table1")
0
+# cmd.execute_non_query rescue nil
0
+# unless adapter == "postgres"
0
+# sql = <<-SQL
0
+# CREATE TABLE table1 (
0
+# `id` serial NOT NULL,
0
+# `int` int(11) default NULL,
0
+# `time` timestamp,
0
+# `bool` tinyint(1) default NULL,
0
+# `date` date default NULL,
0
+# PRIMARY KEY (`id`)
0
+# );
0
+# SQL
0
+# else
0
+# sql = <<-SQL
0
+# CREATE TABLE table1 (
0
+# "id" serial NOT NULL,
0
+# "int" integer default NULL,
0
+# "time" timestamp,
0
+# "bool" boolean default NULL,
0
+# "date" date default NULL,
0
+# PRIMARY KEY ("id")
0
+# );
0
+# SQL
0
+# end
0
+# cmd2 = c.create_command(sql)
0
+# cmd2.execute_non_query
0
+# insert1 = adapter == "postgres" ?
0
+# "INSERT into table1(#{$escaped_columns}) VALUES(1, NULL, #{quote}#{Time.now.to_s_db}#{quote}, false, #{quote}#{Date.today.to_s}#{quote})" :
0
+# "INSERT into table1(#{$escaped_columns}) VALUES(1, NULL, #{quote}#{Time.now.to_s_db}#{quote}, 0, #{quote}#{Date.today.to_s}#{quote})"
0
+# cmd3 = c.create_command(insert1)
0
+# cmd3.execute_non_query
0
+# insert2 = adapter == "postgres" ?
0
+# "INSERT into table1(#{$escaped_columns}) VALUES(2, 17, #{quote}#{Time.now.to_s_db}#{quote}, true, NULL)" :
0
+# "INSERT into table1(#{$escaped_columns}) VALUES(2, 17, #{quote}#{Time.now.to_s_db}#{quote}, 1, NULL)"
0
+# cmd4 = c.create_command(insert2)
0
+# cmd4.execute_non_query
0
+# ensure
0
+# c.close if defined?(c) && c
0
+# end
0
\ No newline at end of file

Comments

    No one has commented yet.