mixr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +5 -0
- data/COPYING +18 -0
- data/ChangeLog +2 -0
- data/README +52 -0
- data/bin/mixr +69 -0
- data/lib/mixr_client.rb +213 -0
- data/mixr_server/mixr.conf_sample +1 -0
- data/mixr_server/mixr.erl +140 -0
- data/setup.rb +1585 -0
- metadata +68 -0
data/AUTHORS
ADDED
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.
|
data/ChangeLog
ADDED
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"]
|
data/bin/mixr
ADDED
@@ -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
|
data/lib/mixr_client.rb
ADDED
@@ -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.
|