mixr 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|