feeds.erl 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. -module(feeds).
  2. -compile(export_all).
  3. -include_lib("kvs/include/feeds.hrl").
  4. -include_lib("kvs/include/users.hrl").
  5. -include_lib("kvs/include/groups.hrl").
  6. -include_lib("kvs/include/feed_state.hrl").
  7. -include_lib("kvs/include/log.hrl").
  8. create() ->
  9. FId = kvs:next_id("feed", 1),
  10. ok = kvs:put(#feed{id = FId} ),
  11. FId.
  12. add_direct_message(FId, User, Desc) -> add_direct_message(FId, User, utils:uuid_ex(), Desc).
  13. add_direct_message(FId, User, EntryId, Desc) -> add_direct_message(FId, User, undefined, EntryId, Desc, []).
  14. add_direct_message(FId, User, To, EntryId, Desc, Medias) -> kvs:feed_add_direct_message(FId, User, To, EntryId, Desc, Medias).
  15. add_group_entry(FId, User, EntryId, Desc, Medias) -> kvs:feed_add_entry(FId, User, EntryId, Desc, Medias).
  16. add_group_entry(FId, User, To, EntryId, Desc, Medias, Type) -> kvs:feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, "").
  17. add_entry(FId, User, EntryId, Desc) -> add_entry(FId, User, EntryId, Desc, []).
  18. add_entry(FId, User, EntryId, Desc, Medias) -> kvs:feed_add_entry(FId, User, EntryId, Desc, Medias).
  19. add_entry(FId, User, To, EntryId, Desc, Medias, Type) -> kvs:feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, "").
  20. add_shared_entry(FId, User, To, EntryId, Desc, Medias, Type, SharedBy) -> kvs:feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, SharedBy).
  21. add_like(Fid, Eid, Uid) ->
  22. Write_one_like = fun(Next) ->
  23. Self_id = kvs:next_id("one_like", 1),
  24. kvs:put(#one_like{ % add one like
  25. id = Self_id,
  26. user_id = Uid,
  27. entry_id = Eid,
  28. feed_id = Fid,
  29. created_time = now(),
  30. next = Next
  31. }),
  32. Self_id
  33. end,
  34. % add entry - like
  35. case kvs:get(entry_likes, Eid) of
  36. {ok, ELikes} ->
  37. kvs:put(ELikes#entry_likes{
  38. one_like_head = Write_one_like(ELikes#entry_likes.one_like_head),
  39. total_count = ELikes#entry_likes.total_count + 1
  40. });
  41. {error, notfound} ->
  42. kvs:put(#entry_likes{
  43. entry_id = Eid,
  44. one_like_head = Write_one_like(undefined),
  45. total_count = 1
  46. })
  47. end,
  48. % add user - like
  49. case kvs:get(user_likes, Uid) of
  50. {ok, ULikes} ->
  51. kvs:put(ULikes#user_likes{
  52. one_like_head = Write_one_like(ULikes#user_likes.one_like_head),
  53. total_count = ULikes#user_likes.total_count + 1
  54. });
  55. {error, notfound} ->
  56. kvs:put(#user_likes{
  57. user_id = Uid,
  58. one_like_head = Write_one_like(undefined),
  59. total_count = 1
  60. })
  61. end.
  62. % statistics
  63. get_entries_count(Uid) ->
  64. case kvs:get(user_etries_count, Uid) of
  65. {ok, UEC} ->
  66. UEC#user_etries_count.entries;
  67. {error, notfound} ->
  68. 0
  69. end.
  70. get_comments_count(Uid) ->
  71. case kvs:get(user_etries_count, Uid) of
  72. {ok, UEC} ->
  73. UEC#user_etries_count.comments;
  74. {error, notfound} ->
  75. 0
  76. end.
  77. get_feed(FId) -> kvs:get(feed, FId).
  78. get_entries_in_feed(FId) -> kvs:entries_in_feed(FId).
  79. get_entries_in_feed(FId, Count) -> kvs:entries_in_feed(FId, Count).
  80. get_entries_in_feed(FId, StartFrom, Count) -> kvs:entries_in_feed(FId, StartFrom, Count).
  81. get_direct_messages(FId, Count) -> kvs:entries_in_feed(FId, undefined, Count).
  82. get_direct_messages(FId, StartFrom, Count) -> kvs:entries_in_feed(FId, StartFrom, Count).
  83. get_entries_in_feed(FId, StartFrom, Count, FromUserId)-> Entries = kvs:entries_in_feed(FId, StartFrom, Count),
  84. [E || #entry{from = From} = E <- Entries, From == FromUserId].
  85. create_message(Table) ->
  86. EId = kvs:next_id("entry", 1),
  87. #entry{id = {EId, system_info},
  88. entry_id = EId,
  89. from = system,
  90. type = {system, new_table},
  91. created_time = now(),
  92. description = Table}.
  93. remove_entry(FeedId, EId) ->
  94. {ok, #feed{top = TopId} = Feed} = get_feed(FeedId),
  95. case kvs:get(entry, {EId, FeedId}) of
  96. {ok, #entry{prev = Prev, next = Next}}->
  97. ?INFO("P: ~p, N: ~p", [Prev, Next]),
  98. case kvs:get(entry, Next) of
  99. {ok, NE} -> kvs:put(NE#entry{prev = Prev});
  100. _ -> ok
  101. end,
  102. case kvs:get(entry, Prev) of
  103. {ok, PE} -> kvs:put(PE#entry{next = Next});
  104. _ -> ok
  105. end,
  106. case TopId of
  107. {EId, FeedId} -> kvs:put(Feed#feed{top = Prev});
  108. _ -> ok
  109. end;
  110. {error, notfound} -> ?INFO("Not found"), ok
  111. end,
  112. kvs:delete(entry, {EId, FeedId}).
  113. edit_entry(FeedId, EId, NewDescription) ->
  114. case kvs:entry_by_id({EId, FeedId}) of
  115. {ok, OldEntry} ->
  116. NewEntryRaw = OldEntry#entry{description = NewDescription,
  117. raw_description = NewDescription},
  118. NewEntry = feedformat:format(NewEntryRaw),
  119. kvs:put(NewEntry);
  120. {error, notfound}->
  121. {error, notfound}
  122. end.
  123. remove_entry_comments(FId, EId) ->
  124. AllComments = kvs:comments_by_entry(FId, EId),
  125. [begin
  126. kvs:delete(comment, ID)
  127. end || #comment{id = ID, media = M} <- AllComments].
  128. entry_add_comment(FId, User, EntryId, ParentComment, CommentId, Content, Medias) ->
  129. case kvs:entry_by_id({EntryId, FId}) of
  130. {ok, _E} ->
  131. kvs:add_comment(FId, User, EntryId, ParentComment, CommentId, Content, Medias);
  132. _ ->
  133. ok
  134. end.
  135. get_one_like_list(undefined) -> [];
  136. get_one_like_list(Id) -> {ok, OneLike} = kvs:get(one_like, Id),
  137. [OneLike] ++ get_one_like_list(OneLike#one_like.next).
  138. get_entries_likes(Entry_id) ->
  139. case kvs:get(entry_likes, Entry_id) of
  140. {ok, Likes} -> get_one_like_list(Likes#entry_likes.one_like_head);
  141. {error, notfound} -> []
  142. end.
  143. get_entries_likes_count(Entry_id) ->
  144. case kvs:get(entry_likes, Entry_id) of
  145. {ok, Likes} ->
  146. Likes#entry_likes.total_count;
  147. {error, notfound} -> 0
  148. end.
  149. get_user_likes_count(UserId) ->
  150. case kvs:get(user_likes, UserId) of
  151. {ok, Likes} -> Likes#user_likes.total_count;
  152. {error, notfound} -> 0
  153. end.
  154. get_user_likes(UserId) ->
  155. case kvs:get(user_likes, UserId) of
  156. {ok, Likes} -> get_one_like_list(Likes#user_likes.one_like_head);
  157. {error, notfound} -> []
  158. end.
  159. get_one_like_list(undefined, _) -> [];
  160. get_one_like_list(_, 0) -> [];
  161. get_one_like_list(Id, N) -> {ok, OneLike} = kvs:get(one_like, Id),
  162. [OneLike] ++ get_one_like_list(OneLike#one_like.next, N-1).
  163. get_user_likes(UserId, {Page, PageAmount}) ->
  164. case kvs:get(user_likes, UserId) of
  165. {ok, Likes} -> lists:nthtail((Page-1)*PageAmount, get_one_like_list(Likes#user_likes.one_like_head, PageAmount*Page));
  166. {error, notfound} -> []
  167. end.
  168. % we have same in user? Why?
  169. is_subscribed_user(UserUidWho, UserUidWhom) -> kvs_users:is_user_subscr(UserUidWho, UserUidWhom).
  170. user_subscription_count(UserUid) -> length(kvs_users:list_subscr(UserUid)).
  171. user_friends_count(UserUid) -> length(kvs_users:list_subscr_me(UserUid)).
  172. get_comments_entries(UserUid, _, _Page, _PageAmount) ->
  173. Pids = [Eid || #comment{entry_id=Eid} <- kvs:select(comment,
  174. fun(#comment{author_id=Who}) when Who=:=UserUid ->true;(_)->false end)],
  175. %?PRINT({"GCE pids length: ", length(Pids)}),
  176. lists:flatten([kvs:select(entry,[{where, fun(#entry{entry_id=ID})-> ID=:=Pid end},
  177. {order, {1, descending}},{limit, {1,1}}]) || Pid <- Pids]).
  178. get_my_discussions(_FId, Page, PageAmount, UserUid) ->
  179. _Offset= case (Page-1)*PageAmount of
  180. 0 -> 1
  181. ;M-> M
  182. end,
  183. Pids = [Eid || #comment{entry_id=Eid} <- kvs:select(comment,
  184. fun(#comment{author_id=Who}) when Who=:=UserUid ->true;(_)->false end)],
  185. lists:flatten([kvs:select(entry,[{where, fun(#entry{entry_id=ID})-> ID=:=Pid end},
  186. {order, {1, descending}},{limit, {1,1}}]) || Pid <- Pids]).
  187. test_likes() ->
  188. add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb", "derp"),
  189. add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb", "derpina"),
  190. add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb", "derpington"),
  191. add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bc", "derp"),
  192. add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bc", "lederpeaux"),
  193. add_like(2, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bd", "derp"),
  194. add_like(2, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2be", "derp"),
  195. [
  196. get_entries_likes("17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb"),
  197. get_entries_likes_count("17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb") == 3,
  198. get_user_likes("derp"),
  199. get_user_likes("derp", {1, 2}),
  200. get_user_likes_count("derp") == 4
  201. ].
  202. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  203. handle_notice(["feed", "delete", Owner] = Route, Message,
  204. #state{owner = Owner} = State) ->
  205. ?INFO("feed(~p): notification received: User=~p, Route=~p, Message=~p",
  206. [self(), Owner, Route, Message]),
  207. {stop, normal, State};
  208. handle_notice(["feed", "group", GroupId, "entry", EntryId, "add"] = Route,
  209. [From|_] = Message,
  210. #state{owner = Owner, feed = Feed} = State) ->
  211. ?INFO("feed(~p): group message: Owner=~p, Route=~p, Message=~p",
  212. [self(), Owner, Route, Message]),
  213. [From, _Destinations, Desc, Medias] = Message,
  214. feed:add_group_entry(Feed, From, [{GroupId, group}], EntryId,
  215. Desc, Medias, {group, direct}),
  216. % statistics
  217. case Owner == GroupId of
  218. false -> ok;
  219. true ->
  220. {ok, Group} = kvs:get(group, GroupId),
  221. GE = Group#group.entries_count,
  222. kvs:put(Group#group{entries_count = GE+1}),
  223. {ok, Subs} = kvs:get(group_subs, {From, GroupId}),
  224. SE = Subs#group_subscription.user_posts_count,
  225. kvs:put(Subs#group_subscription{user_posts_count = SE+1})
  226. end,
  227. self() ! {feed_refresh,Feed,20},
  228. {noreply, State};
  229. handle_notice(["feed", "user", FeedOwner, "entry", EntryId, "add"] = Route,
  230. [From|_] = Message,
  231. #state{owner = WorkerOwner, feed = Feed, direct = Direct} = State) ->
  232. ?INFO("feed(~p): message: Owner=~p, Route=~p, Message=~p",
  233. [self(), WorkerOwner, Route, Message]),
  234. [From, Destinations, Desc, Medias] = Message,
  235. if
  236. %% user added message to own feed
  237. FeedOwner == From andalso FeedOwner == WorkerOwner->
  238. FilteredDst = [D || {_, group} = D <- Destinations],
  239. feed:add_entry(Feed, From, FilteredDst, EntryId, Desc, Medias,
  240. {user, normal}), self() ! {feed_refresh,Feed,20};
  241. %% friend added message to public feed
  242. FeedOwner == From ->
  243. feed:add_entry(Feed, From, [], EntryId, Desc, Medias,
  244. {user, normal}), self() ! {feed_refresh,Feed,20};
  245. %% direct message to worker owner
  246. FeedOwner == WorkerOwner ->
  247. feed:add_direct_message(Direct, From, [{FeedOwner, user}],
  248. EntryId, Desc, Medias), self() ! {direct_refresh,Direct,20};
  249. %% user sent direct message to friend, add copy to his direct feed
  250. From == WorkerOwner ->
  251. feed:add_direct_message(Direct, WorkerOwner, Destinations,
  252. EntryId, Desc, Medias), self() ! {direct_refresh,Direct,20};
  253. true ->
  254. ?INFO("not matched case in entry->add")
  255. end,
  256. {noreply, State};
  257. % add/delete system message
  258. handle_notice(["feed", "user", _FeedOwner, "entry", EntryId, "add_system"] = Route,
  259. [From|_] = Message,
  260. #state{owner = WorkerOwner, feed = Feed, direct = _Direct} = State) ->
  261. ?INFO("feed(~p): system message: Owner=~p, Route=~p, Message=~p",
  262. [self(), WorkerOwner, Route, Message]),
  263. [From, _Destinations, Desc, Medias] = Message,
  264. feed:add_entry(Feed, From, [], EntryId, Desc, Medias, {user, system}),
  265. {noreply, State};
  266. handle_notice(["feed", "group", GroupId, "entry", EntryId, "add_system"] = Route,
  267. [From|_] = Message,
  268. #state{owner = Owner, feed = Feed} = State) ->
  269. ?INFO("feed(~p): group system message: Owner=~p, Route=~p, Message=~p",
  270. [self(), Owner, Route, Message]),
  271. [From, _Destinations, Desc, Medias] = Message,
  272. feed:add_group_entry(Feed, From, [{GroupId, group}], EntryId,
  273. Desc, Medias, {group, system}),
  274. {noreply, State};
  275. handle_notice(["feed", "user", UId, "post_note"] = Route, Message,
  276. #state{owner = Owner, feed = Feed} = State) ->
  277. ?INFO("feed(~p): post_note: Owner=~p, Route=~p, Message=~p", [self(), Owner, Route, Message]),
  278. Note = Message,
  279. Id = utils:uuid_ex(),
  280. feed:add_entry(Feed, UId, [], Id, Note, [], {user, system_note}),
  281. {noreply, State};
  282. handle_notice(["feed", _, WhoShares, "entry", NewEntryId, "share"],
  283. #entry{entry_id = _EntryId, raw_description = Desc, media = Medias,
  284. to = Destinations, from = From} = E,
  285. #state{feed = Feed, type = user} = State) ->
  286. %% FIXME: sharing is like posting to the wall
  287. ?INFO("share: ~p, WhoShares: ~p", [E, WhoShares]),
  288. % NewEntryId = utils:uuid_ex(),
  289. feed:add_shared_entry(Feed, From, Destinations, NewEntryId, Desc, Medias, {user, normal}, WhoShares),
  290. {noreply, State};
  291. handle_notice(["feed", "group", _Group, "entry", EntryId, "delete"] = Route,
  292. Message,
  293. #state{owner = Owner, feed = Feed} = State) ->
  294. ?INFO("feed(~p): remove entry: Owner=~p, Route=~p, Message=~p",
  295. [self(), Owner, Route, Message]),
  296. %% all group subscribers shold delete entry from their feeds
  297. feed:remove_entry(Feed, EntryId),
  298. self() ! {feed_refresh,Feed,20},
  299. {noreply, State};
  300. handle_notice(["feed", _Type, EntryOwner, "entry", EntryId, "delete"] = Route,
  301. Message,
  302. #state{owner = Owner, feed=Feed, direct=Direct} = State) ->
  303. case {EntryOwner, Message} of
  304. %% owner of the antry has deleted entry, we will delete it too
  305. {_, [EntryOwner|_]} ->
  306. ?INFO("feed(~p): remove entry: Owner=~p, Route=~p, Message=~p",
  307. [self(), Owner, Route, Message]),
  308. feeds:remove_entry(Feed, EntryId),
  309. feeds:remove_entry(Direct, EntryId);
  310. %% we are owner of the entry - delete it
  311. {Owner, _} ->
  312. ?INFO("feed(~p): remove entry: Owner=~p, Route=~p, Message=~p",
  313. [self(), Owner, Route, Message]),
  314. feeds:remove_entry(Feed, EntryId),
  315. feeds:remove_entry(Direct, EntryId);
  316. %% one of the friends has deleted some entry from his feed. Ignore
  317. _ ->
  318. ok
  319. end,
  320. self() ! {feed_refresh, State#state.feed,20},
  321. {noreply, State};
  322. handle_notice(["feed", _Type, _EntryOwner, "entry", EntryId, "edit"] = Route,
  323. Message,
  324. #state{owner = Owner, feed=Feed} = State) ->
  325. [NewDescription|_] = Message,
  326. ?INFO("feed(~p): edit: Owner=~p, Route=~p, Message=~p",
  327. [self(), Owner, Route, Message]),
  328. %% edit entry in all feeds
  329. feeds:edit_entry(Feed, EntryId, NewDescription),
  330. {noreply, State};
  331. handle_notice(["feed", _Type, _EntryOwner, "comment", CommentId, "add"] = Route,
  332. Message,
  333. #state{owner = Owner, feed=Feed} = State) ->
  334. [From, EntryId, ParentComment, Content, Medias] = Message,
  335. ?INFO("feed(~p): add comment: Owner=~p, Route=~p, Message=~p",
  336. [self(), Owner, Route, Message]),
  337. feeds:entry_add_comment(Feed, From, EntryId, ParentComment, CommentId, Content, Medias),
  338. {noreply, State};
  339. handle_notice(["feed", "user", UId, "count_entry_in_statistics"] = Route,
  340. Message, #state{owner = Owner, type =Type} = State) ->
  341. ?INFO("queue_action(~p): count_entry_in_statistics: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),
  342. case kvs:get(user_etries_count, UId) of
  343. {ok, UEC} ->
  344. kvs:put(UEC#user_etries_count{
  345. entries = UEC#user_etries_count.entries+1
  346. }),
  347. kvs_users:attempt_active_user_top(UId, UEC#user_etries_count.entries+1);
  348. {error, notfound} ->
  349. kvs:put(#user_etries_count{
  350. user_id = UId,
  351. entries = 1
  352. }),
  353. kvs_users:attempt_active_user_top(UId, 1)
  354. end,
  355. {noreply, State};
  356. handle_notice(["feed", "user", UId, "count_comment_in_statistics"] = Route,
  357. Message, #state{owner = Owner, type =Type} = State) ->
  358. ?INFO("queue_action(~p): count_comment_in_statistics: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),
  359. case kvs:get(user_etries_count, UId) of
  360. {ok, UEC} ->
  361. kvs:put(UEC#user_etries_count{
  362. comments = UEC#user_etries_count.comments+1
  363. });
  364. {error, notfound} ->
  365. kvs:put(#user_etries_count{
  366. user_id = UId,
  367. comments = 1
  368. })
  369. end,
  370. {noreply, State};
  371. handle_notice(["likes", _, _, "add_like"] = Route, % _, _ is here beacause of the same message used for comet update
  372. Message, #state{owner = Owner, type =Type} = State) ->
  373. ?INFO("queue_action(~p): add_like: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),
  374. {UId, E} = Message,
  375. {EId, FId} = E#entry.id,
  376. feed:add_like(FId, EId, UId),
  377. {noreply, State};
  378. handle_notice(Route, Message, State) -> error_logger:info_msg("Unknown FEEDS notice").