mixr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1,5 @@
1
+ = Authors
2
+
3
+ Gregoire Lejeune <gregoire dot lejeune at free dot fr>
4
+ * Maintener
5
+
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Gregoire Lejeune
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ 0.1.0 :
2
+ * Initial version
data/README ADDED
@@ -0,0 +1,52 @@
1
+ = Mixr
2
+
3
+ == About
4
+
5
+ Mixr is a tiny memory object caching system
6
+
7
+ == Licence
8
+
9
+ Copyright (c) 2008 Gregoire Lejeune
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to
13
+ deal in the Software without restriction, including without limitation the
14
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15
+ sell copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in
19
+ all copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ == INSTALLATION
29
+
30
+ sudo gem install mixr
31
+
32
+ == REQUIREMENT
33
+
34
+ Mixr server was written in erlang[http://www.erlang.org]. So you need it to use Mixr.
35
+
36
+ == USAGE
37
+
38
+ First, start the mixr server :
39
+
40
+ $ mixr start
41
+
42
+ Then do something like this :
43
+
44
+ require 'mixr_client'
45
+
46
+ m = MixrClient.new( "mixr.myserver.com", 9900 )
47
+
48
+ # store
49
+ m["key"] = "value"
50
+
51
+ # fetch
52
+ my_val = m["key"]
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'fileutils'
4
+ require 'socket'
5
+
6
+ ## If the app run on Windows, check if win32/process is installed
7
+ if /Windows/.match( ENV['OS'] )
8
+ begin
9
+ require 'win32/process'
10
+ rescue LoadError => e
11
+ warn "`win32-process' is not installed!"
12
+ end
13
+ end
14
+
15
+ require 'simple-daemon'
16
+
17
+ # Get local hostname
18
+ hostname = Socket.gethostname.gsub( /\..*$/, "" )
19
+
20
+ # Server Directory
21
+ server_directory = File.expand_path(File.join(File.dirname(__FILE__), "..", "mixr_server"))
22
+
23
+ # mixr server options
24
+ options = { :mixr_directory => "#{ENV['HOME']}/.mixr", :config_file => :default_or_none}
25
+ server_daemonize = true
26
+
27
+ # Make ~/.mixr or mixr_directory if it doesn't exist
28
+ unless FileTest.directory?( options[:mixr_directory] )
29
+ FileUtils.mkdir_p( options[:mixr_directory] )
30
+ end
31
+
32
+ # Compile erlang
33
+ compile_command = "erlc -o #{options[:mixr_directory]} #{File.join(server_directory, 'mixr.erl')}"
34
+ `#{compile_command}`
35
+
36
+
37
+
38
+
39
+
40
+
41
+ SimpleDaemon::WORKING_DIRECTORY = options[:mixr_directory]
42
+ class MixrDaemon < SimpleDaemon::Base
43
+ @@command = nil
44
+
45
+ def self.command=(s)
46
+ @@command = s
47
+ end
48
+
49
+ def self.start
50
+ fhi = IO.popen( @@command )
51
+ while (line = fhi.gets)
52
+ print line
53
+ end
54
+ # exec @@command
55
+ end
56
+
57
+ def self.stop
58
+
59
+ end
60
+ end
61
+
62
+ MixrDaemon.command = "cd #{options[:mixr_directory]}; erl -mnesia dir '\"#{options[:mixr_directory]}/Mixr.database\"' -name #{hostname} -noshell -s mixr start -s init stop "
63
+
64
+ # Start or demonize
65
+ if server_daemonize == true
66
+ MixrDaemon.daemonize
67
+ else
68
+ server.start
69
+ end
@@ -0,0 +1,213 @@
1
+ # Copyright (c) 2008 Gregoire Lejeune
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to
5
+ # deal in the Software without restriction, including without limitation the
6
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ # sell copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ # THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ require 'socket'
21
+
22
+ class MixrError < StandardError; end
23
+
24
+ class MixrClient
25
+
26
+ def initialize( h = 'localhost', p = 9900 )
27
+ @host = h
28
+ @port = p
29
+ end
30
+
31
+ private
32
+ def action( c )
33
+ socket = TCPSocket.new( @host, @port )
34
+ socket.write( c )
35
+ r = socket.read
36
+ socket.close
37
+ r
38
+ end
39
+
40
+ def id
41
+ @id
42
+ end
43
+
44
+ public
45
+
46
+ # hsh.clear -> hsh
47
+ #
48
+ # Removes all key-value pairs from _hsh_.
49
+ def clear
50
+ eval(action( "CLEAR@" ))
51
+ self
52
+ end
53
+
54
+ # hsh.keys => array
55
+ #
56
+ # Returns a new array populated with the keys from this hash.
57
+ def keys
58
+ eval(action( "KEYS@" ))
59
+ end
60
+
61
+ # hsh.values => array
62
+ #
63
+ # Returns a new array populated with the values from _hsh_.
64
+ def values
65
+ eval(action( "VALUES@" ))
66
+ end
67
+
68
+ # hsh.each_value {| value | block } -> hsh
69
+ #
70
+ # Calls _block_ once for each key in _hsh_, passing the value as a
71
+ # parameter.
72
+ def each_value( &b )
73
+ values.each do |v|
74
+ yield( v )
75
+ end
76
+ end
77
+
78
+ # hsh.each_key {| key | block } -> hsh
79
+ #
80
+ # Calls _block_ once for each key in _hsh_, passing the key as a
81
+ # parameter.
82
+ def each_key( &b )
83
+ keys.each do |k|
84
+ yield( k )
85
+ end
86
+ end
87
+
88
+ # hsh.empty? => true or false
89
+ #
90
+ # Returns +true+ if _hsh_ contains no key-value pairs.
91
+ def empty?
92
+ keys.size == 0
93
+ end
94
+
95
+ # hsh.each {| key, value | block } -> hsh
96
+ #
97
+ # Calls _block_ once for each key in _hsh_, passing the key and value
98
+ # to the block as a two-element array. Because of the assignment
99
+ # semantics of block parameters, these elements will be split out if
100
+ # the block has two formal parameters. Also see +Hash.each_pair+,
101
+ # which will be marginally more efficient for blocks with two
102
+ # parameters.
103
+ def each( &b )
104
+ keys.each do |k|
105
+ yield(k, self[k])
106
+ end
107
+ self
108
+ end
109
+
110
+ # hsh.delete(key) => value
111
+ # hsh.delete(key) {|key| block } => value
112
+ #
113
+ # Deletes and returns a key-value pair from _hsh_ whose key is equal
114
+ # to _key_. If the key is not found, returns the _default value_. If
115
+ # the optional code block is given and the key is not found, pass in
116
+ # the key and return the result of _block_.
117
+ def delete( k )
118
+ begin
119
+ r = eval(action( "DELETE@#{k}" ))
120
+ rescue SyntaxError
121
+ r = nil
122
+ end
123
+ if block_given? and r.nil?
124
+ yield( k )
125
+ else
126
+ r
127
+ end
128
+ end
129
+
130
+ # hsh[key] = value => value
131
+ # hsh.store(key, value) => value
132
+ #
133
+ # Element Assignment---Associates the value given by _value_ with the
134
+ # key given by _key_. _key_ should not have its value changed while
135
+ # it is in use as a key.
136
+ def []=( k, v )
137
+ unless eval(action( "SET@#{k}|#{v}" ))
138
+ raise MixrError, "Can't set #{k} with value #{v}!"
139
+ end
140
+ end
141
+ alias :store :[]=
142
+
143
+ # hsh[key] => value
144
+ #
145
+ # Element Reference---Retrieves the _value_ object corresponding to
146
+ # the _key_ object. If not found, returns the a default value
147
+ def []( k )
148
+ begin
149
+ eval(action( "GET@#{k}" ))
150
+ rescue SyntaxError
151
+ nil
152
+ end
153
+ end
154
+
155
+ # hsh.fetch(key [, default] ) => obj
156
+ # hsh.fetch(key) {| key | block } => obj
157
+ #
158
+ # Returns a value from the hash for the given key. If the key can't
159
+ # be found, there are several options: With no other arguments, it
160
+ # will raise an +IndexError+ exception; if _default_ is given, then
161
+ # that will be returned; if the optional code block is specified,
162
+ # then that will be run and its result returned.
163
+ def fetch( k, d = nil )
164
+ r = self[k] || d
165
+ if block_given? and r.nil?
166
+ r = yield( k )
167
+ end
168
+ r
169
+ end
170
+
171
+ # hsh.length => fixnum
172
+ # hsh.size => fixnum
173
+ #
174
+ # Returns the number of key-value pairs in the hash.
175
+ def length
176
+ keys.size
177
+ end
178
+ alias :size :length
179
+
180
+ # hsh.has_key?(key) => true or false
181
+ # hsh.include?(key) => true or false
182
+ # hsh.key?(key) => true or false
183
+ # hsh.member?(key) => true or false
184
+ #
185
+ # Returns +true+ if the given key is present in _hsh_.
186
+ def has_key?( k )
187
+ keys.include?( k )
188
+ end
189
+ alias :include? :has_key?
190
+ alias :key? :has_key?
191
+ alias :member? :has_key?
192
+
193
+ # hsh.has_value?(value) => true or false
194
+ # hsh.value?(value) => true or false
195
+ #
196
+ # Returns +true+ if the given value is present for some key in _hsh_.
197
+ def has_value?( v )
198
+ values.include?( v )
199
+ end
200
+ alias :value? :has_value?
201
+
202
+ def to_hash
203
+ h = {}
204
+ self.each do |k, v|
205
+ h[k] = v
206
+ end
207
+ h
208
+ end
209
+
210
+ def method_missing( id, *args )
211
+ to_hash.send( id.id2name, *args )
212
+ end
213
+ end
@@ -0,0 +1 @@
1
+ {port, 9900}.
@@ -0,0 +1,140 @@
1
+ % Copyright (c) 2008 Gregoire Lejeune
2
+ %
3
+ % Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ % of this software and associated documentation files (the "Software"), to
5
+ % deal in the Software without restriction, including without limitation the
6
+ % rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ % sell copies of the Software, and to permit persons to whom the Software is
8
+ % furnished to do so, subject to the following conditions:
9
+ %
10
+ % The above copyright notice and this permission notice shall be included in
11
+ % all copies or substantial portions of the Software.
12
+ %
13
+ % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ % THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ % IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ % CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ -module(mixr).
21
+ -author('gregoire.lejeune@free.fr').
22
+ -compile(export_all).
23
+ -import(random).
24
+ -include_lib("stdlib/include/qlc.hrl").
25
+
26
+ %% The primary key of the table is the first column in the table. So...
27
+ -record(mixr_cache, {key, value}).
28
+
29
+ %% Version du server
30
+ -define(MIXR_VERSION, "0.1.0").
31
+
32
+ %% Ceci permet de créer la base Mnesia
33
+ do_this_once() ->
34
+ mnesia:create_schema([node()]),
35
+ mnesia:start(),
36
+ mnesia:create_table(mixr_cache, [{attributes, record_info(fields, mixr_cache)}]),
37
+ mnesia:stop().
38
+
39
+ %% Démarrage du serveur.
40
+ start(Port) ->
41
+ do_this_once(),
42
+ mnesia:start(),
43
+ mnesia:wait_for_tables([mixr_cache], 20000),
44
+ {ok, ListenSocket} = gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]),
45
+ io:format("** mixr version ~s Started (port ~p)~n", [?MIXR_VERSION, Port]),
46
+ loop(ListenSocket).
47
+ start() ->
48
+ case file:consult("mixr.conf") of
49
+ {ok, C} ->
50
+ io:format("** Read config from ./mixr.conf~n"),
51
+ [{port, P}] = C,
52
+ start(P);
53
+ {error, _} ->
54
+ case file:consult(string:concat(os:getenv("HOME"), "/mixr.conf")) of
55
+ {ok, C} ->
56
+ io:format("** Read config from $HOME/mixr.conf~n"),
57
+ [{port, P}] = C,
58
+ start(P);
59
+ {error, _} ->
60
+ io:format("No configuration file found. Start mixr server on port 9900!"),
61
+ start(9900)
62
+ end
63
+ end.
64
+
65
+ loop(ListenSocket) ->
66
+ case gen_tcp:accept(ListenSocket) of
67
+ {ok, Socket} ->
68
+ spawn(fun() ->
69
+ handle_connection(Socket)
70
+ end),
71
+ loop(ListenSocket);
72
+ {error, Reason} ->
73
+ io:format("Error: ~p~n", [Reason])
74
+ end.
75
+
76
+ handle_connection(Socket) ->
77
+ try communication(Socket)
78
+ catch
79
+ error:Reason ->
80
+ {gen_tcp:send(Socket, io_lib:format("Error: ~p~n", [Reason]))}
81
+ end,
82
+ ok = gen_tcp:close(Socket).
83
+
84
+ communication(Socket) ->
85
+ {ok, Binary} = gen_tcp:recv(Socket, 0),
86
+ io:format( "mixr server receive : ~p~n", [Binary]),
87
+ {ok, [A|D]} = regexp:split( binary_to_list(Binary), "@" ),
88
+ case A of
89
+ "KEYS" -> R = keys();
90
+ "VALUES" -> R = values();
91
+ "CLEAR" -> R = clear();
92
+ "DELETE" -> [R] = delete(D);
93
+ "GET" -> [R] = do_get(D);
94
+ "SET" -> R = do_set(D)
95
+ end,
96
+ gen_tcp:send(Socket, io_lib:format( "~p", [R] )).
97
+
98
+ keys() ->
99
+ do(qlc:q([X#mixr_cache.key || X <- mnesia:table(mixr_cache)])).
100
+
101
+ values() ->
102
+ do(qlc:q([X#mixr_cache.value || X <- mnesia:table(mixr_cache)])).
103
+
104
+ clear() ->
105
+ lists:foreach( fun(E) ->
106
+ mnesia:transaction( fun() -> mnesia:delete({mixr_cache, E}) end )
107
+ end, keys() ),
108
+ true.
109
+
110
+ delete(T) ->
111
+ R = do_get(T),
112
+ [K] = T,
113
+ mnesia:transaction( fun() -> mnesia:delete({mixr_cache, K}) end ),
114
+ R.
115
+
116
+ do_get(T) ->
117
+ [K] = T,
118
+ do(qlc:q([X#mixr_cache.value || X <- mnesia:table(mixr_cache),
119
+ X#mixr_cache.key =:= K
120
+ ])).
121
+
122
+ do_set(T) ->
123
+ [D] = T,
124
+ {ok, [K, V]} = regexp:split( D, "|" ),
125
+ Row = #mixr_cache{key=K, value=V},
126
+ F = fun() ->
127
+ mnesia:write(Row)
128
+ end,
129
+ case mnesia:transaction(F) of
130
+ {aborted, _} -> false;
131
+ {atomic, _} -> true
132
+ end.
133
+
134
+ view() ->
135
+ io:format( "~p~n", [do(qlc:q([X || X <- mnesia:table(mixr_cache)]))] ).
136
+
137
+ do(Q) ->
138
+ F = fun() -> qlc:e(Q) end,
139
+ {atomic, Val} = mnesia:transaction(F),
140
+ Val.