1/* Copyright 2016 Software Freedom Conservancy Inc.
2 *
3 * This software is licensed under the GNU LGPL (version 2.1 or later).
4 * See the COPYING file in this distribution.
5 */
6
7public uint int64_hash(int64? n) {
8    // Rotating XOR hash
9    uint8 *u8 = (uint8 *) n;
10    uint hash = 0;
11    for (int ctr = 0; ctr < (sizeof(int64) / sizeof(uint8)); ctr++) {
12        hash = (hash << 4) ^ (hash >> 28) ^ (*u8++);
13    }
14
15    return hash;
16}
17
18public bool int64_equal(int64? a, int64? b) {
19    int64 *bia = (int64 *) a;
20    int64 *bib = (int64 *) b;
21
22    return (*bia) == (*bib);
23}
24
25public int int64_compare(int64? a, int64? b) {
26    int64 diff = *((int64 *) a) - *((int64 *) b);
27    if (diff < 0)
28        return -1;
29    else if (diff > 0)
30        return 1;
31    else
32        return 0;
33}
34
35public int uint64_compare(uint64? a, uint64? b) {
36    uint64 a64 = *((uint64 *) a);
37    uint64 b64 = *((uint64 *) b);
38
39    if (a64 < b64)
40        return -1;
41    else if (a64 > b64)
42        return 1;
43    else
44        return 0;
45}
46
47public delegate bool ValueEqualFunc(Value a, Value b);
48
49public bool bool_value_equals(Value a, Value b) {
50    return (bool) a == (bool) b;
51}
52
53public bool int_value_equals(Value a, Value b) {
54    return (int) a == (int) b;
55}
56
57public ulong timeval_to_ms(TimeVal time_val) {
58    return (((ulong) time_val.tv_sec) * 1000) + (((ulong) time_val.tv_usec) / 1000);
59}
60
61public ulong now_ms() {
62    return timeval_to_ms(TimeVal());
63}
64
65public ulong now_sec() {
66    TimeVal time_val = TimeVal();
67
68    return time_val.tv_sec;
69}
70
71public inline time_t now_time_t() {
72    return (time_t) now_sec();
73}
74
75public string md5_file(File file) throws Error {
76    Checksum md5 = new Checksum(ChecksumType.MD5);
77    uint8[] buffer = new uint8[64 * 1024];
78
79    FileInputStream fins = file.read(null);
80    for (;;) {
81        size_t bytes_read = fins.read(buffer, null);
82        if (bytes_read <= 0)
83            break;
84
85        md5.update((uchar[]) buffer, bytes_read);
86    }
87
88    try {
89        fins.close(null);
90    } catch (Error err) {
91        warning("Unable to close MD5 input stream for %s: %s", file.get_path(), err.message);
92    }
93
94    return md5.get_string();
95}
96
97// Once generic functions are available in Vala, this could be genericized.
98public bool equal_sets(Gee.Set<string>? a, Gee.Set<string>? b) {
99    if ((a != null && a.size == 0) && (b == null))
100        return true;
101
102    if ((a == null) && (b != null && b.size == 0))
103        return true;
104
105    if ((a == null && b != null) || (a != null && b == null))
106        return false;
107
108    if (a == null && b == null)
109        return true;
110
111    if (a.size != b.size)
112        return false;
113
114    // because they're sets and the same size, only need to iterate over one set to know
115    // it is equal to the other
116    foreach (string element in a) {
117        if (!b.contains(element))
118            return false;
119    }
120
121    return true;
122}
123
124// Once generic functions are available in Vala, this could be genericized.
125public Gee.Set<string>? intersection_of_sets(Gee.Set<string>? a, Gee.Set<string>? b,
126    Gee.Set<string>? excluded) {
127    if (a != null && b == null) {
128        if (excluded != null)
129            excluded.add_all(a);
130
131        return null;
132    }
133
134    if (a == null && b != null) {
135        if (excluded != null)
136            excluded.add_all(b);
137
138        return null;
139    }
140
141    Gee.Set<string> intersection = new Gee.HashSet<string>();
142
143    foreach (string element in a) {
144        if (b.contains(element))
145            intersection.add(element);
146        else if (excluded != null)
147            excluded.add(element);
148    }
149
150    foreach (string element in b) {
151        if (a.contains(element))
152            intersection.add(element);
153        else if (excluded != null)
154            excluded.add(element);
155    }
156
157    return intersection.size > 0 ? intersection : null;
158}
159
160public uchar[] serialize_photo_ids(Gee.Collection<Photo> photos) {
161    int64[] ids = new int64[photos.size];
162    int ctr = 0;
163    foreach (Photo photo in photos)
164        ids[ctr++] = photo.get_photo_id().id;
165
166    size_t bytes = photos.size * sizeof(int64);
167    uchar[] serialized = new uchar[bytes];
168    Memory.copy(serialized, ids, bytes);
169
170    return serialized;
171}
172
173public Gee.List<PhotoID?>? unserialize_photo_ids(uchar* serialized, int size) {
174    size_t count = (size / sizeof(int64));
175    if (count <= 0 || serialized == null)
176        return null;
177
178    int64[] ids = new int64[count];
179    Memory.copy(ids, serialized, size);
180
181    Gee.ArrayList<PhotoID?> list = new Gee.ArrayList<PhotoID?>();
182    foreach (int64 id in ids)
183        list.add(PhotoID(id));
184
185    return list;
186}
187
188public uchar[] serialize_media_sources(Gee.Collection<MediaSource> media) {
189    Gdk.Atom[] atoms = new Gdk.Atom[media.size];
190    int ctr = 0;
191    foreach (MediaSource current_media in media)
192        atoms[ctr++] = Gdk.Atom.intern(current_media.get_source_id(), false);
193
194    size_t bytes = media.size * sizeof(Gdk.Atom);
195    uchar[] serialized = new uchar[bytes];
196    Memory.copy(serialized, atoms, bytes);
197
198    return serialized;
199}
200
201public Gee.List<MediaSource>? unserialize_media_sources(uchar* serialized, int size) {
202    size_t count = (size / sizeof(Gdk.Atom));
203    if (count <= 0 || serialized == null)
204        return null;
205
206    Gdk.Atom[] atoms = new Gdk.Atom[count];
207    Memory.copy(atoms, serialized, size);
208
209    Gee.ArrayList<MediaSource> list = new Gee.ArrayList<MediaSource>();
210    foreach (Gdk.Atom current_atom in atoms) {
211        MediaSource media = MediaCollectionRegistry.get_instance().fetch_media(current_atom.name());
212        assert(media != null);
213        list.add(media);
214    }
215
216    return list;
217}
218
219public string format_local_datespan(Time from_date, Time to_date) {
220    string from_format, to_format;
221
222    // Ticket #3240 - Change the way date ranges are pretty-
223    // printed if the start and end date occur on consecutive days.
224    if (from_date.year == to_date.year) {
225        // are these consecutive dates?
226        if ((from_date.month == to_date.month) && (from_date.day == (to_date.day - 1))) {
227            // Yes; display like so: Sat, July 4 - 5, 20X6
228            from_format =  Resources.get_start_multiday_span_format_string();
229            to_format = Resources.get_end_multiday_span_format_string();
230        } else {
231            // No, but they're in the same year; display in shortened
232            // form: Sat, July 4 - Mon, July 6, 20X6
233            from_format = Resources.get_start_multimonth_span_format_string();
234            to_format = Resources.get_end_multimonth_span_format_string();
235        }
236    } else {
237        // Span crosses a year boundary, use long form dates
238        // for both start and end date.
239        from_format = Resources.get_long_date_format_string();
240        to_format = Resources.get_long_date_format_string();
241    }
242
243    return String.strip_leading_zeroes("%s - %s".printf(from_date.format(from_format),
244        to_date.format(to_format)));
245}
246
247public string format_local_date(Time date) {
248    return String.strip_leading_zeroes(date.format(Resources.get_long_date_format_string()));
249}
250
251public delegate void OneShotCallback();
252
253public class OneShotScheduler {
254    private string name;
255    private unowned OneShotCallback callback;
256    private uint scheduled = 0;
257
258    public OneShotScheduler(string name, OneShotCallback callback) {
259        this.name = name;
260        this.callback = callback;
261    }
262
263    ~OneShotScheduler() {
264#if TRACE_DTORS
265        debug("DTOR: OneShotScheduler for %s", name);
266#endif
267
268        cancel();
269    }
270
271    public bool is_scheduled() {
272        return scheduled != 0;
273    }
274
275    public void at_idle() {
276        at_priority_idle(Priority.DEFAULT_IDLE);
277    }
278
279    public void at_priority_idle(int priority) {
280        if (scheduled == 0)
281            scheduled = Idle.add_full(priority, callback_wrapper);
282    }
283
284    public void after_timeout(uint msec, bool reschedule) {
285        priority_after_timeout(Priority.DEFAULT, msec, reschedule);
286    }
287
288    public void priority_after_timeout(int priority, uint msec, bool reschedule) {
289        if (scheduled != 0 && !reschedule)
290            return;
291
292        if (scheduled != 0)
293            Source.remove(scheduled);
294
295        scheduled = Timeout.add_full(priority, msec, callback_wrapper);
296    }
297
298    public void cancel() {
299        if (scheduled == 0)
300            return;
301
302        Source.remove(scheduled);
303        scheduled = 0;
304    }
305
306    private bool callback_wrapper() {
307        scheduled = 0;
308        callback();
309
310        return false;
311    }
312}
313
314public class OpTimer {
315    private string name;
316    private Timer timer = new Timer();
317    private long count = 0;
318    private double elapsed = 0;
319    private double shortest = double.MAX;
320    private double longest = double.MIN;
321
322    public OpTimer(string name) {
323        this.name = name;
324    }
325
326    public void start() {
327        timer.start();
328    }
329
330    public void stop() {
331        double time = timer.elapsed();
332
333        elapsed += time;
334
335        if (time < shortest)
336            shortest = time;
337
338        if (time > longest)
339            longest = time;
340
341        count++;
342    }
343
344    public string to_string() {
345        if (count > 0) {
346            return "%s: count=%ld elapsed=%.03lfs min/avg/max=%.03lf/%.03lf/%.03lf".printf(name,
347                count, elapsed, shortest, elapsed / (double) count, longest);
348        } else {
349            return "%s: no operations".printf(name);
350        }
351    }
352}
353
354