Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions profile17.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
-- Tuple of profile has always 2 fields.
-- Field 1 -- user-id
-- Field 2 -- key/value dict. Key is always a number.

proscribe_host = '10.161.41.111'
proscribe_port = 27017
keys_to_proscribe = {324}

-- Create a user, then init storage, create functions and then revoke ALL priveleges from user
local function init_storage(init_func, interface)
local init_username = 'profile'
box.schema.user.create(init_username, {if_not_exists = true})
box.schema.user.grant(init_username, 'execute,read,write', 'universe', nil,
{if_not_exists = true})
box.session.su(init_username)

init_func()

for _, v in pairs(interface) do
box.schema.func.create(v, {setuid = true, if_not_exists = true})
end

box.session.su('admin')
box.schema.user.revoke(init_username, 'execute,read,write', 'universe')
end

-- Create a role, which can execute interface functions
local function init_role(role_name, interface)
box.schema.role.create(role_name, {if_not_exists = true})
for _, v in pairs(interface) do
box.schema.role.grant(role_name, 'execute', 'function', v,
{if_not_exists = true})
end
end

-- Scheme initialization
local function init()
local s = box.schema.create_space('profile', {
if_not_exists = true,
})
s:create_index('primary', {
type = 'tree',
unique = true,
parts = {1, 'unsigned'},
if_not_exists = true,
})
end

-- List of functions, which is possible to call from outside the box
local interface = {
'profile_delete',
'profile_get_all',
'profile_multiget',
'profile_multiset',
'profile_set',
}

msgpack = require('msgpack')
socket = require('socket')
digest = require('digest')
pickle = require('pickle')

proscribe_socket = socket('AF_INET', 'SOCK_DGRAM', 'udp')

-- Function, which sends requested profile_key changes to special DWH daemon
local function send_to_proscribe(uid, profile_key, old_value, new_value)
local fit = false
for _, v in ipairs(keys_to_proscribe) do
if v == profile_key then
fit = true
end
end
if not fit then
return
end

if not old_value then
old_value = ''
end
if not new_value then
new_value = ''
end
if old_value == new_value then -- we doesnt want to send data without changes
return
end

old_value = digest.base64_encode(old_value)
new_value = digest.base64_encode(new_value)

local body = pickle.pack('iliiAiA', 1, uid, profile_key, #old_value, old_value, #new_value, new_value)
local packet = pickle.pack('iiiA', 1, #body, 0, body)
proscribe_socket:sendto(proscribe_host, proscribe_port, packet)
end

-- Cast internal profile format to the format, requested by client-side.
local function cast_profile_to_return_format(profile)
setmetatable(profile.data, msgpack.map_mt)
return {profile.uid, profile.data}
end

local function store_profile(profile)
local count = 0
for k,v in pairs(profile.data) do count = count + 1 end

-- We dont want to store empty profiles. Save space.
if count == 0 then
return box.space.profile:delete(profile.uid)
end

setmetatable(profile.data, msgpack.map_mt)
return box.space.profile:replace({profile.uid, profile.data})
end

local function create_new_profile(user_id)
local profile = {}
profile.uid = user_id
profile.data = {}
return profile
end

local function load_profile(user_id)
local tup = box.space.profile:select(user_id)
-- In case no profile found, operate it as profile without keys/values
if #tup == 0 then
return create_new_profile(user_id)
end
local profile = {}
profile.uid = user_id
profile.data = tup[1][2] -- Index 1 is because we have only 1 tuple with such userid (index is unique). Second field of tuple is key/value dict.
return profile
end

local function set_profile_key(profile, key, value)
-- Do not store empty keys. We want to save space.
if value == '' then
value = nil
end
profile.data[key] = value
end

-- function profile_delete delete profile. Returns nothing
function profile_delete(user_id)
box.space.profile:delete(user_id)
end

-- function profile_get_all returns full profile
function profile_get_all(user_id)
local profile = load_profile(user_id)
return cast_profile_to_return_format(profile)
end

-- function profile_multiget returns only requested keys from profile. Accepts user_id and then several keys
function profile_multiget(user_id, ...)
local pref_list = {...}
if #pref_list == 0 then
return cast_profile_to_return_format(create_new_profile(user_id))
end

local profile = load_profile(user_id)

-- Create a copy of profile. We select few keys, so it is faster to copy only needed keys, then clear not needed keys
local profile_copy = create_new_profile(profile.uid)
for _, v in ipairs(pref_list) do
if profile.data[v] then
profile_copy.data[v] = profile.data[v]
end
end

return cast_profile_to_return_format(profile_copy)
end

-- function profile_multiset accepts user_id and then key, value, key, value, key, value, ... Returns full updated profile.
function profile_multiset(user_id, ...)
local pref_list = {...}

if #pref_list % 2 ~= 0 then
error('Not even number of arguments')
end

local profile = load_profile(user_id)

-- In case of no keys were passed, just return full profile
if #pref_list == 0 then
return cast_profile_to_return_format(profile)
end

local i, pref_key = next(pref_list)
local i, pref_value = next(pref_list, i)
-- iterate all passed key/value pairs from arguments
while pref_key ~= nil and pref_value ~= nil do
send_to_proscribe(profile.uid, pref_key, profile.data[pref_key], pref_value)
set_profile_key(profile, pref_key, pref_value)
i, pref_key = next(pref_list, i)
i, pref_value = next(pref_list, i)
end

store_profile(profile)
return cast_profile_to_return_format(profile)
end

-- function profile_set set only one key. Returns full updated profile
function profile_set(user_id, key, value)
return profile_multiset(user_id, key, value)
end

init_storage(init, interface)
init_role('profile_role', interface)
66 changes: 66 additions & 0 deletions profiles.lua
Original file line number Diff line number Diff line change
Expand Up @@ -502,3 +502,69 @@ function profile_print_bigger_than(size_in_bytes)
end
end
end

local helper_key_id = 1011
local email_key_id = 314

local function get_helpers(helpers_string)
if (helpers_string:len() - 1) % 4 ~= 0 then
return nil, "Bad size of helpers"
end

local helpers = {}
local i = 1
while i < helpers_string:len() do
local index = box.unpack('i', string.sub(helpers_string, i, i + 3))
local state = bit.band(index, 0x80000000)
index = bit.band(index, 0x7FFFFFFF)
i = i + 4

local time = box.unpack('i', string.sub(helpers_string, i, i + 3))
i = i + 4

local show = box.unpack('i', string.sub(helpers_string, i, i + 3))
i = i + 4

local close = box.unpack('i', string.sub(helpers_string, i, i + 3))
i = i + 4

local helper = {index = index, state = state, time = time, show = show, close = close}

helpers[index] = helper
end

if string.sub(helpers_string, i, i+1) ~= '!' then
return nil, "Non '!' end of helpers, invalid helpers"
end

return helpers, nil
end

function profile_print_with_helper(helper_number)
if box.cfg.replication_source == nil then error("replica api only") end
if type(helper_number) == "string" then helper_number = tonumber(helper_number) end

profile_apply_func(
function(p, helper_number)
local packed_key_id = box.pack("w", helper_key_id)
local packed_email_key_id = box.pack("w", email_key_id)

if p.prefs[packed_key_id] then
if p.prefs[packed_key_id]:len() > 1 then
local helpers, err = get_helpers(p.prefs[packed_key_id])

if helpers then
if helpers[helper_number] then
print("user_id: ", profile_id_to_int(p.id), " id: ", helper_key_id, " val: ", helper_number, " ", helpers[helper_number]['state'], " ", helpers[helper_number]['time'], " ", helpers[helper_number]['show'], " ", helpers[helper_number]['close'], " ", p.prefs[packed_email_key_id])
end
else
print("user_id: ", profile_id_to_int(p.id), " id: ", helper_key_id, " val: BAD (", err, ")")
end
end
end
end,
helper_number
)

end

30 changes: 25 additions & 5 deletions ussender.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
-- Race conditions in this files are possible. It is ok by biz logic.
--

local tuple_length_limit = 1001 --max numbers of element + 1. VALUE MUST BE ODD.
local space_no = 0

local function truncate_tuple(selected)
selected = selected:transform(1, #selected - tuple_length_limit):transform(1, (tuple_length_limit - 1) / 2)
box.replace(space_no, selected)
return selected
end

function ussender_add(user_id, sender_id)
local user_id = box.unpack("i", user_id)
local sender_id = box.unpack("l", sender_id)

local selected = { box.select_limit(0, 0, 0, 1, user_id) }
local selected = { box.select_limit(space_no, 0, 0, 1, user_id) }

if #selected == 0 then
box.insert(0, user_id, sender_id)
box.insert(space_no, user_id, sender_id)
else

if #selected[1] >= tuple_length_limit then
selected[1] = truncate_tuple(selected[1])
end

local fun, param, state = selected[1]:pairs()
state, _ = fun(param, state) -- skip the first element of tuple
for state, v in fun, param, state do
Expand All @@ -15,21 +32,24 @@ function ussender_add(user_id, sender_id)
return
end
end
box.update(0, user_id, "!p", -1, sender_id)
box.update(space_no, user_id, "!p", -1, sender_id)
end

end

function ussender_select(user_id)
local user_id = box.unpack("i", user_id)
local ret = {box.select_limit(0, 0, 0, 1, user_id)}
local ret = {box.select_limit(space_no, 0, 0, 1, user_id)}
if #ret == 0 then
return {user_id}
end
if #ret[1] >= tuple_length_limit then
ret[1] = truncate_tuple(ret[1])
end
return ret
end

function ussender_delete(user_id)
local user_id = box.unpack("i", user_id)
box.delete(0, user_id)
box.delete(space_no, user_id)
end