1:- module(bc_analytics, [
2 bc_analytics_record_pixel/1, 3 bc_analytics_record_user/2, 4 bc_analytics_record_session/2, 5 bc_analytics_record_pageview/2, 6 bc_analytics_record_pageview_extend/1, 7 bc_enable_analytics/1, 8 bc_analytics_flush_output/0,
9 bc_month_file_name/3 10]).
14:- use_module(library(url)). 15:- use_module(library(debug)). 16:- use_module(library(error)). 17:- use_module(library(docstore)). 18
19:- dynamic(enabled/0). 20:- dynamic(path/1). 21:- dynamic(stream/1). 22:- dynamic(open_month/2). 23
25
26bc_analytics_record_pixel(Data):-
27 Data.user_id = null, !.
28
29bc_analytics_record_pixel(Data):-
30 integer_timestamp(TimeStamp),
31 record_entry(Data.put(timestamp, TimeStamp)).
32
35
36bc_analytics_record_user(UserData, UserId):-
37 must_be(dict, UserData),
38 ds_uuid(UserId),
39 integer_timestamp(TimeStamp),
40 record_entry(UserData.put(_{
41 user_id: UserId,
42 timestamp: TimeStamp})).
43
46
47bc_analytics_record_session(SessionData, SessionId):-
48 must_be(dict, SessionData),
49 ds_uuid(SessionId),
50 integer_timestamp(TimeStamp),
51 record_entry(SessionData.put(_{
52 session_id: SessionId,
53 timestamp: TimeStamp})).
54
55bc_analytics_record_pageview(PageviewData, PageviewId):-
56 must_be(dict, PageviewData),
57 ( get_dict(pageview_id, PageviewData, PageviewId)
58 -> true
59 ; ds_uuid(PageviewId)),
60 parse_url(PageviewData.location, UrlParts),
61 memberchk(path(Path), UrlParts),
62 integer_timestamp(TimeStamp),
63 record_entry(PageviewData.put(_{
64 location: Path,
65 pageview_id: PageviewId,
66 timestamp: TimeStamp})).
67
68bc_analytics_record_pageview_extend(PageviewData):-
69 record_entry(PageviewData).
70
73
74integer_timestamp(TimeStamp):-
75 get_time(Time),
76 TimeStamp is round(Time).
77
78record_entry(Entry):-
79 with_mutex(serialized_write, record_entry_unsafe(Entry)).
80
81record_entry_unsafe(_):-
82 \+ enabled, !.
83
84record_entry_unsafe(Entry):-
85 output_stream(Stream),
86 write_canonical_term(Stream, Entry).
87
88output_stream(Stream):-
89 ( open_month(Year, Month)
90 -> ( current_month(Year, Month)
91 -> stream(Stream)
92 ; open_output_stream(Stream))
93 ; open_output_stream(Stream)).
94
97
98open_output_stream(Stream):-
99 ( stream(OldStream)
100 -> close(OldStream),
101 retractall(stream(_))
102 ; true),
103 current_month(Year, Month),
104 bc_month_file_name(Year, Month, File),
105 open(File, append, Stream, [encoding('utf8')]),
106 retractall(stream(Stream)),
107 retractall(open_month(_, _)),
108 asserta(stream(Stream)),
109 asserta(open_month(Year, Month)).
110
111bc_month_file_name(Year, Month, File):-
112 path(Base),
113 atomic_list_concat([Base, '/', Year, '-', Month, '.pl'], File).
114
116
117current_month(Year, Month):-
118 get_time(TimeStamp),
119 stamp_date_time(TimeStamp, DateTime, 'UTC'),
120 date_time_value(year, DateTime, Year),
121 date_time_value(month, DateTime, Month).
122
125
126write_canonical_term(Stream, Term):-
127 write_term(Stream, Term, [
128 ignore_ops,
129 quoted,
130 dotlists(true),
131 nl(true),
132 fullstop(true)
133 ]).
134
137
138bc_enable_analytics(_):-
139 enabled, !.
140
141bc_enable_analytics(Path):-
142 debug(bc_analytics, 'Enabling analytics with storage at ~w.', [Path]),
143 ( exists_directory(Path)
144 -> ( access_file(Path, write)
145 -> true
146 ; throw(error(analytics_directory_not_writable)))
147 ; throw(error(analytics_directory_not_exists))),
148 asserta(enabled),
149 asserta(path(Path)).
150
152
153bc_analytics_flush_output:-
154 ( stream(Stream)
155 -> flush_output(Stream)
156 ; true)
Generic visitor tracking analytics */