From 7bfb48ef84384ff0460f273ea5841fba628d2a46 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 26 Mar 2013 15:52:57 +0100 Subject: code restructure --- LICENSE | 2 +- Makefile | 20 +- invoice/footer-line.svg | 115 +++ invoice/logo.svg | 198 +++++ src/Makefile | 9 + src/admin.vala | 191 ----- src/audio.vala | 81 -- src/audio/Makefile | 9 + src/audio/audio-interface.vala | 23 + src/audio/audio.vala | 83 ++ src/audio/main.vala | 38 + src/config/Makefile | 9 + src/config/config-interface.vala | 26 + src/config/config.vala | 62 ++ src/config/main.vala | 40 + src/curses-ui/Makefile | 9 + src/curses-ui/clock.vala | 76 ++ src/curses-ui/curses-ui.vala | 83 ++ src/curses-ui/dialog.vala | 51 ++ src/curses-ui/logo.vala | 45 ++ src/curses-ui/main.vala | 49 ++ src/curses-ui/message_box.vala | 60 ++ src/curses-ui/numbers.vala | 113 +++ src/curses-ui/status.vala | 39 + src/database/Makefile | 9 + src/database/database.vala | 846 ++++++++++++++++++++ src/database/db-interface.vala | 148 ++++ src/database/main.vala | 44 ++ src/db.vala | 919 ---------------------- src/device.vala | 280 ------- src/main.vala | 131 --- src/pdf-invoice/Makefile | 9 +- src/pdf-invoice/main.vala | 36 + src/pdf-invoice/pdf-invoice-interface.vala | 54 ++ src/pdf-invoice/pdf-invoice.vala | 98 +-- src/pdf-invoice/test.vala | 47 ++ src/pgp/Makefile | 9 + src/pgp/main.vala | 53 ++ src/pgp/pgp-interface.vala | 21 + src/pgp/pgp.vala | 127 +++ src/scanner-session/Makefile | 9 + src/scanner-session/main.vala | 38 + src/scanner-session/scannersession-interface.vala | 25 + src/scanner-session/scannersession.vala | 164 ++++ src/scannersession.vala | 165 ---- src/serial-device/Makefile | 9 + src/serial-device/main.vala | 47 ++ src/serial-device/serial-device-interface.vala | 20 + src/serial-device/serial-device.vala | 281 +++++++ src/template.vala | 104 --- src/ui/clock.vala | 61 -- src/ui/curses-ui.vala | 68 -- src/ui/dialog.vala | 36 - src/ui/logo.vala | 30 - src/ui/message_box.vala | 45 -- src/ui/numbers.vala | 98 --- src/ui/status.vala | 24 - src/web.vala | 854 -------------------- src/web/Makefile | 9 + src/web/csv.vala | 75 ++ src/web/main.vala | 35 + src/web/template.vala | 104 +++ src/web/web.vala | 858 ++++++++++++++++++++ src/web/websession.vala | 142 ++++ src/websession.vala | 142 ---- 65 files changed, 4388 insertions(+), 3317 deletions(-) create mode 100644 invoice/footer-line.svg create mode 100644 invoice/logo.svg create mode 100644 src/Makefile delete mode 100644 src/admin.vala delete mode 100644 src/audio.vala create mode 100644 src/audio/Makefile create mode 100644 src/audio/audio-interface.vala create mode 100644 src/audio/audio.vala create mode 100644 src/audio/main.vala create mode 100644 src/config/Makefile create mode 100644 src/config/config-interface.vala create mode 100644 src/config/config.vala create mode 100644 src/config/main.vala create mode 100644 src/curses-ui/Makefile create mode 100644 src/curses-ui/clock.vala create mode 100644 src/curses-ui/curses-ui.vala create mode 100644 src/curses-ui/dialog.vala create mode 100644 src/curses-ui/logo.vala create mode 100644 src/curses-ui/main.vala create mode 100644 src/curses-ui/message_box.vala create mode 100644 src/curses-ui/numbers.vala create mode 100644 src/curses-ui/status.vala create mode 100644 src/database/Makefile create mode 100644 src/database/database.vala create mode 100644 src/database/db-interface.vala create mode 100644 src/database/main.vala delete mode 100644 src/db.vala delete mode 100644 src/device.vala delete mode 100644 src/main.vala create mode 100644 src/pdf-invoice/main.vala create mode 100644 src/pdf-invoice/pdf-invoice-interface.vala create mode 100644 src/pdf-invoice/test.vala create mode 100644 src/pgp/Makefile create mode 100644 src/pgp/main.vala create mode 100644 src/pgp/pgp-interface.vala create mode 100644 src/pgp/pgp.vala create mode 100644 src/scanner-session/Makefile create mode 100644 src/scanner-session/main.vala create mode 100644 src/scanner-session/scannersession-interface.vala create mode 100644 src/scanner-session/scannersession.vala delete mode 100644 src/scannersession.vala create mode 100644 src/serial-device/Makefile create mode 100644 src/serial-device/main.vala create mode 100644 src/serial-device/serial-device-interface.vala create mode 100644 src/serial-device/serial-device.vala delete mode 100644 src/template.vala delete mode 100644 src/ui/clock.vala delete mode 100644 src/ui/curses-ui.vala delete mode 100644 src/ui/dialog.vala delete mode 100644 src/ui/logo.vala delete mode 100644 src/ui/message_box.vala delete mode 100644 src/ui/numbers.vala delete mode 100644 src/ui/status.vala delete mode 100644 src/web.vala create mode 100644 src/web/Makefile create mode 100644 src/web/csv.vala create mode 100644 src/web/main.vala create mode 100644 src/web/template.vala create mode 100644 src/web/web.vala create mode 100644 src/web/websession.vala delete mode 100644 src/websession.vala diff --git a/LICENSE b/LICENSE index 934fff2..1f0b9a1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Files: * -Copyright: 2012, Sebastian Reichel +Copyright: 2012-2013, Sebastian Reichel License: ISC Files: templates/js/sorttable.js diff --git a/Makefile b/Makefile index 6836132..b80d992 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ -SRC=src/main.vala src/device.vala src/scannersession.vala src/db.vala src/audio.vala src/web.vala src/graph-data.vala src/template.vala src/websession.vala src/admin.vala src/price.vapi src/ui/*.vala -DEPS=--pkg posix --pkg linux --pkg libsoup-2.4 --pkg sqlite3 --pkg gee-1.0 --pkg gio-2.0 --pkg gstreamer-0.10 --pkg libarchive --pkg gpgme --pkg curses -X -lncursesw -FLAGS=-X -lgpgme -X -w --enable-experimental --thread --vapidir=vapi +all: + cd src && make all -barcode-scanner: $(SRC) - valac-0.16 --output $@ $(FLAGS) $(DEPS) $^ +clean: + cd src && make clean + +install: + cd src && make install shop.db: sql/tables.sql sql/views.sql sql/trigger.sql @for file in $^ ; do \ @@ -11,10 +13,4 @@ shop.db: sql/tables.sql sql/views.sql sql/trigger.sql sqlite3 shop.db < $$file; \ done -run: barcode-scanner - ./barcode-scanner /dev/ttyS0 - -clean: - @rm -f barcode-scanner src/*.c - -.PHONY: clean install +.PHONY: all clean install diff --git a/invoice/footer-line.svg b/invoice/footer-line.svg new file mode 100644 index 0000000..eafdffe --- /dev/null +++ b/invoice/footer-line.svg @@ -0,0 +1,115 @@ + + + +image/svg+xml + + + + + + + + + + + + + + \ No newline at end of file diff --git a/invoice/logo.svg b/invoice/logo.svg new file mode 100644 index 0000000..81e97a9 --- /dev/null +++ b/invoice/logo.svg @@ -0,0 +1,198 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..da0bf41 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,9 @@ +DAEMONS=audio config curses-ui database pdf-invoice pgp scanner-session serial-device web + +all: + @$(foreach dir,$(DAEMONS),cd $(dir) && make all && cd ..;) + +clean: + @$(foreach dir,$(DAEMONS),cd $(dir) && make clean && cd ..;) + +.PHONY: all clean diff --git a/src/admin.vala b/src/admin.vala deleted file mode 100644 index de123e1..0000000 --- a/src/admin.vala +++ /dev/null @@ -1,191 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public class CSVMemberFile { - private UserInfo[] members; - - public Gee.List missing_unblocked_members() { - var result = new Gee.ArrayList(); - var dbusers = db.get_member_ids(); - - foreach(var u in dbusers) { - bool found=false; - foreach(var m in members) { - if(u == m.id) { - found=true; - break; - } - } - - if(!found) { - if(!db.user_is_disabled(u)) - result.add(u); - } - } - - return result; - } - - private string[] csv_split(string line) { - return /;(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/.split(line); - } - - private string csv_value(string value) { - if(value[0] == '"' && value[value.length-1] == '"') - return value.substring(1,value.length-2); - else - return value; - } - - public CSVMemberFile(string data) { - foreach(var line in data.split("\n")) { - var linedata = csv_split(line); - if(linedata.length >= 9) { - var m = UserInfo(); - m.id = int.parse(csv_value(linedata[0])); - m.email = csv_value(linedata[1]); - m.firstname = csv_value(linedata[2]); - m.lastname = csv_value(linedata[3]); - m.street = csv_value(linedata[4]); - m.postcode = int.parse(csv_value(linedata[5])); - m.city = csv_value(linedata[6]); - m.gender = csv_value(linedata[7]) == "m" ? "masculinum" : csv_value(linedata[7]) == "w" ? "femininum" : "unknown"; - m.pgp = csv_value(linedata[8]); - if(csv_value(linedata[0]) != "EXTERNEMITGLIEDSNUMMER") - members += m; - } - } - } - - public UserInfo[] get_members() { - return members; - } -} - -public class PGPKeyArchive { - private string keyring; - private GPG.Context gpg; - - public PGPKeyArchive(KeyFile config) { - /* check version (important!) */ - GPG.check_version(); - - /* initialize default context */ - GPG.Context.Context(out gpg); - - try { - keyring = config.get_string("PGP", "keyring"); - - /* remove quotes */ - if(keyring.has_prefix("\"") && keyring.has_suffix("\"")) - keyring = keyring.substring(1,keyring.length-2); - } catch(KeyFileError e) { - write_to_log("KeyFileError: %s", e.message); - return; - } - - /* TODO: check existence of keyring */ - - /* set home directory */ - var info = gpg.get_engine_info(); - gpg.set_engine_info(info.protocol, info.file_name, keyring); - - /* enable ascii armor */ - gpg.set_armor(true); - } - - public string[] import_archive(uint8[] data) { - string[] result = {}; - unowned Archive.Entry entry; - var archive = new Archive.Read(); - - /* support all formats & compression types */ - archive.support_compression_all(); - archive.support_format_all(); - - /* load test archive for now */ - if(archive.open_memory(data, data.length) != Archive.Result.OK) - return result; - - while(archive.next_header(out entry) == Archive.Result.OK) { - var name = entry.pathname(); - var size = entry.size(); - var content = new uint8[size]; - - /* skip entries, which contain a slash */ - if(name.contains("/")) - continue; - - /* skip files, which are big (probably not a minimal pgp key) */ - if(size > 50000) - continue; - - if(archive.read_data((void*) content, (ssize_t) size) == size) { - if(!((string) content).has_prefix("-----BEGIN PGP PUBLIC KEY BLOCK-----")) - continue; - - /* put byte data into GPG.Data object */ - GPG.Data gpgdata; - GPG.Data.create_from_memory(out gpgdata, content, false); - - /* import keys */ - gpg.op_import(gpgdata); - - /* get result */ - unowned GPG.ImportResult importresult = gpg.op_import_result(); - - /* add imported fingerprints to result */ - for(unowned GPG.ImportStatus st = importresult.imports; st != null; st = st.next) { - if(!(st.fpr in result) && (st.status & GPG.ImportStatusFlags.NEW) != 0) - result += st.fpr; - } - } - } - - return result; - } - - public string[] list_keys() { - string[] result = {}; - GPG.Key key; - - gpg.op_keylist_start(); - - while(gpg.op_keylist_next(out key) == GPGError.ErrorCode.NO_ERROR) { - result += key.subkeys[0].fpr; - } - - gpg.op_keylist_end(); - - return result; - } - - public string get_key(string fingerprint) { - GPG.Data keydata; - GPG.Data.create(out keydata); - - if(gpg.op_export(fingerprint, 0, keydata) == GPGError.ErrorCode.NO_ERROR) { - long size = keydata.seek(0, Posix.FILE.SEEK_END); - keydata.seek(0, Posix.FILE.SEEK_SET); - stdout.printf("size: %ld\n", size); - uint8[] data = new uint8[size]; - keydata.read(data); - return (string) data; - } else { - stdout.printf("error!\n"); - return ""; - } - } -} diff --git a/src/audio.vala b/src/audio.vala deleted file mode 100644 index 3f22e6d..0000000 --- a/src/audio.vala +++ /dev/null @@ -1,81 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public class AudioPlayer { - private dynamic Gst.Element p; - string path; - - public signal void end_of_stream(); - - private bool bus_callback(Gst.Bus bus, Gst.Message message) { - switch (message.type) { - case Gst.MessageType.EOS: - end_of_stream(); - break; - } - - return true; - } - - public AudioPlayer() { - path = Environment.get_current_dir()+"/sounds/"; - - var alsa = Gst.ElementFactory.make("alsasink", "alsa"); - p = Gst.ElementFactory.make("playbin2", "play"); - p.set("audio-sink", alsa); - p.get_bus().add_watch(bus_callback); - } - - public void play_system(string file) { - p.set_state(Gst.State.NULL); - p.uri = "file://" + path + "system/"+file; - p.set_state(Gst.State.PLAYING); - } - - private string[] get_files(string dir) { - try { - var directory = File.new_for_path(dir); - var enumerator = directory.enumerate_children(FileAttribute.STANDARD_NAME, 0); - string[] result = {}; - - FileInfo file_info; - while ((file_info = enumerator.next_file ()) != null) { - result += file_info.get_name(); - } - - return result; - } catch (Error e) { - write_to_log("Error: %s\n", e.message); - return {}; - } - } - - private string get_random_file(string dir) { - var files = get_files(dir); - var index = Random.int_range(0, files.length); - return files[index]; - } - - public string get_random_user_theme() { - return get_random_file(path + "user/"); - } - - public void play_user(string theme, string type) { - p.set_state(Gst.State.NULL); - var file = get_random_file(path + "user/" + theme+ "/" + type); - p.uri = "file://" + path + "user/" + theme+ "/" + type + "/" + file; - p.set_state(Gst.State.PLAYING); - } -} diff --git a/src/audio/Makefile b/src/audio/Makefile new file mode 100644 index 0000000..a6fd629 --- /dev/null +++ b/src/audio/Makefile @@ -0,0 +1,9 @@ +all: audio + +audio: main.vala audio.vala audio-interface.vala + valac -o $@ --pkg gstreamer-0.10 --pkg gio-2.0 $^ + +clean: + rm -rf audio + +.PHONY: all clean diff --git a/src/audio/audio-interface.vala b/src/audio/audio-interface.vala new file mode 100644 index 0000000..89d4a6f --- /dev/null +++ b/src/audio/audio-interface.vala @@ -0,0 +1,23 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.AudioPlayer")] +public interface AudioPlayer : Object { + public abstract signal void end_of_stream(); + + public abstract void play_system(string file) throws IOError; + public abstract string get_random_user_theme() throws IOError; + public abstract void play_user(string theme, string type) throws IOError; +} diff --git a/src/audio/audio.vala b/src/audio/audio.vala new file mode 100644 index 0000000..0225c01 --- /dev/null +++ b/src/audio/audio.vala @@ -0,0 +1,83 @@ +/* Copyright 2012-2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.AudioPlayer")] +public class AudioPlayerImplementation { + private dynamic Gst.Element p; + string path; + + public signal void end_of_stream(); + + private bool bus_callback(Gst.Bus bus, Gst.Message message) { + switch(message.type) { + case Gst.MessageType.EOS: + end_of_stream(); + break; + } + + return true; + } + + public AudioPlayerImplementation() { + path = Environment.get_current_dir()+"/sounds/"; + + var alsa = Gst.ElementFactory.make("alsasink", "alsa"); + p = Gst.ElementFactory.make("playbin2", "play"); + p.set("audio-sink", alsa); + p.get_bus().add_watch(bus_callback); + } + + public void play_system(string file) { + p.set_state(Gst.State.NULL); + p.uri = "file://" + path + "system/"+file; + p.set_state(Gst.State.PLAYING); + } + + private string[] get_files(string dir) { + try { + var directory = File.new_for_path(dir); + var enumerator = directory.enumerate_children(FileAttribute.STANDARD_NAME, 0); + string[] result = {}; + + FileInfo file_info; + while ((file_info = enumerator.next_file ()) != null) { + result += file_info.get_name(); + } + + return result; + } catch (Error e) { + // TODO + //write_to_log("Error: %s\n", e.message); + return {}; + } + } + + private string get_random_file(string dir) { + var files = get_files(dir); + var index = Random.int_range(0, files.length); + return files[index]; + } + + public string get_random_user_theme() { + return get_random_file(path + "user/"); + } + + public void play_user(string theme, string type) { + p.set_state(Gst.State.NULL); + var file = get_random_file(path + "user/" + theme+ "/" + type); + p.uri = "file://" + path + "user/" + theme+ "/" + type + "/" + file; + p.set_state(Gst.State.PLAYING); + } +} diff --git a/src/audio/main.vala b/src/audio/main.vala new file mode 100644 index 0000000..f3f64f4 --- /dev/null +++ b/src/audio/main.vala @@ -0,0 +1,38 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +AudioPlayerImplementation player; + +public static int main(string[] args) { + Bus.own_name( + BusType.SESSION, + "io.mainframe.shopsystem.AudioPlayer", + BusNameOwnerFlags.NONE, + on_bus_aquired, + () => {}, + () => stderr.printf("Could not aquire name\n")); + + new MainLoop().run(); + + return 0; +} + +void on_bus_aquired(DBusConnection con) { + try { + con.register_object("/io/mainframe/shopsystem/audio", player); + } catch(IOError e) { + stderr.printf("Could not register service\n"); + } +} diff --git a/src/config/Makefile b/src/config/Makefile new file mode 100644 index 0000000..0e81ee9 --- /dev/null +++ b/src/config/Makefile @@ -0,0 +1,9 @@ +all: config + +config: main.vala config.vala config-interface.vala + valac -o $@ --pkg gio-2.0 $^ + +clean: + rm -rf config + +.PHONY: all clean diff --git a/src/config/config-interface.vala b/src/config/config-interface.vala new file mode 100644 index 0000000..c34e3a7 --- /dev/null +++ b/src/config/config-interface.vala @@ -0,0 +1,26 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.Config")] +public interface Config : Object { + public abstract bool has_group(string group_name) throws IOError, KeyFileError; + public abstract bool has_key(string group_name, string key) throws IOError, KeyFileError; + public abstract string get_string(string group_name, string key) throws IOError, KeyFileError; + public abstract bool get_boolean(string group_name, string key) throws IOError, KeyFileError; + public abstract int get_integer(string group_name, string key) throws IOError, KeyFileError; + public abstract int64 get_int64(string group_name, string key) throws IOError, KeyFileError; + public abstract uint64 get_uint64(string group_name, string key) throws IOError, KeyFileError; + public abstract double get_double(string group_name, string key) throws IOError, KeyFileError; +} diff --git a/src/config/config.vala b/src/config/config.vala new file mode 100644 index 0000000..60fd835 --- /dev/null +++ b/src/config/config.vala @@ -0,0 +1,62 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.Config")] +public class Cfg { + + private KeyFile file; + + public Cfg(string file) { + try { + this.file = new KeyFile(); + this.file.load_from_file(file, KeyFileFlags.NONE); + } catch(Error e) { + error("Could not load configuration file: %s", e.message); + } + } + + public bool has_group(string group_name) throws KeyFileError { + return file.has_group(group_name); + } + + public bool has_key(string group_name, string key) throws KeyFileError { + return file.has_key(group_name, key); + } + + public string get_string(string group_name, string key) throws KeyFileError { + return file.get_string(group_name, key); + } + + public bool get_boolean(string group_name, string key) throws KeyFileError { + return file.get_boolean(group_name, key); + } + + public int get_integer(string group_name, string key) throws KeyFileError { + return file.get_integer(group_name, key); + } + + public int64 get_int64(string group_name, string key) throws KeyFileError { + return file.get_int64(group_name, key); + } + + public uint64 get_uint64(string group_name, string key) throws KeyFileError { + return file.get_uint64(group_name, key); + } + + public double get_double(string group_name, string key) throws KeyFileError { + return file.get_double(group_name, key); + } + +} diff --git a/src/config/main.vala b/src/config/main.vala new file mode 100644 index 0000000..aa971d6 --- /dev/null +++ b/src/config/main.vala @@ -0,0 +1,40 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +Cfg cfg; + +public static int main(string[] args) { + cfg = new Cfg("../../ktt-shopsystem.cfg"); + + Bus.own_name( + BusType.SESSION, + "io.mainframe.shopsystem.Config", + BusNameOwnerFlags.NONE, + on_bus_aquired, + () => {}, + () => stderr.printf("Could not aquire name\n")); + + new MainLoop().run(); + + return 0; +} + +void on_bus_aquired(DBusConnection con) { + try { + con.register_object("/io/mainframe/shopsystem/config", cfg); + } catch(IOError e) { + stderr.printf("Could not register service\n"); + } +} diff --git a/src/curses-ui/Makefile b/src/curses-ui/Makefile new file mode 100644 index 0000000..5d168ba --- /dev/null +++ b/src/curses-ui/Makefile @@ -0,0 +1,9 @@ +all: curses-ui + +curses-ui: clock.vala curses-ui.vala dialog.vala logo.vala main.vala message_box.vala numbers.vala status.vala ../audio/audio-interface.vala + valac -o $@ --pkg curses -X -lncursesw --pkg posix --pkg gio-2.0 $^ + +clean: + rm -rf curses-ui + +.PHONY: all clean diff --git a/src/curses-ui/clock.vala b/src/curses-ui/clock.vala new file mode 100644 index 0000000..dd3ddcd --- /dev/null +++ b/src/curses-ui/clock.vala @@ -0,0 +1,76 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +using Curses; + +public class ClockWindow { + AsciiNumbers ascii; + Window win; + + public ClockWindow() { + ascii = new AsciiNumbers(); + win = new Window(6, 18, 1, COLS-2-18); + win.bkgdset(COLOR_PAIR(0) | Attribute.BOLD); + + win.clrtobot(); + win.box(0, 0); + + win.refresh(); + } + + public void update() { + string[] x; + var now = new DateTime.now_local(); + + x = ascii.get('0' + (char) (now.get_hour() / 10)); + win.mvaddstr(1,1, x[0]); + win.mvaddstr(2,1, x[1]); + win.mvaddstr(3,1, x[2]); + + x = ascii.get('0' + (char) (now.get_hour() % 10)); + win.mvaddstr(1,4, x[0]); + win.mvaddstr(2,4, x[1]); + win.mvaddstr(3,4, x[2]); + + x = ascii.get(':'); + win.mvaddstr(1,7, x[0]); + win.mvaddstr(2,7, x[1]); + win.mvaddstr(3,7, x[2]); + + x = ascii.get('0' + (char) (now.get_minute() / 10)); + win.mvaddstr(1,10, x[0]); + win.mvaddstr(2,10, x[1]); + win.mvaddstr(3,10, x[2]); + + x = ascii.get('0' + (char) (now.get_minute() % 10)); + win.mvaddstr(1,13, x[0]); + win.mvaddstr(2,13, x[1]); + win.mvaddstr(3,13, x[2]); + + + win.clrtobot(); + win.box(0, 0); + + win.mvaddstr(5,4, now.format("%Y-%m-%d")); + + win.refresh(); + } + + public void redraw() { + win.touchwin(); + win.refresh(); + } + +} diff --git a/src/curses-ui/curses-ui.vala b/src/curses-ui/curses-ui.vala new file mode 100644 index 0000000..ec007f2 --- /dev/null +++ b/src/curses-ui/curses-ui.vala @@ -0,0 +1,83 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public class CursesUI { + MessageBox messages; + Dialog dialog; + Logo banner; + ClockWindow clkwin; + StatusPanel statuswin; + + public CursesUI() { + /* unicode support */ + Intl.setlocale(LocaleCategory.CTYPE, ""); + + /* initialize curses */ + Curses.initscr(); + + /* disable cursor */ + Curses.curs_set(0); + + /* initialize color mode and define color pairs */ + Curses.start_color(); + Curses.init_pair(0, Curses.Color.WHITE, Curses.Color.BLACK); + Curses.init_pair(1, Curses.Color.GREEN, Curses.Color.BLACK); + Curses.init_pair(2, Curses.Color.WHITE, Curses.Color.RED); + + /* initialize widgets */ + banner = new Logo(); + statuswin = new StatusPanel(); + messages = new MessageBox(); + clkwin = new ClockWindow(); + + clkwin.update(); + + Timeout.add_seconds(10, update_time); + } + + ~CursesUI() { + exit(); + } + + public void exit() { + /* Reset the terminal mode */ + Curses.endwin(); + } + + bool update_time() { + clkwin.update(); + return true; + } + + public void status(string message) { + statuswin.set(message); + } + + public void log(string message) { + messages.add(message); + } + + public void dialog_open(string title, string message) { + dialog = new Dialog(message, title); + } + + public void dialog_close() { + dialog = null; + messages.redraw(); + banner.redraw(); + clkwin.redraw(); + statuswin.redraw(); + } +} diff --git a/src/curses-ui/dialog.vala b/src/curses-ui/dialog.vala new file mode 100644 index 0000000..a8585d4 --- /dev/null +++ b/src/curses-ui/dialog.vala @@ -0,0 +1,51 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +using Curses; + +public class Dialog { + Window win; + Window subwin; + + public Dialog(string message, string title = "KtT Shopsystem Error", int h=16, int w=60) + requires (title.length <= w-4) + { + int y = LINES/2-h/2; + int x = COLS/2-w/2; + + int title_x = (w-title.length)/2; + + win = new Window(h, w, y, x); + + /* make the dialog white on red */ + win.bkgdset(COLOR_PAIR(2) | Attribute.BOLD); + win.clrtobot(); + + /* message subwindow */ + subwin = win.derwin(h-4, w-4, 2, 2); + subwin.clrtobot(); + subwin.printw(message); + subwin.refresh(); + + /* dialog title */ + win.box(0,0); + win.mvaddstr(0, title_x, title); + win.mvaddch(0, title_x-2, Acs.RTEE); + win.mvaddch(0, title_x-1, ' '); + win.mvaddch(0, title_x+title.length, ' '); + win.mvaddch(0, title_x+title.length+1, Acs.LTEE); + win.refresh(); + } +} diff --git a/src/curses-ui/logo.vala b/src/curses-ui/logo.vala new file mode 100644 index 0000000..dbc716d --- /dev/null +++ b/src/curses-ui/logo.vala @@ -0,0 +1,45 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +using Curses; + +public class Logo { + Window win; + + public Logo() { + win = new Window(8, COLS - 2, 0, 1); + win.bkgdset(COLOR_PAIR(1) | Attribute.BOLD); + + win.addstr("\n"); + win.addstr(" _ ___ _____ ____ _ \n"); + win.addstr(" | |/ / ||_ _| / ___|| |__ ___ _ __ \n"); + win.addstr(" | ' /| __|| | \\___ \\| '_ \\ / _ \\| '_ \\ \n"); + win.addstr(" | . \\| |_ | | ___) | | | | (_) | |_) )\n"); + win.addstr(" |_|\\_\\\\__||_| |____/|_| |_|\\___/| .__/ \n"); + win.addstr(" |_| \n"); + + win.clrtobot(); + + win.box(0, 0); + + win.refresh(); + } + + public void redraw() { + win.touchwin(); + win.refresh(); + } + +} diff --git a/src/curses-ui/main.vala b/src/curses-ui/main.vala new file mode 100644 index 0000000..1d79c43 --- /dev/null +++ b/src/curses-ui/main.vala @@ -0,0 +1,49 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public MainLoop loop; + +public static int main(string[] args) { + /* handle unix signals */ + Unix.signal_add(Posix.SIGTERM, handle_signals); + Unix.signal_add(Posix.SIGINT, handle_signals); + + AudioPlayer audio = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.AudioPlayer", "/io/mainframe/shopsystem/audio"); + + var ui = new CursesUI(); + + ui.log("KtT Shop System has been started"); + audio.play_system("startup.ogg"); + + /* run mainloop */ + loop.run(); + + ui.log("Stopping Shop System"); + audio.play_system("shutdown.ogg"); + + /* we need to run the mainloop to play audio */ + audio.end_of_stream.connect(() => { loop.quit(); }); + loop.run(); + + /* leave curses mode */ + ui.exit(); + + return 0; +} + +bool handle_signals() { + loop.quit(); + return false; +} diff --git a/src/curses-ui/message_box.vala b/src/curses-ui/message_box.vala new file mode 100644 index 0000000..cc258b4 --- /dev/null +++ b/src/curses-ui/message_box.vala @@ -0,0 +1,60 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +using Curses; + +public class MessageBox { + Window win; + Window subwin; + DateTime last; + + public MessageBox() { + win = new Window(LINES-9, COLS - 2, 8, 1); + win.bkgdset(COLOR_PAIR(0)); + + win.clrtobot(); + win.box(0, 0); + win.refresh(); + + subwin = win.derwin(LINES-11, COLS-4, 1, 1); + subwin.scrollok(true); + subwin.clrtobot(); + subwin.refresh(); + + last = new DateTime.from_unix_utc(0); + } + + public void add(string msg) { + var now = new DateTime.now_local(); + + if(now.get_day_of_year() != last.get_day_of_year() || now.get_year() != last.get_year()) { + string curtime = now.format("%Y-%m-%d"); + subwin.addstr("\nDate Changed: " + curtime); + } + + last = now; + + string curtime = now.format("%H:%M:%S"); + subwin.addstr("\n[" + curtime + "] " + msg); + subwin.refresh(); + } + + public void redraw() { + win.touchwin(); + win.refresh(); + subwin.touchwin(); + subwin.refresh(); + } +} diff --git a/src/curses-ui/numbers.vala b/src/curses-ui/numbers.vala new file mode 100644 index 0000000..200cf63 --- /dev/null +++ b/src/curses-ui/numbers.vala @@ -0,0 +1,113 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public class AsciiNumbers { + + public string[] zero = { + " _ ", + "/ \\", + "\\_/" + }; + + public string[] one = { + " ", + " /|", + " |" + }; + + public string[] two = { + "__ ", + " _)", + "(__" + }; + + public string[] three = { + "__ ", + " _)", + "__)" + }; + + public string[] four = { + " ", + "|_|", + " |" + }; + + public string[] five = { + " __", + "|_ ", + "__)" + }; + + public string[] six = { + " _ ", + "/_ ", + "\\_)" + }; + + public string[] seven = { + "___", + " /", + " / " + }; + + public string[] eight = { + " _ ", + "(_)", + "(_)" + }; + + public string[] nine = { + " _ ", + "(_\\", + " _/" + }; + + public string[] colon = { + " ", + " o ", + " o " + }; + + public string[] get(char c) { + switch(c) { + case '0': + return zero; + case '1': + return one; + case '2': + return two; + case '3': + return three; + case '4': + return four; + case '5': + return five; + case '6': + return six; + case '7': + return seven; + case '8': + return eight; + case '9': + return nine; + case ':': + return colon; + default: + return {}; + } + } + +} diff --git a/src/curses-ui/status.vala b/src/curses-ui/status.vala new file mode 100644 index 0000000..5be6a00 --- /dev/null +++ b/src/curses-ui/status.vala @@ -0,0 +1,39 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +using Curses; + +public class StatusPanel { + Window win; + + public StatusPanel() { + win = new Window(1, COLS - 2, LINES-1, 1); + win.bkgdset(COLOR_PAIR(2) | Attribute.BOLD); + + win.clrtobot(); + win.refresh(); + } + + public void set(string msg) { + win.mvaddstr(0,1, msg); + win.clrtobot(); + win.refresh(); + } + + public void redraw() { + win.touchwin(); + win.refresh(); + } +} diff --git a/src/database/Makefile b/src/database/Makefile new file mode 100644 index 0000000..70253d2 --- /dev/null +++ b/src/database/Makefile @@ -0,0 +1,9 @@ +all: db + +db: main.vala database.vala db-interface.vala ../price.vapi + valac -o $@ --pkg sqlite3 --pkg gee-1.0 --pkg gio-2.0 $^ + +clean: + rm -rf db + +.PHONY: all clean diff --git a/src/database/database.vala b/src/database/database.vala new file mode 100644 index 0000000..640e175 --- /dev/null +++ b/src/database/database.vala @@ -0,0 +1,846 @@ +/* Copyright 2012-2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public const int day_in_seconds = 24*60*60; + +[DBus (name = "io.mainframe.shopsystem.Database")] +public class DataBase : Object { + private class Statement { + private Sqlite.Statement stmt; + + public Statement(Sqlite.Database db, string query) { + int rc = db.prepare_v2(query, -1, out stmt); + + if(rc != Sqlite.OK) { + error("could not prepare statement: %s", query); + } + } + + public void reset() { + stmt.reset(); + } + + public int step() { + return stmt.step(); + } + + public int bind_null(int index) { + return stmt.bind_null(index); + } + + public int bind_int(int index, int value) { + return stmt.bind_int(index, value); + } + + public int bind_text(int index, string value) { + return stmt.bind_text(index, value); + } + + public int bind_int64(int index, int64 value) { + return stmt.bind_int64(index, value); + } + + public int column_int(int index) { + return stmt.column_int(index); + } + + public string column_text(int index) { + var result = stmt.column_text(index); + return (result != null) ? result : ""; + } + + public int64 column_int64(int index) { + return stmt.column_int64(index); + } + } + + private Sqlite.Database db; + private static Gee.HashMap queries = new Gee.HashMap(); + private static Gee.HashMap statements = new Gee.HashMap(); + //private static HashTable queries = new HashTable(null, null); + //private static HashTable statements = new HashTable(null, null); + + public DataBase(string file) { + int rc; + + rc = Sqlite.Database.open(file, out db); + if(rc != Sqlite.OK) { + error("could not open database!"); + } + + /* setup queries */ + queries["product_name"] = "SELECT name FROM products WHERE id = ?"; + queries["product_amount"] = "SELECT amount FROM products WHERE id = ?"; + queries["products"] = "SELECT id, name, amount FROM products ORDER BY name"; + queries["purchase"] = "INSERT INTO sales ('user', 'product', 'timestamp') VALUES (?, ?, ?)"; + queries["last_purchase"] = "SELECT product FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; + queries["undo"] = "DELETE FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; + queries["product_create"] = "INSERT INTO products ('id', 'name', 'amount') VALUES (?, ?, ?)"; + queries["price_create"] = "INSERT INTO prices ('product', 'valid_from', 'memberprice', 'guestprice') VALUES (?, ?, ?, ?)"; + queries["stock"] = "INSERT INTO restock ('user', 'product', 'amount', 'price', 'timestamp', 'supplier', 'best_before_date') VALUES (?, ?, ?, ?, ?, ?, ?)"; + queries["price"] = "SELECT memberprice, guestprice FROM prices WHERE product = ? AND valid_from <= ? ORDER BY valid_from DESC LIMIT 1"; + queries["prices"] = "SELECT valid_from, memberprice, guestprice FROM prices WHERE product = ? ORDER BY valid_from ASC;"; + queries["restocks"] = "SELECT timestamp, amount, price, supplier, best_before_date FROM restock WHERE product = ? ORDER BY timestamp ASC;"; + queries["profit_complex"] = "SELECT SUM(memberprice - (SELECT price FROM purchaseprices WHERE product = purch.product)) FROM sales purch, prices WHERE purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1);"; + queries["sales_complex"] = "SELECT SUM(memberprice) FROM sales purch, prices WHERE purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1);"; + queries["stock_status"] = "SELECT id, name, amount, memberprice, guestprice FROM products, prices WHERE products.id = prices.product AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = products.id ORDER BY valid_from DESC LIMIT 1) ORDER BY name"; + queries["stock_amount"] = "SELECT timestamp, amount FROM restock WHERE product = ? UNION ALL SELECT timestamp, -1 AS amount FROM sales WHERE product = ? ORDER BY timestamp DESC"; + queries["session_set"] = "UPDATE authentication SET session=? WHERE user = ?"; + queries["session_get"] = "SELECT user FROM authentication WHERE session = ?"; + queries["username"] = "SELECT firstname, lastname FROM users WHERE id = ?"; + queries["password_get"] = "SELECT password FROM authentication WHERE user = ?"; + queries["password_set"] = "UPDATE authentication SET password=? WHERE user = ?"; + queries["userinfo"] = "SELECT firstname, lastname, email, gender, street, plz, city, pgp FROM users WHERE id = ?"; + queries["userauth"] = "SELECT disabled, superuser FROM authentication WHERE user = ?"; + queries["profit_by_product"] = "SELECT name, SUM(memberprice - (SELECT price FROM purchaseprices WHERE product = purch.product)) AS price FROM sales purch, prices, products WHERE purch.product = products.id AND purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1) GROUP BY name ORDER BY price;"; + queries["invoice"] = "SELECT timestamp, productid, productname, price FROM invoice WHERE user = ? AND timestamp >= ? AND timestamp < ?;"; + queries["purchase_first"] = "SELECT timestamp FROM sales WHERE user = ? ORDER BY timestamp ASC LIMIT 1"; + queries["purchase_last"] = "SELECT timestamp FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; + queries["count_articles"] = "SELECT COUNT(*) FROM products"; + queries["count_users"] = "SELECT COUNT(*) FROM users"; + queries["stock_value"] = "SELECT SUM(amount * price) FROM products INNER JOIN purchaseprices ON products.id = purchaseprices.product"; + queries["total_sales"] = "SELECT SUM(price) FROM invoice WHERE user >= 0 AND timestamp >= ?"; + queries["total_profit"] = "SELECT SUM(price - (SELECT price FROM purchaseprices WHERE product = productid)) FROM invoice WHERE user >= 0 AND timestamp >= ?"; + queries["user_get_ids"] = "SELECT id FROM users WHERE id > 0"; + queries["user_replace"] = "INSERT OR REPLACE INTO users ('id', 'email', 'firstname', 'lastname', 'gender', 'street', 'plz', 'city', 'pgp') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + queries["user_auth_create"] = "INSERT OR IGNORE INTO authentication (user) VALUES (?)"; + queries["user_disable"] = "UPDATE authentication SET disabled = ? WHERE user = ?"; + queries["last_timestamp"] = "SELECT timestamp FROM sales ORDER BY timestamp DESC LIMIT 1"; + queries["supplier_list"] = "SELECT id, name, postal_code, city, street, phone, website FROM supplier"; + queries["supplier_get"] = "SELECT id, name, postal_code, city, street, phone, website FROM supplier WHERE id = ?"; + queries["supplier_add"] = "INSERT INTO supplier('name', 'postal_code', 'city', 'street', 'phone', 'website') VALUES (?, ?, ?, ?, ?, ?)"; + + /* compile queries into statements */ + foreach(var entry in queries.entries) { + statements[entry.key] = new Statement(db, entry.value); + } +#if 0 + foreach(var key in queries.get_keys()) { + statements[key] = new Statement(db, queries[key]); + } +#endif + } + + public GLib.HashTable get_products() { + var result = new GLib.HashTable(null, null); + statements["products"].reset(); + + while(statements["products"].step() == Sqlite.ROW) + result[statements["products"].column_text(0)] = statements["products"].column_text(1); + + return result; + } + +#if 0 + public stock get_stats_stock() { + var result = new stock(); + var now = time_t(); + + /* init products */ + statements["products"].reset(); + while(statements["products"].step() == Sqlite.ROW) { + var id = uint64.parse(statements["products"].column_text(0)); + var name = statements["products"].column_text(1); + int amount = int.parse(statements["products"].column_text(2)); + var product = new stock.product(id, name); + result.add(product); + product.add(now, amount); + + statements["stock_amount"].reset(); + statements["stock_amount"].bind_text(1, "%llu".printf(id)); + statements["stock_amount"].bind_text(2, "%llu".printf(id)); + + while(statements["stock_amount"].step() == Sqlite.ROW) { + var timestamp = uint64.parse(statements["stock_amount"].column_text(0)); + var diff = statements["stock_amount"].column_int(1); + product.add(timestamp+1, amount); + amount -= diff; + product.add(timestamp, amount); + } + } + + return result; + } +#endif + +#if 0 + public profit_per_product get_stats_profit_per_products() { + var result = new profit_per_product(); + + statements["profit_by_product"].reset(); + statements["profit_by_product"].bind_int(1, 0); + statements["profit_by_product"].bind_text(2, "99999999999999"); + + while(statements["profit_by_product"].step() == Sqlite.ROW) { + var name = statements["profit_by_product"].column_text(0); + var profit = statements["profit_by_product"].column_int(1); + result.add(name, profit); + } + + return result; + } +#endif + +#if 0 + public profit_per_weekday get_stats_profit_per_weekday() { + var result = new profit_per_weekday(); + + var now = new DateTime.now_utc(); + var today = new DateTime.utc(now.get_year(), now.get_month(), now.get_day_of_month(), 8, 0, 0); + var tomorrow = today.add_days(1); + var weekday = tomorrow.get_day_of_week()-1; + + var to = tomorrow.to_unix(); + var from = to - day_in_seconds; + + var weeks = 8; + + for(int i=0; i 0) + statements["stock"].bind_int(6, supplier); + else + statements["stock"].bind_null(6); + if(best_before_date > 0) + statements["stock"].bind_int64(7, best_before_date); + else + statements["stock"].bind_null(7); + + rc = statements["stock"].step(); + + if(rc != Sqlite.DONE) + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + public void new_product(uint64 id, string name, int memberprice, int guestprice) throws DatabaseError { + statements["product_create"].reset(); + statements["product_create"].bind_text(1, @"$id"); + statements["product_create"].bind_text(2, name); + statements["product_create"].bind_int(3, 0); + int rc = statements["product_create"].step(); + + if(rc != Sqlite.DONE) { + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + new_price(id, 0, memberprice, guestprice); + } + + public void new_price(uint64 product, int64 timestamp, int memberprice, int guestprice) throws DatabaseError { + statements["price_create"].reset(); + statements["price_create"].bind_text(1, @"$product"); + statements["price_create"].bind_int64(2, timestamp); + statements["price_create"].bind_int(3, memberprice); + statements["price_create"].bind_int(4, guestprice); + int rc = statements["price_create"].step(); + + if(rc != Sqlite.DONE) { + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + } + + public bool check_user_password(int32 user, string password) { + statements["password_get"].reset(); + statements["password_get"].bind_int(1, user); + + if(statements["password_get"].step() == Sqlite.ROW) { + var pwhash_db = statements["password_get"].column_text(0); + var pwhash_user = Checksum.compute_for_string(ChecksumType.SHA256, password); + + return pwhash_db == pwhash_user; + } else { + return false; + } + } + + public void set_user_password(int32 user, string password) throws DatabaseError { + var pwhash = Checksum.compute_for_string(ChecksumType.SHA256, password); + int rc; + + /* create user auth line if not existing */ + statements["user_auth_create"].reset(); + statements["user_auth_create"].bind_int(1, user); + rc = statements["user_auth_create"].step(); + if(rc != Sqlite.DONE) + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + + /* set password */ + statements["password_set"].reset(); + statements["password_set"].bind_text(1, pwhash); + statements["password_set"].bind_int(2, user); + rc = statements["password_set"].step(); + if(rc != Sqlite.DONE) + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + public void set_sessionid(int user, string sessionid) throws DatabaseError { + statements["session_set"].reset(); + statements["session_set"].bind_text(1, sessionid); + statements["session_set"].bind_int(2, user); + + int rc = statements["session_set"].step(); + if(rc != Sqlite.DONE) + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + public int get_user_by_sessionid(string sessionid) throws DatabaseError { + statements["session_get"].reset(); + statements["session_get"].bind_text(1, sessionid); + + if(statements["session_get"].step() == Sqlite.ROW) { + return statements["session_get"].column_int(0); + } else { + throw new DatabaseError.SESSION_NOT_FOUND("No such session available in database!"); + } + } + + public UserInfo get_user_info(int user) throws DatabaseError { + var result = UserInfo(); + statements["userinfo"].reset(); + statements["userinfo"].bind_int(1, user); + int rc = statements["userinfo"].step(); + + if(rc == Sqlite.ROW) { + result.id = user; + result.firstname = statements["userinfo"].column_text(0); + result.lastname = statements["userinfo"].column_text(1); + result.email = statements["userinfo"].column_text(2); + result.gender = statements["userinfo"].column_text(3); + result.street = statements["userinfo"].column_text(4); + result.postcode = statements["userinfo"].column_int(5); + result.city = statements["userinfo"].column_text(6); + result.pgp = statements["userinfo"].column_text(7); + } else if(rc == Sqlite.DONE) { + throw new DatabaseError.USER_NOT_FOUND("user not found"); + } else { + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + return result; + } + + public UserAuth get_user_auth(int user) throws DatabaseError { + var result = UserAuth(); + result.id = user; + result.disabled = false; + result.superuser = false; + + statements["userauth"].reset(); + statements["userauth"].bind_int(1, user); + int rc = statements["userauth"].step(); + + if(rc == Sqlite.ROW) { + result.disabled = statements["userauth"].column_int(0) == 1; + result.superuser = statements["userauth"].column_int(1) == 1; + } else if(rc == Sqlite.DONE) { + throw new DatabaseError.USER_NOT_FOUND("user not found"); + } else { + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + return result; + } + + public string get_username(int user) throws DatabaseError { + statements["username"].reset(); + statements["username"].bind_int(1, user); + + if(statements["username"].step() == Sqlite.ROW) { + return statements["username"].column_text(0)+" "+statements["username"].column_text(1); + } else { + throw new DatabaseError.USER_NOT_FOUND("No such user available in database!"); + } + } + + public InvoiceEntry[] get_invoice(int user, int64 from=0, int64 to=-1) throws DatabaseError { + InvoiceEntry[] result = {}; + + if(to == -1) { + to = time_t(); + } + + statements["invoice"].reset(); + statements["invoice"].bind_int(1, user); + statements["invoice"].bind_int64(2, from); + statements["invoice"].bind_int64(3, to); + int rc = statements["invoice"].step(); + + while(rc == Sqlite.ROW) { + InvoiceEntry entry = {}; + entry.timestamp = statements["invoice"].column_int64(0); + entry.product.ean = uint64.parse(statements["invoice"].column_text(1)); + entry.product.name = statements["invoice"].column_text(2); + entry.price = statements["invoice"].column_int(3); + result += entry; + + rc = statements["invoice"].step(); + } + + if(rc != Sqlite.DONE) { + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + return result; + } + + public int64 get_first_purchase(int user) { + statements["purchase_first"].reset(); + statements["purchase_first"].bind_int(1, user); + + if(statements["purchase_first"].step() == Sqlite.ROW) + return statements["purchase_first"].column_int64(0); + else + return 0; + } + + public int64 get_last_purchase(int user) { + statements["purchase_last"].reset(); + statements["purchase_last"].bind_int(1, user); + + if(statements["purchase_last"].step() == Sqlite.ROW) + return statements["purchase_last"].column_int64(0); + else + return 0; + } + + public StatsInfo get_stats_info() { + var result = StatsInfo(); + + DateTime now = new DateTime.now_local(); + DateTime today = new DateTime.local(now.get_year(), now.get_month(), now.get_hour() < 8 ? now.get_day_of_month()-1 : now.get_day_of_month(), 8, 0, 0); + DateTime month = new DateTime.local(now.get_year(), now.get_month(), 1, 0, 0, 0); + + DateTime last4weeks = now.add_days(-28); + DateTime last4months = now.add_months(-4); + + statements["count_articles"].reset(); + if(statements["count_articles"].step() == Sqlite.ROW) + result.count_articles = statements["count_articles"].column_int(0); + + statements["count_users"].reset(); + if(statements["count_users"].step() == Sqlite.ROW) + result.count_users = statements["count_users"].column_int(0); + + statements["stock_value"].reset(); + if(statements["stock_value"].step() == Sqlite.ROW) + result.stock_value = statements["stock_value"].column_int(0); + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, 0); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_total = statements["total_sales"].column_int(0); + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, 0); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_total = statements["total_profit"].column_int(0); + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, today.to_unix()); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_today = statements["total_sales"].column_int(0); + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, today.to_unix()); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_today = statements["total_profit"].column_int(0); + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, month.to_unix()); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_this_month = statements["total_sales"].column_int(0); + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, month.to_unix()); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_this_month = statements["total_profit"].column_int(0); + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, last4weeks.to_unix()); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_per_day = statements["total_sales"].column_int(0) / 28; + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, last4weeks.to_unix()); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_per_day = statements["total_profit"].column_int(0) / 28; + + statements["total_sales"].reset(); + statements["total_sales"].bind_int64(1, last4months.to_unix()); + if(statements["total_sales"].step() == Sqlite.ROW) + result.sales_per_month = statements["total_sales"].column_int(0) / 4; + + statements["total_profit"].reset(); + statements["total_profit"].bind_int64(1, last4months.to_unix()); + if(statements["total_profit"].step() == Sqlite.ROW) + result.profit_per_month = statements["total_profit"].column_int(0) / 4; + + return result; + } + + public int[] get_member_ids() { + int[] result = {}; + + statements["user_get_ids"].reset(); + while(statements["user_get_ids"].step() == Sqlite.ROW) + result += statements["user_get_ids"].column_int(0); + + return result; + } + + public void user_disable(int user, bool value) throws DatabaseError { + int rc; + + /* create user auth line if not existing */ + statements["user_auth_create"].reset(); + statements["user_auth_create"].bind_int(1, user); + rc = statements["user_auth_create"].step(); + if(rc != Sqlite.DONE) + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + + /* set disabled flag */ + statements["user_disable"].reset(); + statements["user_disable"].bind_int(1, value ? 1 : 0); + statements["user_disable"].bind_int(2, user); + rc = statements["user_disable"].step(); + if(rc != Sqlite.DONE) + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + public void user_replace(UserInfo u) throws DatabaseError { + statements["user_replace"].reset(); + statements["user_replace"].bind_int(1, u.id); + statements["user_replace"].bind_text(2, u.email); + statements["user_replace"].bind_text(3, u.firstname); + statements["user_replace"].bind_text(4, u.lastname); + statements["user_replace"].bind_text(5, u.gender); + statements["user_replace"].bind_text(6, u.street); + statements["user_replace"].bind_int(7, u.postcode); + statements["user_replace"].bind_text(8, u.city); + statements["user_replace"].bind_text(9, u.pgp); + + int rc = statements["user_replace"].step(); + if(rc != Sqlite.DONE) + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + + public bool user_is_disabled(int user) throws DatabaseError { + return get_user_auth(user).disabled; + } + + public bool user_exists(int user) throws DatabaseError { + if(user in get_member_ids()) + return true; + return false; + } + + public bool user_equals(UserInfo u) throws DatabaseError { + var dbu = get_user_info(u.id); + return u.equals(dbu); + } + + public int64 get_timestamp_of_last_purchase() { + statements["last_timestamp"].reset(); + if(statements["last_timestamp"].step() != Sqlite.ROW) + return 0; + return statements["last_timestamp"].column_int64(0); + } + + public Supplier[] get_supplier_list() { + Supplier[] result = {}; + + statements["supplier_list"].reset(); + while(statements["supplier_list"].step() == Sqlite.ROW) { + Supplier entry = { + statements["supplier_list"].column_int64(0), + statements["supplier_list"].column_text(1), + statements["supplier_list"].column_text(2), + statements["supplier_list"].column_text(3), + statements["supplier_list"].column_text(4), + statements["supplier_list"].column_text(5), + statements["supplier_list"].column_text(6) + }; + + result += entry; + } + + return result; + } + + public Supplier get_supplier(int id) { + Supplier result = Supplier(); + + statements["supplier_get"].reset(); + statements["supplier_get"].bind_int(1, id); + + if(statements["supplier_get"].step() != Sqlite.ROW) { + result.id = 0; + result.name = "Unknown"; + result.postal_code = ""; + result.city = ""; + result.street = ""; + result.phone = ""; + result.website = ""; + } else { + result.id = statements["supplier_get"].column_int64(0); + result.name = statements["supplier_get"].column_text(1); + result.postal_code = statements["supplier_get"].column_text(2); + result.city = statements["supplier_get"].column_text(3); + result.street = statements["supplier_get"].column_text(4); + result.phone = statements["supplier_get"].column_text(5); + result.website = statements["supplier_get"].column_text(6); + } + + return result; + } + + public void add_supplier(string name, string postal_code, string city, string street, string phone, string website) throws DatabaseError { + statements["supplier_add"].reset(); + statements["supplier_add"].bind_text(1, name); + statements["supplier_add"].bind_text(2, postal_code); + statements["supplier_add"].bind_text(3, city); + statements["supplier_add"].bind_text(4, street); + statements["supplier_add"].bind_text(5, phone); + statements["supplier_add"].bind_text(6, website); + int rc = statements["supplier_add"].step(); + + if(rc != Sqlite.DONE) { + throw new DatabaseError.INTERNAL_ERROR("internal error: %d", rc); + } + } +} diff --git a/src/database/db-interface.vala b/src/database/db-interface.vala new file mode 100644 index 0000000..da95eb7 --- /dev/null +++ b/src/database/db-interface.vala @@ -0,0 +1,148 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.Database")] +public interface Database : Object { + public abstract StockEntry[] get_stock() throws IOError; + public abstract PriceEntry[] get_prices(uint64 product) throws IOError; + public abstract RestockEntry[] get_restocks(uint64 product) throws IOError; + public abstract bool buy(int32 user, uint64 article) throws IOError, DatabaseError; + public abstract string get_product_name(uint64 article) throws IOError, DatabaseError; + public abstract int get_product_amount(uint64 article) throws IOError, DatabaseError; + public abstract Price get_product_price(int user, uint64 article) throws IOError, DatabaseError; + public abstract bool undo(int32 user) throws IOError, DatabaseError; + public abstract bool restock(int user, uint64 product, uint amount, uint price, int supplier, int64 best_before_date) throws IOError, DatabaseError; + public abstract bool new_product(uint64 id, string name, int memberprice, int guestprice) throws IOError, DatabaseError; + public abstract bool new_price(uint64 product, int64 timestamp, int memberprice, int guestprice) throws IOError, DatabaseError; + public abstract bool check_user_password(int32 user, string password) throws IOError; + public abstract void set_user_password(int32 user, string password) throws IOError, DatabaseError; + public abstract void set_sessionid(int user, string sessionid) throws IOError, DatabaseError; + public abstract int get_user_by_sessionid(string sessionid) throws IOError, DatabaseError; + public abstract UserInfo get_user_info(int user) throws IOError, DatabaseError; + public abstract UserAuth get_user_auth(int user) throws IOError, DatabaseError; + public abstract string get_username(int user) throws IOError, DatabaseError; + public abstract InvoiceEntry[] get_invoice(int user, int64 from=0, int64 to=-1) throws IOError, DatabaseError; + public abstract int64 get_first_purchase(int user) throws IOError; + public abstract int64 get_last_purchase(int user) throws IOError; + public abstract StatsInfo get_stats_info() throws IOError; + public abstract int[] get_member_ids() throws IOError; + public abstract void user_disable(int user, bool value) throws IOError, DatabaseError; + public abstract void user_replace(UserInfo u) throws IOError, DatabaseError; + public abstract bool user_is_disabled(int user) throws IOError, DatabaseError; + public abstract bool user_exists(int user) throws IOError, DatabaseError; + public abstract bool user_equals(UserInfo u) throws IOError, DatabaseError; + public abstract int64 get_timestamp_of_last_purchase() throws IOError; + public abstract Supplier[] get_supplier_list() throws IOError; + public abstract Supplier get_supplier(int id) throws IOError; + public abstract bool add_supplier(string name, string postal_code, string city, string street, string phone, string website) throws IOError, DatabaseError; +} + +public struct StockEntry { + public string id; + public string name; + public int amount; + public string memberprice; + public string guestprice; +} + +public struct PriceEntry { + public int64 valid_from; + public Price memberprice; + public Price guestprice; +} + +public struct RestockEntry { + public int64 timestamp; + public int amount; + public string price; + public int supplier; + public int64 best_before_date; +} + +public struct Supplier { + public int64 id; + public string name; + public string postal_code; + public string city; + public string street; + public string phone; + public string website; +} + +public struct UserInfo { + public int id; + public string firstname; + public string lastname; + public string email; + public string gender; + public string street; + public int postcode; + public string city; + public string pgp; + + public bool equals(UserInfo x) { + if(id != x.id) return false; + if(firstname != x.firstname) return false; + if(lastname != x.lastname) return false; + if(email != x.email) return false; + if(gender != x.gender) return false; + if(street != x.street) return false; + if(postcode != x.postcode) return false; + if(city != x.city) return false; + if(pgp != x.pgp) return false; + + return true; + } +} + +public struct UserAuth { + public int id; + public bool disabled; + public bool superuser; +} + +public struct Product { + public uint64 ean; + public string name; +} + +public struct InvoiceEntry { + public int64 timestamp; + Product product; + Price price; +} + +public struct StatsInfo { + public int count_articles; + public int count_users; + public Price stock_value; + public Price sales_total; + public Price profit_total; + public Price sales_today; + public Price profit_today; + public Price sales_this_month; + public Price profit_this_month; + public Price sales_per_day; + public Price profit_per_day; + public Price sales_per_month; + public Price profit_per_month; +} + +public errordomain DatabaseError { + INTERNAL_ERROR, + PRODUCT_NOT_FOUND, + SESSION_NOT_FOUND, + USER_NOT_FOUND +} diff --git a/src/database/main.vala b/src/database/main.vala new file mode 100644 index 0000000..676e960 --- /dev/null +++ b/src/database/main.vala @@ -0,0 +1,44 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public static void write_to_log(string message, ...) { + /* TODO: send message via DBus? Replace some write_to_log by throwing an error? */ +} + +DataBase db; + +public static int main(string[] args) { + db = new DataBase("../../shop.db"); + + Bus.own_name( + BusType.SESSION, + "io.mainframe.shopsystem.Database", + BusNameOwnerFlags.NONE, + on_bus_aquired, + () => {}, + () => stderr.printf("Could not aquire name\n")); + + new MainLoop().run(); + + return 0; +} + +void on_bus_aquired(DBusConnection con) { + try { + con.register_object("/io/mainframe/shopsystem/database", db); + } catch(IOError e) { + stderr.printf("Could not register service\n"); + } +} diff --git a/src/db.vala b/src/db.vala deleted file mode 100644 index dc1c834..0000000 --- a/src/db.vala +++ /dev/null @@ -1,919 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public const int day_in_seconds = 24*60*60; - -public struct StockEntry { - public string id; - public string name; - public int amount; - public string memberprice; - public string guestprice; -} - -public struct PriceEntry { - public int64 valid_from; - public Price memberprice; - public Price guestprice; -} - -public struct RestockEntry { - public int64 timestamp; - public int amount; - public string price; - public int supplier; - public int64 best_before_date; -} - -public struct Supplier { - public int64 id; - public string name; - public string postal_code; - public string city; - public string street; - public string phone; - public string website; -} - -public struct UserInfo { - public int id; - public string firstname; - public string lastname; - public string email; - public string gender; - public string street; - public int postcode; - public string city; - public string pgp; - - public bool equals(UserInfo x) { - if(id != x.id) return false; - if(firstname != x.firstname) return false; - if(lastname != x.lastname) return false; - if(email != x.email) return false; - if(gender != x.gender) return false; - if(street != x.street) return false; - if(postcode != x.postcode) return false; - if(city != x.city) return false; - if(pgp != x.pgp) return false; - - return true; - } - - public bool exists_in_db() { - if(id in db.get_member_ids()) - return true; - else - return false; - } - - public bool equals_db() { - return this.equals(db.get_user_info(id)); - } -} - -public struct UserAuth { - public int id; - public bool disabled; - public bool superuser; -} - -public struct Product { - public uint64 ean; - public string name; -} - -public struct InvoiceEntry { - public int64 timestamp; - Product product; - Price price; -} - -public struct StatsInfo { - public int count_articles; - public int count_users; - public Price stock_value; - public Price sales_total; - public Price profit_total; - public Price sales_today; - public Price profit_today; - public Price sales_this_month; - public Price profit_this_month; - public Price sales_per_day; - public Price profit_per_day; - public Price sales_per_month; - public Price profit_per_month; -} - -public class Database { - private class Statement { - private Sqlite.Statement stmt; - - public Statement(Sqlite.Database db, string query) { - int rc = db.prepare_v2(query, -1, out stmt); - - if(rc != Sqlite.OK) { - error("could not prepare statement: %s", query); - } - } - - public void reset() { - stmt.reset(); - } - - public int step() { - return stmt.step(); - } - - public int bind_null(int index) { - return stmt.bind_null(index); - } - - public int bind_int(int index, int value) { - return stmt.bind_int(index, value); - } - - public int bind_text(int index, string value) { - return stmt.bind_text(index, value); - } - - public int bind_int64(int index, int64 value) { - return stmt.bind_int64(index, value); - } - - public int column_int(int index) { - return stmt.column_int(index); - } - - public string column_text(int index) { - return stmt.column_text(index); - } - - public int64 column_int64(int index) { - return stmt.column_int64(index); - } - } - - private Sqlite.Database db; - private static Gee.HashMap queries = new Gee.HashMap(); - private static Gee.HashMap statements = new Gee.HashMap(); - - public Database(string file) { - int rc; - - rc = Sqlite.Database.open(file, out db); - if(rc != Sqlite.OK) { - error("could not open database!"); - } - - /* setup queries */ - queries["product_name"] = "SELECT name FROM products WHERE id = ?"; - queries["product_amount"] = "SELECT amount FROM products WHERE id = ?"; - queries["products"] = "SELECT id, name, amount FROM products ORDER BY name"; - queries["purchase"] = "INSERT INTO sales ('user', 'product', 'timestamp') VALUES (?, ?, ?)"; - queries["last_purchase"] = "SELECT product FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; - queries["undo"] = "DELETE FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; - queries["product_create"] = "INSERT INTO products ('id', 'name', 'amount') VALUES (?, ?, ?)"; - queries["price_create"] = "INSERT INTO prices ('product', 'valid_from', 'memberprice', 'guestprice') VALUES (?, ?, ?, ?)"; - queries["stock"] = "INSERT INTO restock ('user', 'product', 'amount', 'price', 'timestamp', 'supplier', 'best_before_date') VALUES (?, ?, ?, ?, ?, ?, ?)"; - queries["price"] = "SELECT memberprice, guestprice FROM prices WHERE product = ? AND valid_from <= ? ORDER BY valid_from DESC LIMIT 1"; - queries["prices"] = "SELECT valid_from, memberprice, guestprice FROM prices WHERE product = ? ORDER BY valid_from ASC;"; - queries["restocks"] = "SELECT timestamp, amount, price, supplier, best_before_date FROM restock WHERE product = ? ORDER BY timestamp ASC;"; - queries["profit_complex"] = "SELECT SUM(memberprice - (SELECT price FROM purchaseprices WHERE product = purch.product)) FROM sales purch, prices WHERE purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1);"; - queries["sales_complex"] = "SELECT SUM(memberprice) FROM sales purch, prices WHERE purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1);"; - queries["stock_status"] = "SELECT id, name, amount, memberprice, guestprice FROM products, prices WHERE products.id = prices.product AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = products.id ORDER BY valid_from DESC LIMIT 1) ORDER BY name"; - queries["stock_amount"] = "SELECT timestamp, amount FROM restock WHERE product = ? UNION ALL SELECT timestamp, -1 AS amount FROM sales WHERE product = ? ORDER BY timestamp DESC"; - queries["session_set"] = "UPDATE authentication SET session=? WHERE user = ?"; - queries["session_get"] = "SELECT user FROM authentication WHERE session = ?"; - queries["username"] = "SELECT firstname, lastname FROM users WHERE id = ?"; - queries["password_get"] = "SELECT password FROM authentication WHERE user = ?"; - queries["password_set"] = "UPDATE authentication SET password=? WHERE user = ?"; - queries["userinfo"] = "SELECT firstname, lastname, email, gender, street, plz, city, pgp FROM users WHERE id = ?"; - queries["userauth"] = "SELECT disabled, superuser FROM authentication WHERE user = ?"; - queries["profit_by_product"] = "SELECT name, SUM(memberprice - (SELECT price FROM purchaseprices WHERE product = purch.product)) AS price FROM sales purch, prices, products WHERE purch.product = products.id AND purch.product = prices.product AND purch.user > 0 AND purch.timestamp > ? AND purch.timestamp < ? AND prices.valid_from = (SELECT valid_from FROM prices WHERE product = purch.product AND valid_from < purch.timestamp ORDER BY valid_from DESC LIMIT 1) GROUP BY name ORDER BY price;"; - queries["invoice"] = "SELECT timestamp, productid, productname, price FROM invoice WHERE user = ? AND timestamp >= ? AND timestamp < ?;"; - queries["purchase_first"] = "SELECT timestamp FROM sales WHERE user = ? ORDER BY timestamp ASC LIMIT 1"; - queries["purchase_last"] = "SELECT timestamp FROM sales WHERE user = ? ORDER BY timestamp DESC LIMIT 1"; - queries["count_articles"] = "SELECT COUNT(*) FROM products"; - queries["count_users"] = "SELECT COUNT(*) FROM users"; - queries["stock_value"] = "SELECT SUM(amount * price) FROM products INNER JOIN purchaseprices ON products.id = purchaseprices.product"; - queries["total_sales"] = "SELECT SUM(price) FROM invoice WHERE user >= 0 AND timestamp >= ?"; - queries["total_profit"] = "SELECT SUM(price - (SELECT price FROM purchaseprices WHERE product = productid)) FROM invoice WHERE user >= 0 AND timestamp >= ?"; - queries["user_get_ids"] = "SELECT id FROM users WHERE id > 0"; - queries["user_replace"] = "INSERT OR REPLACE INTO users ('id', 'email', 'firstname', 'lastname', 'gender', 'street', 'plz', 'city', 'pgp') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; - queries["user_auth_create"] = "INSERT OR IGNORE INTO authentication (user) VALUES (?)"; - queries["user_disable"] = "UPDATE authentication SET disabled = ? WHERE user = ?"; - queries["last_timestamp"] = "SELECT timestamp FROM sales ORDER BY timestamp DESC LIMIT 1"; - queries["supplier_list"] = "SELECT id, name, postal_code, city, street, phone, website FROM supplier"; - queries["supplier_get"] = "SELECT id, name, postal_code, city, street, phone, website FROM supplier WHERE id = ?"; - queries["supplier_add"] = "INSERT INTO supplier('name', 'postal_code', 'city', 'street', 'phone', 'website') VALUES (?, ?, ?, ?, ?, ?)"; - - /* compile queries into statements */ - foreach(var entry in queries.entries) { - statements[entry.key] = new Statement(db, entry.value); - } - } - - public Gee.HashMap get_products() { - var result = new Gee.HashMap(null, null); - statements["products"].reset(); - - while(statements["products"].step() == Sqlite.ROW) - result[statements["products"].column_text(0)] = statements["products"].column_text(1); - - return result; - } - - public stock get_stats_stock() { - var result = new stock(); - var now = time_t(); - - /* init products */ - statements["products"].reset(); - while(statements["products"].step() == Sqlite.ROW) { - var id = uint64.parse(statements["products"].column_text(0)); - var name = statements["products"].column_text(1); - int amount = int.parse(statements["products"].column_text(2)); - var product = new stock.product(id, name); - result.add(product); - product.add(now, amount); - - statements["stock_amount"].reset(); - statements["stock_amount"].bind_text(1, "%llu".printf(id)); - statements["stock_amount"].bind_text(2, "%llu".printf(id)); - - while(statements["stock_amount"].step() == Sqlite.ROW) { - var timestamp = uint64.parse(statements["stock_amount"].column_text(0)); - var diff = statements["stock_amount"].column_int(1); - product.add(timestamp+1, amount); - amount -= diff; - product.add(timestamp, amount); - } - } - - return result; - } - - public profit_per_product get_stats_profit_per_products() { - var result = new profit_per_product(); - - statements["profit_by_product"].reset(); - statements["profit_by_product"].bind_int(1, 0); - statements["profit_by_product"].bind_text(2, "99999999999999"); - - while(statements["profit_by_product"].step() == Sqlite.ROW) { - var name = statements["profit_by_product"].column_text(0); - var profit = statements["profit_by_product"].column_int(1); - result.add(name, profit); - } - - return result; - } - - public profit_per_weekday get_stats_profit_per_weekday() { - var result = new profit_per_weekday(); - - var now = new DateTime.now_utc(); - var today = new DateTime.utc(now.get_year(), now.get_month(), now.get_day_of_month(), 8, 0, 0); - var tomorrow = today.add_days(1); - var weekday = tomorrow.get_day_of_week()-1; - - var to = tomorrow.to_unix(); - var from = to - day_in_seconds; - - var weeks = 8; - - for(int i=0; i get_stock() { - var result = new Gee.ArrayList(); - statements["stock_status"].reset(); - - while(statements["stock_status"].step() == Sqlite.ROW) { - StockEntry entry = { - statements["stock_status"].column_text(0), - statements["stock_status"].column_text(1), - statements["stock_status"].column_int(2), - null, - null - }; - - Price mprice = statements["stock_status"].column_int(3); - Price gprice = statements["stock_status"].column_int(4); - - entry.memberprice = @"$mprice"; - entry.guestprice = @"$gprice"; - - result.add(entry); - } - - return result; - } - - public Gee.List get_prices(uint64 product) { - var result = new Gee.ArrayList(); - statements["prices"].reset(); - statements["prices"].bind_text(1, "%llu".printf(product)); - - while(statements["prices"].step() == Sqlite.ROW) { - PriceEntry entry = { - statements["prices"].column_int64(0), - statements["prices"].column_int(1), - statements["prices"].column_int(2) - }; - - result.add(entry); - } - - return result; - } - - public Gee.List get_restocks(uint64 product) { - var result = new Gee.ArrayList(); - statements["restocks"].reset(); - statements["restocks"].bind_text(1, "%llu".printf(product)); - - while(statements["restocks"].step() == Sqlite.ROW) { - RestockEntry entry = { - statements["restocks"].column_int64(0), - statements["restocks"].column_int(1) - }; - - Price p = statements["restocks"].column_int(2); - entry.price = @"$p"; - entry.supplier = statements["restocks"].column_int(3); - entry.best_before_date = statements["restocks"].column_int64(4); - - result.add(entry); - } - - return result; - } - - public bool buy(int32 user, uint64 article) { - int rc = 0; - int64 timestamp = (new DateTime.now_utc()).to_unix(); - - statements["purchase"].reset(); - statements["purchase"].bind_int(1, user); - statements["purchase"].bind_text(2, "%llu".printf(article)); - statements["purchase"].bind_text(3, "%llu".printf(timestamp)); - - rc = statements["purchase"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - - return true; - } - - public string get_product_name(uint64 article) { - statements["product_name"].reset(); - statements["product_name"].bind_text(1, "%llu".printf(article)); - - int rc = statements["product_name"].step(); - - switch(rc) { - case Sqlite.ROW: - return statements["product_name"].column_text(0); - case Sqlite.DONE: - return "unbekanntes Produkt: %llu".printf(article); - default: - return "[internal error: %d]".printf(rc); - } - } - - public int get_product_amount(uint64 article) { - statements["product_amount"].reset(); - statements["product_amount"].bind_text(1, "%llu".printf(article)); - - int rc = statements["product_amount"].step(); - - switch(rc) { - case Sqlite.ROW: - return statements["product_amount"].column_int(0); - case Sqlite.DONE: - warning("unbekanntes Produkt: %llu".printf(article)); - return -1; - default: - warning("[internal error: %d]".printf(rc)); - return -1; - } - } - - public Price get_product_price(int user, uint64 article) { - int64 timestamp = (new DateTime.now_utc()).to_unix(); - bool member = user != 0; - - statements["price"].reset(); - statements["price"].bind_text(1, "%llu".printf(article)); - statements["price"].bind_text(2, "%lld".printf(timestamp)); - - int rc = statements["price"].step(); - - switch(rc) { - case Sqlite.ROW: - if(member) - return statements["price"].column_int(0); - else - return statements["price"].column_int(1); - case Sqlite.DONE: - write_to_log("unbekanntes Produkt: %llu\n", article); - return 0; - default: - write_to_log("[internal error: %d]\n", rc); - return 0; - } - } - - public bool undo(int32 user) { - uint64 pid = 0; - int rc = 0; - - statements["last_purchase"].reset(); - statements["last_purchase"].bind_int(1, user); - - rc = statements["last_purchase"].step(); - switch(rc) { - case Sqlite.ROW: - pid = uint64.parse(statements["last_purchase"].column_text(0)); - string pname = get_product_name(pid); - write_to_log("Remove purchase of %s", pname); - break; - case Sqlite.DONE: - write_to_log("Error: undo not possible without purchases"); - return false; - default: - error("[internal error: %d]".printf(rc)); - } - - statements["undo"].reset(); - statements["undo"].bind_int(1, user); - - rc = statements["undo"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - - return true; - } - - public bool restock(int user, uint64 product, uint amount, uint price, int supplier, int64 best_before_date) { - if(user > 0) { - int rc = 0; - int64 timestamp = (new DateTime.now_utc()).to_unix(); - - statements["stock"].reset(); - statements["stock"].bind_int(1, user); - statements["stock"].bind_text(2, @"$product"); - statements["stock"].bind_text(3, @"$amount"); - statements["stock"].bind_text(4, @"$price"); - statements["stock"].bind_int64(5, timestamp); - if(supplier > 0) - statements["stock"].bind_int(6, supplier); - else - statements["stock"].bind_null(6); - if(best_before_date > 0) - statements["stock"].bind_int64(7, best_before_date); - else - statements["stock"].bind_null(7); - - rc = statements["stock"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - - return true; - } - - return false; - } - - public bool new_product(uint64 id, string name, int memberprice, int guestprice) { - statements["product_create"].reset(); - statements["product_create"].bind_text(1, @"$id"); - statements["product_create"].bind_text(2, name); - statements["product_create"].bind_int(3, 0); - int rc = statements["product_create"].step(); - - if(rc != Sqlite.DONE) { - warning("[internal error: %d]".printf(rc)); - return false; - } - - return new_price(id, 0, memberprice, guestprice); - } - - public bool new_price(uint64 product, int64 timestamp, int memberprice, int guestprice) { - statements["price_create"].reset(); - statements["price_create"].bind_text(1, @"$product"); - statements["price_create"].bind_int64(2, timestamp); - statements["price_create"].bind_int(3, memberprice); - statements["price_create"].bind_int(4, guestprice); - int rc = statements["price_create"].step(); - - if(rc != Sqlite.DONE) { - warning("[internal error: %d]".printf(rc)); - return false; - } - - return true; - } - - public bool check_user_password(int32 user, string password) { - statements["password_get"].reset(); - statements["password_get"].bind_int(1, user); - - if(statements["password_get"].step() == Sqlite.ROW) { - var pwhash_db = statements["password_get"].column_text(0); - var pwhash_user = Checksum.compute_for_string(ChecksumType.SHA256, password); - - return pwhash_db == pwhash_user; - } else { - return false; - } - } - - public void set_user_password(int32 user, string password) { - var pwhash = Checksum.compute_for_string(ChecksumType.SHA256, password); - int rc; - - /* create user auth line if not existing */ - statements["user_auth_create"].reset(); - statements["user_auth_create"].bind_int(1, user); - rc = statements["user_auth_create"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - - /* set password */ - statements["password_set"].reset(); - statements["password_set"].bind_text(1, pwhash); - statements["password_set"].bind_int(2, user); - rc = statements["password_set"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - } - - public void set_sessionid(int user, string sessionid) { - statements["session_set"].reset(); - statements["session_set"].bind_text(1, sessionid); - statements["session_set"].bind_int(2, user); - - int rc = statements["session_set"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - } - - public int get_user_by_sessionid(string sessionid) throws WebSessionError { - statements["session_get"].reset(); - statements["session_get"].bind_text(1, sessionid); - - if(statements["session_get"].step() == Sqlite.ROW) { - return statements["session_get"].column_int(0); - } else { - throw new WebSessionError.SESSION_NOT_FOUND("No such session available in database!"); - } - } - - public UserInfo get_user_info(int user) { - var result = UserInfo(); - statements["userinfo"].reset(); - statements["userinfo"].bind_int(1, user); - - if(statements["userinfo"].step() == Sqlite.ROW) { - result.id = user; - result.firstname = statements["userinfo"].column_text(0); - result.lastname = statements["userinfo"].column_text(1); - result.email = statements["userinfo"].column_text(2); - result.gender = statements["userinfo"].column_text(3); - result.street = statements["userinfo"].column_text(4); - result.postcode = statements["userinfo"].column_int(5); - result.city = statements["userinfo"].column_text(6); - result.pgp = statements["userinfo"].column_text(7); - } - - return result; - } - - public UserAuth get_user_auth(int user) { - var result = UserAuth(); - result.id = user; - result.disabled = false; - result.superuser = false; - - statements["userauth"].reset(); - statements["userauth"].bind_int(1, user); - if(statements["userauth"].step() == Sqlite.ROW) { - result.disabled = statements["userauth"].column_int(0) == 1; - result.superuser = statements["userauth"].column_int(1) == 1; - } - - return result; - } - - public string get_username(int user) throws WebSessionError { - statements["username"].reset(); - statements["username"].bind_int(1, user); - - if(statements["username"].step() == Sqlite.ROW) { - return statements["username"].column_text(0)+" "+statements["username"].column_text(1); - } else { - throw new WebSessionError.USER_NOT_FOUND("No such user available in database!"); - } - } - - public Gee.List get_invoice(int user, int64 from=0, int64 to=-1) { - var result = new Gee.ArrayList(); - - if(to == -1) { - to = time_t(); - } - - statements["invoice"].reset(); - statements["invoice"].bind_int(1, user); - statements["invoice"].bind_int64(2, from); - statements["invoice"].bind_int64(3, to); - - while(statements["invoice"].step() == Sqlite.ROW) { - InvoiceEntry entry = {}; - entry.timestamp = statements["invoice"].column_int64(0); - entry.product.ean = uint64.parse(statements["invoice"].column_text(1)); - entry.product.name = statements["invoice"].column_text(2); - entry.price = statements["invoice"].column_int(3); - result.add(entry); - } - - return result; - } - - public DateTime get_first_purchase(int user) { - statements["purchase_first"].reset(); - statements["purchase_first"].bind_int(1, user); - - if(statements["purchase_first"].step() == Sqlite.ROW) - return new DateTime.from_unix_utc(statements["purchase_first"].column_int64(0)); - else - return new DateTime.from_unix_utc(0); - } - - public DateTime get_last_purchase(int user) { - statements["purchase_last"].reset(); - statements["purchase_last"].bind_int(1, user); - - if(statements["purchase_last"].step() == Sqlite.ROW) - return new DateTime.from_unix_utc(statements["purchase_last"].column_int64(0)); - else - return new DateTime.from_unix_utc(0); - } - - public StatsInfo get_stats_info() { - var result = StatsInfo(); - - DateTime now = new DateTime.now_local(); - DateTime today = new DateTime.local(now.get_year(), now.get_month(), now.get_hour() < 8 ? now.get_day_of_month()-1 : now.get_day_of_month(), 8, 0, 0); - DateTime month = new DateTime.local(now.get_year(), now.get_month(), 1, 0, 0, 0); - - DateTime last4weeks = now.add_days(-28); - DateTime last4months = now.add_months(-4); - - statements["count_articles"].reset(); - if(statements["count_articles"].step() == Sqlite.ROW) - result.count_articles = statements["count_articles"].column_int(0); - - statements["count_users"].reset(); - if(statements["count_users"].step() == Sqlite.ROW) - result.count_users = statements["count_users"].column_int(0); - - statements["stock_value"].reset(); - if(statements["stock_value"].step() == Sqlite.ROW) - result.stock_value = statements["stock_value"].column_int(0); - - statements["total_sales"].reset(); - statements["total_sales"].bind_int64(1, 0); - if(statements["total_sales"].step() == Sqlite.ROW) - result.sales_total = statements["total_sales"].column_int(0); - - statements["total_profit"].reset(); - statements["total_profit"].bind_int64(1, 0); - if(statements["total_profit"].step() == Sqlite.ROW) - result.profit_total = statements["total_profit"].column_int(0); - - statements["total_sales"].reset(); - statements["total_sales"].bind_int64(1, today.to_unix()); - if(statements["total_sales"].step() == Sqlite.ROW) - result.sales_today = statements["total_sales"].column_int(0); - - statements["total_profit"].reset(); - statements["total_profit"].bind_int64(1, today.to_unix()); - if(statements["total_profit"].step() == Sqlite.ROW) - result.profit_today = statements["total_profit"].column_int(0); - - statements["total_sales"].reset(); - statements["total_sales"].bind_int64(1, month.to_unix()); - if(statements["total_sales"].step() == Sqlite.ROW) - result.sales_this_month = statements["total_sales"].column_int(0); - - statements["total_profit"].reset(); - statements["total_profit"].bind_int64(1, month.to_unix()); - if(statements["total_profit"].step() == Sqlite.ROW) - result.profit_this_month = statements["total_profit"].column_int(0); - - statements["total_sales"].reset(); - statements["total_sales"].bind_int64(1, last4weeks.to_unix()); - if(statements["total_sales"].step() == Sqlite.ROW) - result.sales_per_day = statements["total_sales"].column_int(0) / 28; - - statements["total_profit"].reset(); - statements["total_profit"].bind_int64(1, last4weeks.to_unix()); - if(statements["total_profit"].step() == Sqlite.ROW) - result.profit_per_day = statements["total_profit"].column_int(0) / 28; - - statements["total_sales"].reset(); - statements["total_sales"].bind_int64(1, last4months.to_unix()); - if(statements["total_sales"].step() == Sqlite.ROW) - result.sales_per_month = statements["total_sales"].column_int(0) / 4; - - statements["total_profit"].reset(); - statements["total_profit"].bind_int64(1, last4months.to_unix()); - if(statements["total_profit"].step() == Sqlite.ROW) - result.profit_per_month = statements["total_profit"].column_int(0) / 4; - - return result; - } - - public Gee.List get_member_ids() { - var result = new Gee.ArrayList(); - - statements["user_get_ids"].reset(); - while(statements["user_get_ids"].step() == Sqlite.ROW) - result.add(statements["user_get_ids"].column_int(0)); - - return result; - } - - public void user_disable(int user, bool value) { - int rc; - - /* create user auth line if not existing */ - statements["user_auth_create"].reset(); - statements["user_auth_create"].bind_int(1, user); - rc = statements["user_auth_create"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - - /* set disabled flag */ - statements["user_disable"].reset(); - statements["user_disable"].bind_int(1, value ? 1 : 0); - statements["user_disable"].bind_int(2, user); - rc = statements["user_disable"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - } - - public void user_replace(UserInfo u) { - statements["user_replace"].reset(); - statements["user_replace"].bind_int(1, u.id); - statements["user_replace"].bind_text(2, u.email); - statements["user_replace"].bind_text(3, u.firstname); - statements["user_replace"].bind_text(4, u.lastname); - statements["user_replace"].bind_text(5, u.gender); - statements["user_replace"].bind_text(6, u.street); - statements["user_replace"].bind_int(7, u.postcode); - statements["user_replace"].bind_text(8, u.city); - statements["user_replace"].bind_text(9, u.pgp); - - int rc = statements["user_replace"].step(); - if(rc != Sqlite.DONE) - error("[internal error: %d]".printf(rc)); - } - - public bool user_is_disabled(int user) { - return get_user_auth(user).disabled; - } - - public int64 get_timestamp_of_last_purchase() { - statements["last_timestamp"].reset(); - if(statements["last_timestamp"].step() != Sqlite.ROW) - return 0; - return statements["last_timestamp"].column_int64(0); - } - - public Gee.List get_supplier_list() { - var result = new Gee.ArrayList(); - statements["supplier_list"].reset(); - - while(statements["supplier_list"].step() == Sqlite.ROW) { - Supplier entry = { - statements["supplier_list"].column_int64(0), - statements["supplier_list"].column_text(1), - statements["supplier_list"].column_text(2), - statements["supplier_list"].column_text(3), - statements["supplier_list"].column_text(4), - statements["supplier_list"].column_text(5), - statements["supplier_list"].column_text(6) - }; - - result.add(entry); - } - - return result; - } - - public Supplier get_supplier(int id) { - Supplier result = Supplier(); - - statements["supplier_get"].reset(); - statements["supplier_get"].bind_int(1, id); - - if(statements["supplier_get"].step() != Sqlite.ROW) { - result.id = 0; - result.name = "Unknown"; - result.postal_code = ""; - result.city = ""; - result.street = ""; - result.phone = ""; - result.website = ""; - } else { - result.id = statements["supplier_get"].column_int64(0); - result.name = statements["supplier_get"].column_text(1); - result.postal_code = statements["supplier_get"].column_text(2); - result.city = statements["supplier_get"].column_text(3); - result.street = statements["supplier_get"].column_text(4); - result.phone = statements["supplier_get"].column_text(5); - result.website = statements["supplier_get"].column_text(6); - } - - return result; - } - - public bool add_supplier(string name, string postal_code, string city, string street, string phone, string website) { - statements["supplier_add"].reset(); - statements["supplier_add"].bind_text(1, name); - statements["supplier_add"].bind_text(2, postal_code); - statements["supplier_add"].bind_text(3, city); - statements["supplier_add"].bind_text(4, street); - statements["supplier_add"].bind_text(5, phone); - statements["supplier_add"].bind_text(6, website); - int rc = statements["supplier_add"].step(); - - if(rc != Sqlite.DONE) { - warning("[internal error: %d]".printf(rc)); - return false; - } - - return true; - } -} diff --git a/src/device.vala b/src/device.vala deleted file mode 100644 index 5d7fc09..0000000 --- a/src/device.vala +++ /dev/null @@ -1,280 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public class Device { - private Posix.termios newtio; - private Posix.termios restoretio; - public int fd=-1; - private IOChannel io_read; - public int byterate; - private File lockfile; - - public signal void received_barcode(string barcode); - - public Device(string device, int rate, int bits, int stopbits) { - Posix.speed_t baudrate = Posix.B9600; - - try { - /* check lock file */ - lockfile = File.new_for_path("/var/lock/LCK.." + device.replace("/dev/", "")); - if(lockfile.query_exists()) { - int pid = -1; - uint8[] data; - if(lockfile.load_contents(null, out data, null)) { - pid = int.parse((string) data); - } else { - error("Can't read lock file!\n"); - } - - if(Posix.kill(pid, 0) == 0) { - error("serial device is locked!\n"); - } - } - - var pid = "%d\n".printf(Posix.getpid()); - lockfile.replace_contents(pid.data, null, false, FileCreateFlags.NONE, null); - - fd = Posix.open(device, Posix.O_RDWR /*| Posix.O_NONBLOCK*/); - - if(fd < 0) { - fd = -1; - lockfile.delete(); - error("Could not open device!\n"); - } - - } catch(Error e) { - error("Could not create lock file: %s!\n", e.message); - } - - - Posix.tcflush(fd, Posix.TCIOFLUSH); - - Posix.tcgetattr(fd, out restoretio); - - /* apply settings */ - switch(rate) { - case 300: - baudrate = Posix.B300; - break; - case 600: - baudrate = Posix.B600; - break; - case 1200: - baudrate = Posix.B1200; - break; - case 2400: - baudrate = Posix.B2400; - break; - case 4800: - baudrate = Posix.B4800; - break; - case 9600: - baudrate = Posix.B9600; - break; - case 19200: - baudrate = Posix.B19200; - break; - case 38400: - baudrate = Posix.B38400; - break; - case 57600: - baudrate = Posix.B57600; - break; - case 115200: - baudrate = Posix.B115200; - break; - case 230400: - baudrate = Posix.B230400; - break; - default: - /* not supported */ - rate = 9600; - break; - } - - Posix.cfsetospeed(ref newtio, baudrate); - Posix.cfsetispeed(ref newtio, baudrate); - - switch(bits) { - case 5: - newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS5; - break; - case 6: - newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS6; - break; - case 7: - newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS7; - break; - case 8: - default: - newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS8; - break; - } - - newtio.c_cflag |= Posix.CLOCAL | Posix.CREAD; - - newtio.c_cflag &= ~(Posix.PARENB | Posix.PARODD); - - /* TODO: parity */ - - newtio.c_cflag &= ~Linux.Termios.CRTSCTS; - - if(stopbits == 2) - newtio.c_cflag |= Posix.CSTOPB; - else - newtio.c_cflag &= ~Posix.CSTOPB; - - newtio.c_iflag = Posix.IGNBRK; - - newtio.c_lflag = 0; - newtio.c_oflag = 0; - - newtio.c_cc[Posix.VTIME]=1; - newtio.c_cc[Posix.VMIN]=1; - - newtio.c_lflag &= ~(Posix.ECHONL|Posix.NOFLSH); - - int mcs=0; - Posix.ioctl(fd, Linux.Termios.TIOCMGET, out mcs); - mcs |= Linux.Termios.TIOCM_RTS; - Posix.ioctl(fd, Linux.Termios.TIOCMSET, out mcs); - - Posix.tcsetattr(fd, Posix.TCSANOW, newtio); - - this.byterate = rate/bits; - - try { - io_read = new IOChannel.unix_new(fd); - io_read.set_line_term("\r\n", 2); - if(io_read.set_encoding(null) != IOStatus.NORMAL) - error("Failed to set encoding"); - if(!(io_read.add_watch(IOCondition.IN | IOCondition.HUP, device_read) != 0)) { - error("Could not bind IOChannel"); - } - } catch(IOChannelError e) { - error("IOChannel: %s", e.message); - } - } - - ~Device() { - /* restore old tty config */ - Posix.tcsetattr(fd, Posix.TCSANOW, restoretio); - - /* close file */ - Posix.close(fd); - - /* remove lock */ - lockfile.delete(); - } - - private bool device_read(IOChannel gio, IOCondition cond) { - IOStatus ret; - string msg; - size_t len, term_char; - - if((cond & IOCondition.HUP) == IOCondition.HUP) - error("Lost device"); - - try { - ret = gio.read_line(out msg, out len, out term_char); - msg = msg[0:(long)term_char]; - - if(msg.has_prefix("USER ") || msg.has_prefix("STOCK") || msg.has_prefix("AMOUNT ")) { - if(!check_code39_checksum(msg)) - received_barcode("SCANNER RETURNED INCORRECT DATA"); - else {/* remove checksum */ - msg = msg[0:-1]; - received_barcode(msg); - } - } - else - received_barcode(msg); - } - catch(IOChannelError e) { - stderr.printf("IOChannel Error: %s", e.message); - return false; - } - catch(ConvertError e) { - stderr.printf("Convert Error: %s", e.message); - return false; - } - return true; - } - - private ssize_t write(void *buf, size_t count) { - ssize_t size = Posix.write(fd, buf, count); - return size; - } - - private bool check_code39_checksum(string data) { - int result = 0; - - for(int i = 0; i= '0' && data[i] <= '9') - result += data[i] - '0'; - else if(data[i] >= 'A' && data[i] <= 'Z') - result += data[i] - 'A' + 10; - else - switch(data[i]) { - case '-': - result += 36; break; - case '.': - result += 37; break; - case ' ': - result += 38; break; - case '$': - result += 39; break; - case '/': - result += 40; break; - case '+': - result += 41; break; - case '%': - result += 42; break; - default: - /* invalid character */ - return false; - } - - result %= 43; - } - - if(result < 10) - result = result + '0'; - else if(result < 36) - result = result - 10 + 'A'; - else - switch(result) { - case 36: result = '-'; break; - case 37: result = '.'; break; - case 38: result = ' '; break; - case 39: result = '$'; break; - case 40: result = '/'; break; - case 41: result = '+'; break; - case 42: result = '%'; break; - } - - return (data[data.length-1] == result); - } - - /** - * @param duration duration of the blink in 0.1 seconds - */ - public void blink(uint duration) { - uint size = byterate/10 * duration; - var msg = new uint8[size]; - Posix.memset(msg, 0xFF, msg.length); - this.write(msg, msg.length); - } -} diff --git a/src/main.vala b/src/main.vala deleted file mode 100644 index 67f00ff..0000000 --- a/src/main.vala +++ /dev/null @@ -1,131 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public Device dev; -public Database db; -public AudioPlayer audio; -public CSVMemberFile csvimport; -public ScannerSession localsession; -public MainLoop loop; -public PGPKeyArchive pgp; -public KeyFile cfg; -public CursesUI ui; - -const OptionEntry[] option_entries = { - { "version", 'v', OptionFlags.IN_MAIN, OptionArg.NONE, ref opt_version, "output version information and exit", null }, - {null} -}; - -/* parameters */ -static bool opt_version; - -public static int main(string[] args) { - /* parse parameters from shell */ - var context = new OptionContext("- KtT Shop System"); - context.set_help_enabled(true); - context.add_main_entries(option_entries, "shop"); - context.add_group(Gst.init_get_option_group()); - - try { - context.parse(ref args); - } catch(OptionError e) { - stderr.puts(e.message + "\n"); - return 1; - } - - if(opt_version) { - stdout.puts("KtT Shop System 0.1\n"); - return 0; - } - - /* handle unix signals */ - Unix.signal_add(Posix.SIGTERM, handle_signals); - Unix.signal_add(Posix.SIGINT, handle_signals); - - try { - cfg = new KeyFile(); - cfg.load_from_file("ktt-shopsystem.cfg", KeyFileFlags.NONE); - } catch(Error e) { - error("Could not load configuration file: %s", e.message); - } - - string devicefile = ""; - try { - devicefile = cfg.get_string("SERIAL", "device"); - } catch(KeyFileError e) { - stderr.puts("Please specify serial device in the configuration file!\n"); - return 1; - } - - dev = new Device(devicefile, 9600, 8, 1); - db = new Database("shop.db"); - audio = new AudioPlayer(); - loop = new MainLoop(); - localsession = new ScannerSession(); - - pgp = new PGPKeyArchive(cfg); - - dev.received_barcode.connect((data) => { - if(localsession.interpret(data)) - dev.blink(10); - }); - - ui = new CursesUI(); - - while(!check_valid_time()) { - write_to_log("Invalid System Time! Retry in 1 minute..."); - Posix.sleep(60); - } - - write_to_log("KtT Shop System has been started"); - audio.play_system("startup.ogg"); - - /* attach webserver to mainloop */ - new WebServer(); - - /* run mainloop */ - loop.run(); - - write_to_log("Stopping Shop System"); - audio.play_system("shutdown.ogg"); - - /* we need to run the mainloop to play audio */ - audio.end_of_stream.connect(() => { loop.quit(); }); - loop.run(); - - /* explicitly call destructors */ - dev = null; - db = null; - audio = null; - ui.exit(); - - return 0; -} - -public bool check_valid_time() { - return time_t() > db.get_timestamp_of_last_purchase(); -} - -public void write_to_log(string format, ...) { - var arguments = va_list(); - var message = format.vprintf(arguments); - - ui.log(message); -} - -bool handle_signals() { - loop.quit(); - return false; -} diff --git a/src/pdf-invoice/Makefile b/src/pdf-invoice/Makefile index 2a5d2a4..cc6db94 100644 --- a/src/pdf-invoice/Makefile +++ b/src/pdf-invoice/Makefile @@ -1,9 +1,12 @@ all: pdf-invoice -pdf-invoice: pdf-invoice.vala ../price.vapi - valac --pkg pangocairo --pkg posix --pkg gio-2.0 $^ +pdf-invoice: main.vala pdf-invoice.vala pdf-invoice-interface.vala ../price.vapi + valac -g -o $@ --pkg pangocairo --pkg librsvg-2.0 --pkg posix --pkg gdk-2.0 --pkg gio-2.0 $^ + +test: pdf-invoice-interface.vala test.vala ../price.vapi + valac -o $@ --pkg gio-2.0 $^ clean: - rm -rf pdf-invoice + rm -rf pdf-invoice test .PHONY: all clean diff --git a/src/pdf-invoice/main.vala b/src/pdf-invoice/main.vala new file mode 100644 index 0000000..76b03c6 --- /dev/null +++ b/src/pdf-invoice/main.vala @@ -0,0 +1,36 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public static int main(string[] args) { + Bus.own_name( + BusType.SESSION, + "io.mainframe.shopsystem.InvoicePDF", + BusNameOwnerFlags.NONE, + on_bus_aquired, + () => {}, + () => stderr.printf("Could not aquire name\n")); + + new MainLoop().run(); + + return 0; +} + +void on_bus_aquired(DBusConnection conn) { + try { + conn.register_object("/io/mainframe/shopsystem/invoicepdf", new InvoicePDF()); + } catch(IOError e) { + stderr.printf("Could not register service\n"); + } +} diff --git a/src/pdf-invoice/pdf-invoice-interface.vala b/src/pdf-invoice/pdf-invoice-interface.vala new file mode 100644 index 0000000..d2c04d9 --- /dev/null +++ b/src/pdf-invoice/pdf-invoice-interface.vala @@ -0,0 +1,54 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.InvoicePDFError")] +public errordomain InvoicePDFError { + /* missing invoice data */ + NO_INVOICE_DATA, + NO_INVOICE_DATE, + NO_INVOICE_ID, + NO_INVOICE_RECIPIENT, + + /* data not supported by renderer */ + ARTICLE_NAME_TOO_LONG, + PRICE_TOO_HIGH, + TOO_FAR_IN_THE_FUTURE +} + +public struct InvoiceRecipient { + public string firstname; + public string lastname; + public string street; + public string postal_code; + public string city; + public string gender; +} + +public struct InvoiceEntry { + int timestamp; + string article; + Price price; +} + +[DBus (name = "io.mainframe.shopsystem.InvoicePDF")] +public interface PDFInvoice : Object { + public abstract string invoice_id { set; owned get; } + public abstract int64 invoice_date { set; owned get; } + public abstract InvoiceRecipient invoice_recipient { set; owned get; } + public abstract InvoiceEntry[] invoice_entries { set; owned get; } + + public abstract uint8[] generate() throws IOError, InvoicePDFError; + public abstract void clear() throws IOError; +} diff --git a/src/pdf-invoice/pdf-invoice.vala b/src/pdf-invoice/pdf-invoice.vala index 92598f6..095dcc6 100644 --- a/src/pdf-invoice/pdf-invoice.vala +++ b/src/pdf-invoice/pdf-invoice.vala @@ -13,35 +13,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -[DBus (name = "io.mainframe.shopsystem.InvoicePDFError")] -public errordomain InvoicePDFError { - /* missing invoice data */ - NO_INVOICE_DATA, - NO_INVOICE_DATE, - NO_INVOICE_ID, - NO_INVOICE_RECIPIENT, - - /* data not supported by renderer */ - ARTICLE_NAME_TOO_LONG, - PRICE_TOO_HIGH, - TOO_FAR_IN_THE_FUTURE -} - -public struct InvoiceRecipient { - public string firstname; - public string lastname; - public string street; - public string postal_code; - public string city; - public string gender; -} - -public struct InvoiceEntry { - int timestamp; - string article; - Price price; -} - [DBus (name = "io.mainframe.shopsystem.InvoicePDF")] public class InvoicePDF { /* A4 sizes (in points, 72 DPI) */ @@ -49,10 +20,10 @@ public class InvoicePDF { private const double height = 841.88976; /* 297mm */ /* invoice content, which should appear in the PDF */ - public string invoice_id { set; get; } + public string invoice_id { set; owned get; } public int64 invoice_date { set; get; } - public InvoiceRecipient invoice_recipient { set; get; } - public InvoiceEntry[] invoice_entries { set; get; } + public InvoiceRecipient invoice_recipient { set; owned get; } + public InvoiceEntry[] invoice_entries { set; owned get; } /* pdf data */ private uint8[] data; @@ -77,29 +48,27 @@ public class InvoicePDF { }; public InvoicePDF() { + clear(); + } + + private void render_svg(Cairo.Context ctx, string file) { + var svg = new Rsvg.Handle.from_file(file); + svg.render_cairo(ctx); } private void draw_footer(Cairo.Context ctx) { - /* TODO: get path from config file, support svg */ - var footer = new Cairo.ImageSurface.from_png("../../invoice/footer-line.png"); - ctx.set_source_surface(footer, 0, 817); - ctx.paint(); + ctx.save(); + ctx.translate(-20, 818); + ctx.scale(1.42, 1.42); + render_svg(ctx, "../../invoice/footer-line.svg"); + ctx.restore(); } private void draw_logo(Cairo.Context ctx) { - /* TODO: get path from config file, support svg */ - var logo = new Cairo.ImageSurface.from_png("../../invoice/logo.png"); - - var pattern = new Cairo.Pattern.for_surface(logo); - Cairo.Matrix scaler; - pattern.get_matrix(out scaler); - scaler.scale(1.41,1.41); - scaler.translate(-364.5,-22.5); - pattern.set_matrix(scaler); - pattern.set_filter(Cairo.Filter.BEST); - - ctx.set_source(pattern); - ctx.paint(); + ctx.save(); + ctx.translate(366,25); + render_svg(ctx, "../../invoice/logo.svg"); + ctx.restore(); } private void draw_address(Cairo.Context ctx) { @@ -661,29 +630,14 @@ public class InvoicePDF { public void clear() { invoice_date = 0; invoice_id = ""; - invoice_recipient = {}; + invoice_recipient = { + "", + "", + "", + "", + "", + "", + }; invoice_entries = null; } } - -public static int main(string[] args) { - Bus.own_name( - BusType.SESSION, - "io.mainframe.shopsystem.InvoicePDF", - BusNameOwnerFlags.NONE, - on_bus_aquired, - () => {}, - () => stderr.printf ("Could not aquire name\n")); - - new MainLoop ().run (); - - return 0; -} - -void on_bus_aquired(DBusConnection conn) { - try { - conn.register_object ("/io/mainframe/invoicepdf", new InvoicePDF()); - } catch (IOError e) { - stderr.printf ("Could not register service\n"); - } -} diff --git a/src/pdf-invoice/test.vala b/src/pdf-invoice/test.vala new file mode 100644 index 0000000..f9d21f6 --- /dev/null +++ b/src/pdf-invoice/test.vala @@ -0,0 +1,47 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public static int main(string args[]) { + PDFInvoice invoice = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.InvoicePDF", "/io/mainframe/shopsystem/invoicepdf"); + + InvoiceRecipient r = { + "Max", + "Mustermann", + "Foobar Straße 42", + "31337", + "Entenhausen", + "masculinum" + }; + + InvoiceEntry e1 = { + 1364271520, + "Club Mate", + 2342 + }; + + /* set invoice data */ + invoice.invoice_id = "TEST"; + invoice.invoice_date = 1364271524; + invoice.invoice_recipient = r; + invoice.invoice_entries = {e1}; + + /* generate pdf */ + var pdfdata = invoice.generate(); + + /* write pdf into file */ + FileUtils.set_contents("test.pdf", (string) pdfdata, pdfdata.length); + + return 0; +} diff --git a/src/pgp/Makefile b/src/pgp/Makefile new file mode 100644 index 0000000..6bcb535 --- /dev/null +++ b/src/pgp/Makefile @@ -0,0 +1,9 @@ +all: pgp + +pgp: main.vala pgp.vala pgp-interface.vala ../config/config-interface.vala + valac -o $@ --vapidir ../../vapi -X -lgpgme --pkg gpgme --pkg gio-2.0 --pkg libarchive $^ + +clean: + rm -rf pgp + +.PHONY: all clean diff --git a/src/pgp/main.vala b/src/pgp/main.vala new file mode 100644 index 0000000..f95b6f7 --- /dev/null +++ b/src/pgp/main.vala @@ -0,0 +1,53 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public static void write_to_log(string message, ...) { + /* TODO: send message via DBus? Replace some write_to_log by throwing an error? */ +} + +PGPKeyArchive pgp; +Config cfg; + +public static int main(string[] args) { + try { + cfg = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.Config", "/io/mainframe/shopsystem/config"); + pgp = new PGPKeyArchive(cfg.get_string("PGP", "keyring")); + } catch(IOError e) { + error("IOError: %s\n", e.message); + } catch(KeyFileError e) { + error("Config Error: %s\n", e.message); + } + + + Bus.own_name( + BusType.SESSION, + "io.mainframe.shopsystem.PGP", + BusNameOwnerFlags.NONE, + on_bus_aquired, + () => {}, + () => stderr.printf("Could not aquire name\n")); + + new MainLoop().run(); + + return 0; +} + +void on_bus_aquired(DBusConnection con) { + try { + con.register_object("/io/mainframe/shopsystem/pgp", pgp); + } catch(IOError e) { + stderr.printf("Could not register service\n"); + } +} diff --git a/src/pgp/pgp-interface.vala b/src/pgp/pgp-interface.vala new file mode 100644 index 0000000..62bfe67 --- /dev/null +++ b/src/pgp/pgp-interface.vala @@ -0,0 +1,21 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.PGP")] +public interface PGP : Object { + public abstract string[] import_archive(uint8[] data) throws IOError; + public abstract string[] list_keys() throws IOError; + public abstract string get_key(string fingerprint) throws IOError; +} diff --git a/src/pgp/pgp.vala b/src/pgp/pgp.vala new file mode 100644 index 0000000..bb48c61 --- /dev/null +++ b/src/pgp/pgp.vala @@ -0,0 +1,127 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.PGP")] +public class PGPKeyArchive { + private string keyring; + private GPG.Context gpg; + + public PGPKeyArchive(string keyring) { + /* check version (important!) */ + GPG.check_version(); + + /* initialize default context */ + GPG.Context.Context(out gpg); + + /* TODO TODO TODO */ +#if 0 + if(keyring.has_prefix("\"") && keyring.has_suffix("\"")) + this.keyring = keyring.substring(1,keyring.length-2); +#endif + this.keyring = keyring; + + /* TODO: check existence of keyring */ + + /* set home directory */ + var info = gpg.get_engine_info(); + gpg.set_engine_info(info.protocol, info.file_name, keyring); + + /* enable ascii armor */ + gpg.set_armor(true); + } + + public string[] import_archive(uint8[] data) { + string[] result = {}; + unowned Archive.Entry entry; + var archive = new Archive.Read(); + + /* support all formats & compression types */ + archive.support_compression_all(); + archive.support_format_all(); + + /* load test archive for now */ + if(archive.open_memory(data, data.length) != Archive.Result.OK) + return result; + + while(archive.next_header(out entry) == Archive.Result.OK) { + var name = entry.pathname(); + var size = entry.size(); + var content = new uint8[size]; + + /* skip entries, which contain a slash */ + if(name.contains("/")) + continue; + + /* skip files, which are big (probably not a minimal pgp key) */ + if(size > 50000) + continue; + + if(archive.read_data((void*) content, (ssize_t) size) == size) { + if(!((string) content).has_prefix("-----BEGIN PGP PUBLIC KEY BLOCK-----")) + continue; + + /* put byte data into GPG.Data object */ + GPG.Data gpgdata; + GPG.Data.create_from_memory(out gpgdata, content, false); + + /* import keys */ + gpg.op_import(gpgdata); + + /* get result */ + unowned GPG.ImportResult importresult = gpg.op_import_result(); + + /* add imported fingerprints to result */ + for(unowned GPG.ImportStatus st = importresult.imports; st != null; st = st.next) { + if(!(st.fpr in result) && (st.status & GPG.ImportStatusFlags.NEW) != 0) + result += st.fpr; + } + } + } + + return result; + } + + public string[] list_keys() { + string[] result = {}; + GPG.Key key; + + gpg.op_keylist_start(); + + while(gpg.op_keylist_next(out key) == GPGError.ErrorCode.NO_ERROR) { + result += key.subkeys[0].fpr; + } + + gpg.op_keylist_end(); + + return result; + } + + public string get_key(string fingerprint) { + GPG.Data keydata; + GPG.Data.create(out keydata); + + if(gpg.op_export(fingerprint, 0, keydata) == GPGError.ErrorCode.NO_ERROR) { + long size = keydata.seek(0, Posix.FILE.SEEK_END); + keydata.seek(0, Posix.FILE.SEEK_SET); + stdout.printf("size: %ld\n", size); + uint8[] data = new uint8[size]; + keydata.read(data); + return (string) data; + } else { + stdout.printf("error!\n"); + return ""; + } + } +} diff --git a/src/scanner-session/Makefile b/src/scanner-session/Makefile new file mode 100644 index 0000000..ea91d3a --- /dev/null +++ b/src/scanner-session/Makefile @@ -0,0 +1,9 @@ +all: scanner-session + +scanner-session: main.vala scannersession.vala scannersession-interface.vala ../database/db-interface.vala ../serial-device/serial-device-interface.vala ../audio/audio-interface.vala ../price.vapi + valac -o $@ --pkg gio-2.0 $^ + +clean: + rm -rf scanner-session + +.PHONY: all clean diff --git a/src/scanner-session/main.vala b/src/scanner-session/main.vala new file mode 100644 index 0000000..11562e1 --- /dev/null +++ b/src/scanner-session/main.vala @@ -0,0 +1,38 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +ScannerSessionImplementation session; + +public static int main(string[] args) { + Bus.own_name( + BusType.SESSION, + "io.mainframe.shopsystem.ScannerSession", + BusNameOwnerFlags.NONE, + on_bus_aquired, + () => {}, + () => stderr.printf("Could not aquire name\n")); + + new MainLoop().run(); + + return 0; +} + +void on_bus_aquired(DBusConnection con) { + try { + con.register_object("/io/mainframe/shopsystem/scanner_session", session); + } catch(IOError e) { + stderr.printf("Could not register service\n"); + } +} diff --git a/src/scanner-session/scannersession-interface.vala b/src/scanner-session/scannersession-interface.vala new file mode 100644 index 0000000..0f81dd4 --- /dev/null +++ b/src/scanner-session/scannersession-interface.vala @@ -0,0 +1,25 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.ScannerSession")] +public interface ScannerSession : Object { + public abstract signal void msg(MessageType type, string message); +} + +public enum MessageType { + INFO, + WARNING, + ERROR +} diff --git a/src/scanner-session/scannersession.vala b/src/scanner-session/scannersession.vala new file mode 100644 index 0000000..6926ba9 --- /dev/null +++ b/src/scanner-session/scannersession.vala @@ -0,0 +1,164 @@ +/* Copyright 2012-2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.ScannerSession")] +public class ScannerSessionImplementation { + private int user = 0; + private string name = "Guest"; + private bool logged_in = false; + private bool disabled = false; + private string theme = "beep"; + + private Database db; + private AudioPlayer audio; + + public signal void msg(MessageType type, string message); + + public ScannerSessionImplementation() { + try { + db = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.Database", "/io/mainframe/shopsystem/database"); + audio = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.AudioPlayer", "/io/mainframe/shopsystem/audio"); + } catch(IOError e) { + error("IOError: %s\n", e.message); + } + } + + private void send_message(MessageType type, string format, ...) { + var arguments = va_list(); + var message = format.vprintf(arguments); + + msg(type, message); + } + + private void logout() { + logged_in = false; + } + + private bool login(int user) { + this.user = user; + try { + this.name = db.get_username(user); + this.disabled = db.get_user_auth(user).disabled; + } catch(DatabaseError e) { + return false; + } + this.logged_in = true; + this.theme = audio.get_random_user_theme(); + + return true; + } + + private bool interpret(string scannerdata) { + if(scannerdata.has_prefix("USER ")) { + string str_id = scannerdata.substring(5); + int32 id = int.parse(str_id); + + /* check if scannerdata has valid format */ + if(scannerdata != "USER %d".printf(id)) { + audio.play_system("error.ogg"); + send_message(MessageType.ERROR, "Invalid User ID: %s", scannerdata); + return false; + } + + if(logged_in) { + send_message(MessageType.WARNING, "Last user forgot to logout"); + logout(); + } + + if(login(id)) { + audio.play_user(theme, "login"); + send_message(MessageType.INFO, "Login: %s (%d)", name, user); + return true; + } else { + audio.play_system("error.ogg"); + send_message(MessageType.ERROR, "Login failed (User ID = %d)", id); + return false; + } + } else if(scannerdata == "GUEST") { + if(logged_in) { + send_message(MessageType.WARNING, "Last user forgot to logout"); + logout(); + } + + if(login(0)) { + audio.play_user(theme, "login"); + send_message(MessageType.INFO, "Login: %s (%d)", name, user); + return true; + } else { + audio.play_system("error.ogg"); + send_message(MessageType.ERROR, "Login failed (User ID = 0)"); + return false; + } + } else if(scannerdata == "UNDO") { + if(!logged_in) { + audio.play_system("error.ogg"); + send_message(MessageType.ERROR, "Can't undo if not logged in!"); + return false; + } else { + if(db.undo(user)) { + audio.play_user(theme, "purchase"); + return true; + } else { + audio.play_user(theme, "error"); + send_message(MessageType.ERROR, "Couldn't undo last purchase!"); + return false; + } + } + } else if(scannerdata == "LOGOUT") { + if(logged_in) { + audio.play_user(theme, "logout"); + send_message(MessageType.INFO, "Logout!"); + logout(); + return true; + } + + return false; + } else { + uint64 id = 0; + scannerdata.scanf("%llu", out id); + + /* check if scannerdata has valid format */ + if(scannerdata != "%llu".printf(id) && scannerdata != "%08llu".printf(id) && scannerdata != "%013llu".printf(id)) { + audio.play_user(theme, "error"); + send_message(MessageType.ERROR, "invalid product: %s", scannerdata); + return false; + } + + var name = db.get_product_name(id); + + if(!logged_in) { + var mprice = db.get_product_price(1, id); + var gprice = db.get_product_price(0, id); + + audio.play_system("error.ogg"); + send_message(MessageType.INFO, @"article info: $name (Member: $mprice €, Guest: $gprice €)"); + send_message(MessageType.ERROR, "Login required for purchase!"); + return false; + } + + if(db.buy(user, id)) { + var price = db.get_product_price(user, id); + audio.play_user(theme, "purchase"); + send_message(MessageType.INFO, @"article bought: $name ($price €)"); + return true; + } else { + audio.play_user(theme, "error"); + send_message(MessageType.ERROR, "purchase failed!"); + return false; + } + } + } +} + diff --git a/src/scannersession.vala b/src/scannersession.vala deleted file mode 100644 index 9dbe3d3..0000000 --- a/src/scannersession.vala +++ /dev/null @@ -1,165 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public class ScannerSession { - public int user { - get; - private set; - default = 0; - } - public string name { - get; - private set; - default = "Guest"; - } - public bool logged_in { - get; - private set; - default = false; - } - public bool disabled { - get; - private set; - default = false; - } - public string theme { - get; - private set; - default = "beep"; - } - - public void logout() { - logged_in = false; - } - - public bool login(int user) { - this.user = user; - try { - this.name = db.get_username(user); - this.disabled = db.get_user_auth(user).disabled; - } catch(WebSessionError e) { - return false; - } - this.logged_in = true; - this.theme = audio.get_random_user_theme(); - - return true; - } - - public ScannerSession() { - } - - public bool interpret(string scannerdata) { - if(scannerdata.has_prefix("USER ")) { - string str_id = scannerdata.substring(5); - int32 id = int.parse(str_id); - - /* check if scannerdata has valid format */ - if(scannerdata != "USER %d".printf(id)) { - audio.play_system("error.ogg"); - write_to_log("Error: Invalid User ID: %s", scannerdata); - return false; - } - - if(logged_in) { - write_to_log("Warning: Last user forgot to logout"); - logout(); - } - - if(login(id)) { - audio.play_user(theme, "login"); - write_to_log("Login: %s (%d)", name, user); - return true; - } else { - audio.play_system("error.ogg"); - write_to_log("Error: Login failed (User ID = %d)", id); - return false; - } - } else if(scannerdata == "GUEST") { - if(logged_in) { - write_to_log("Warning: Last user forgot to logout"); - logout(); - } - - if(login(0)) { - audio.play_user(theme, "login"); - write_to_log("Login: %s (%d)", name, user); - return true; - } else { - audio.play_system("error.ogg"); - write_to_log("Error: Login failed (User ID = 0)"); - return false; - } - } else if(scannerdata == "UNDO") { - if(!logged_in) { - audio.play_system("error.ogg"); - write_to_log("Error: Can't undo if not logged in!"); - return false; - } else { - if(db.undo(user)) { - audio.play_user(theme, "purchase"); - return true; - } else { - audio.play_user(theme, "error"); - write_to_log("Error: Couldn't undo last purchase!"); - return false; - } - } - } else if(scannerdata == "LOGOUT") { - if(logged_in) { - audio.play_user(theme, "logout"); - write_to_log("Logout!"); - logout(); - return true; - } - - return false; - } else { - uint64 id = 0; - scannerdata.scanf("%llu", out id); - - /* check if scannerdata has valid format */ - if(scannerdata != "%llu".printf(id) && scannerdata != "%08llu".printf(id) && scannerdata != "%013llu".printf(id)) { - audio.play_user(theme, "error"); - write_to_log("Error: invalid product: %s", scannerdata); - return false; - } - - var name = db.get_product_name(id); - - if(!logged_in) { - var mprice = db.get_product_price(1, id); - var gprice = db.get_product_price(0, id); - - audio.play_system("error.ogg"); - write_to_log(@"article info: $name (Member: $mprice €, Guest: $gprice €)"); - write_to_log("Error: Login required for purchase!"); - return false; - } - - if(db.buy(user, id)) { - var price = db.get_product_price(user, id); - audio.play_user(theme, "purchase"); - write_to_log(@"article bought: $name ($price €)"); - return true; - } else { - audio.play_user(theme, "error"); - write_to_log("Error: purchase failed!"); - return false; - } - } - } -} - diff --git a/src/serial-device/Makefile b/src/serial-device/Makefile new file mode 100644 index 0000000..952b3a1 --- /dev/null +++ b/src/serial-device/Makefile @@ -0,0 +1,9 @@ +all: serial-device + +serial-device: main.vala serial-device.vala serial-device-interface.vala ../config/config-interface.vala + valac -o $@ --pkg linux --pkg posix --pkg gio-2.0 $^ + +clean: + rm -rf serial-device + +.PHONY: all clean diff --git a/src/serial-device/main.vala b/src/serial-device/main.vala new file mode 100644 index 0000000..e338842 --- /dev/null +++ b/src/serial-device/main.vala @@ -0,0 +1,47 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +Device dev; + +public static int main(string[] args) { + try { + Config cfg = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.Config", "/io/mainframe/shopsystem/config"); + dev = new Device(cfg.get_string("SERIAL", "device"), 9600, 8, 1); + } catch(IOError e) { + error("IOError: %s\n", e.message); + } catch(KeyFileError e) { + error("Config Error: %s\n", e.message); + } + + Bus.own_name( + BusType.SESSION, + "io.mainframe.shopsystem.SerialDevice", + BusNameOwnerFlags.NONE, + on_bus_aquired, + () => {}, + () => stderr.printf("Could not aquire name\n")); + + new MainLoop().run(); + + return 0; +} + +void on_bus_aquired(DBusConnection con) { + try { + con.register_object("/io/mainframe/shopsystem/device", dev); + } catch(IOError e) { + stderr.printf("Could not register service\n"); + } +} diff --git a/src/serial-device/serial-device-interface.vala b/src/serial-device/serial-device-interface.vala new file mode 100644 index 0000000..572e810 --- /dev/null +++ b/src/serial-device/serial-device-interface.vala @@ -0,0 +1,20 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.SerialDevice")] +public interface SerialDevice : Object { + public abstract signal void received_barcode(string barcode); + public abstract void blink(uint duration) throws IOError; +} diff --git a/src/serial-device/serial-device.vala b/src/serial-device/serial-device.vala new file mode 100644 index 0000000..76bb5b5 --- /dev/null +++ b/src/serial-device/serial-device.vala @@ -0,0 +1,281 @@ +/* Copyright 2012-2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +[DBus (name = "io.mainframe.shopsystem.SerialDevice")] +public class Device { + private Posix.termios newtio; + private Posix.termios restoretio; + private int fd=-1; + private IOChannel io_read; + private int byterate; + private File lockfile; + + public signal void received_barcode(string barcode); + + public Device(string device, int rate, int bits, int stopbits) { + Posix.speed_t baudrate = Posix.B9600; + + try { + /* check lock file */ + lockfile = File.new_for_path("/var/lock/LCK.." + device.replace("/dev/", "")); + if(lockfile.query_exists()) { + int pid = -1; + uint8[] data; + if(lockfile.load_contents(null, out data, null)) { + pid = int.parse((string) data); + } else { + error("Can't read lock file!\n"); + } + + if(Posix.kill(pid, 0) == 0) { + error("serial device is locked!\n"); + } + } + + var pid = "%d\n".printf(Posix.getpid()); + lockfile.replace_contents(pid.data, null, false, FileCreateFlags.NONE, null); + + fd = Posix.open(device, Posix.O_RDWR /*| Posix.O_NONBLOCK*/); + + if(fd < 0) { + fd = -1; + lockfile.delete(); + error("Could not open device!\n"); + } + + } catch(Error e) { + error("Could not create lock file: %s!\n", e.message); + } + + + Posix.tcflush(fd, Posix.TCIOFLUSH); + + Posix.tcgetattr(fd, out restoretio); + + /* apply settings */ + switch(rate) { + case 300: + baudrate = Posix.B300; + break; + case 600: + baudrate = Posix.B600; + break; + case 1200: + baudrate = Posix.B1200; + break; + case 2400: + baudrate = Posix.B2400; + break; + case 4800: + baudrate = Posix.B4800; + break; + case 9600: + baudrate = Posix.B9600; + break; + case 19200: + baudrate = Posix.B19200; + break; + case 38400: + baudrate = Posix.B38400; + break; + case 57600: + baudrate = Posix.B57600; + break; + case 115200: + baudrate = Posix.B115200; + break; + case 230400: + baudrate = Posix.B230400; + break; + default: + /* not supported */ + rate = 9600; + break; + } + + Posix.cfsetospeed(ref newtio, baudrate); + Posix.cfsetispeed(ref newtio, baudrate); + + switch(bits) { + case 5: + newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS5; + break; + case 6: + newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS6; + break; + case 7: + newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS7; + break; + case 8: + default: + newtio.c_cflag = (newtio.c_cflag & ~Posix.CSIZE) | Posix.CS8; + break; + } + + newtio.c_cflag |= Posix.CLOCAL | Posix.CREAD; + + newtio.c_cflag &= ~(Posix.PARENB | Posix.PARODD); + + /* TODO: parity */ + + newtio.c_cflag &= ~Linux.Termios.CRTSCTS; + + if(stopbits == 2) + newtio.c_cflag |= Posix.CSTOPB; + else + newtio.c_cflag &= ~Posix.CSTOPB; + + newtio.c_iflag = Posix.IGNBRK; + + newtio.c_lflag = 0; + newtio.c_oflag = 0; + + newtio.c_cc[Posix.VTIME]=1; + newtio.c_cc[Posix.VMIN]=1; + + newtio.c_lflag &= ~(Posix.ECHONL|Posix.NOFLSH); + + int mcs=0; + Posix.ioctl(fd, Linux.Termios.TIOCMGET, out mcs); + mcs |= Linux.Termios.TIOCM_RTS; + Posix.ioctl(fd, Linux.Termios.TIOCMSET, out mcs); + + Posix.tcsetattr(fd, Posix.TCSANOW, newtio); + + this.byterate = rate/bits; + + try { + io_read = new IOChannel.unix_new(fd); + io_read.set_line_term("\r\n", 2); + if(io_read.set_encoding(null) != IOStatus.NORMAL) + error("Failed to set encoding"); + if(!(io_read.add_watch(IOCondition.IN | IOCondition.HUP, device_read) != 0)) { + error("Could not bind IOChannel"); + } + } catch(IOChannelError e) { + error("IOChannel: %s", e.message); + } + } + + ~Device() { + /* restore old tty config */ + Posix.tcsetattr(fd, Posix.TCSANOW, restoretio); + + /* close file */ + Posix.close(fd); + + /* remove lock */ + lockfile.delete(); + } + + private bool device_read(IOChannel gio, IOCondition cond) { + IOStatus ret; + string msg; + size_t len, term_char; + + if((cond & IOCondition.HUP) == IOCondition.HUP) + error("Lost device"); + + try { + ret = gio.read_line(out msg, out len, out term_char); + msg = msg[0:(long)term_char]; + + if(msg.has_prefix("USER ") || msg.has_prefix("STOCK") || msg.has_prefix("AMOUNT ")) { + if(!check_code39_checksum(msg)) + received_barcode("SCANNER RETURNED INCORRECT DATA"); + else {/* remove checksum */ + msg = msg[0:-1]; + received_barcode(msg); + } + } + else + received_barcode(msg); + } + catch(IOChannelError e) { + stderr.printf("IOChannel Error: %s", e.message); + return false; + } + catch(ConvertError e) { + stderr.printf("Convert Error: %s", e.message); + return false; + } + return true; + } + + private ssize_t write(void *buf, size_t count) { + ssize_t size = Posix.write(fd, buf, count); + return size; + } + + private bool check_code39_checksum(string data) { + int result = 0; + + for(int i = 0; i= '0' && data[i] <= '9') + result += data[i] - '0'; + else if(data[i] >= 'A' && data[i] <= 'Z') + result += data[i] - 'A' + 10; + else + switch(data[i]) { + case '-': + result += 36; break; + case '.': + result += 37; break; + case ' ': + result += 38; break; + case '$': + result += 39; break; + case '/': + result += 40; break; + case '+': + result += 41; break; + case '%': + result += 42; break; + default: + /* invalid character */ + return false; + } + + result %= 43; + } + + if(result < 10) + result = result + '0'; + else if(result < 36) + result = result - 10 + 'A'; + else + switch(result) { + case 36: result = '-'; break; + case 37: result = '.'; break; + case 38: result = ' '; break; + case 39: result = '$'; break; + case 40: result = '/'; break; + case 41: result = '+'; break; + case 42: result = '%'; break; + } + + return (data[data.length-1] == result); + } + + /** + * @param duration duration of the blink in 0.1 seconds + */ + public void blink(uint duration) { + uint size = byterate/10 * duration; + var msg = new uint8[size]; + Posix.memset(msg, 0xFF, msg.length); + this.write(msg, msg.length); + } +} diff --git a/src/template.vala b/src/template.vala deleted file mode 100644 index 8fe56d0..0000000 --- a/src/template.vala +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public errordomain TemplateError { - NOT_FOUND, - NOT_LOADABLE, - NOT_ALLOWED, -} - -public class WebTemplate { - private string template; - public uint8[] data { get { return template.data; } } - - public WebTemplate(string file, WebSession login) throws TemplateError { - var b = File.new_for_path("templates/base.html"); - var m = File.new_for_path("templates/menu.html"); - var f = File.new_for_path("templates/"+file); - File fauth; - - if(login.logged_in) - fauth = File.new_for_path("templates/menu_logout.html"); - else - fauth = File.new_for_path("templates/menu_login.html"); - - uint8[] basis, menu, template, auth; - - if(!b.query_exists()) - throw new TemplateError.NOT_FOUND("templates/base.html not found!"); - - if(!m.query_exists()) - throw new TemplateError.NOT_FOUND("templates/menu.html not found!"); - - if(!fauth.query_exists()) - throw new TemplateError.NOT_FOUND(fauth.get_path()+" not found!"); - - if(!f.query_exists()) - throw new TemplateError.NOT_FOUND("templates/"+file+" not found!"); - - try { - if(!b.load_contents(null, out basis, null)) - throw new TemplateError.NOT_LOADABLE("templates/base.html could not be loaded!"); - if(!m.load_contents(null, out menu, null)) - throw new TemplateError.NOT_LOADABLE("templates/menu.html could not be loaded!"); - if(!fauth.load_contents(null, out auth, null)) - throw new TemplateError.NOT_LOADABLE(fauth.get_path()+" could not be loaded!"); - if(!f.load_contents(null, out template, null)) - throw new TemplateError.NOT_LOADABLE("templates/"+file+" could not be loaded!"); - } catch(Error e) { - throw new TemplateError.NOT_LOADABLE("could not load templates!"); - } - - this.template = ((string) basis).replace("{{{NAVBAR}}}", ((string) menu)); - this.template = this.template.replace("{{{AUTH}}}", ((string) auth)); - this.template = this.template.replace("{{{CONTENT}}}", ((string) template)); - this.template = this.template.replace("{{{USERNAME}}}", login.name); - this.template = this.template.replace("{{{USERID}}}", "%d".printf(login.user)); - this.template = this.template.replace("{{{SUPERUSER}}}", login.superuser ? "" : "hidden"); - } - - public WebTemplate.DATA(string file) throws TemplateError { - var f = File.new_for_path("templates/"+file); - uint8[] template; - - if(!f.query_exists()) - throw new TemplateError.NOT_FOUND("templates/"+file+" not found!"); - - try { - if(!f.load_contents(null, out template, null)) - throw new TemplateError.NOT_LOADABLE("templates/"+file+" could not be loaded!"); - } catch(Error e) { - throw new TemplateError.NOT_LOADABLE("could not load templates!"); - } - - this.template = (string) template; - } - - public void replace(string key, string value) { - template = template.replace("{{{"+key+"}}}", value); - } - - public void menu_set_active(string key) { - try { - var regex_active = new Regex("{{{MENU\\."+key+"}}}"); - var regex_other = new Regex("{{{MENU\\..*}}}"); - - template = regex_active.replace(template, -1, 0, "active"); - template = regex_other.replace(template, -1, 0, ""); - } catch(RegexError e) { - warning ("%s", e.message); - } - } -} diff --git a/src/ui/clock.vala b/src/ui/clock.vala deleted file mode 100644 index e38ad9c..0000000 --- a/src/ui/clock.vala +++ /dev/null @@ -1,61 +0,0 @@ -using Curses; - -public class ClockWindow { - AsciiNumbers ascii; - Window win; - - public ClockWindow() { - ascii = new AsciiNumbers(); - win = new Window(6, 18, 1, COLS-2-18); - win.bkgdset(COLOR_PAIR(0) | Attribute.BOLD); - - win.clrtobot(); - win.box(0, 0); - - win.refresh(); - } - - public void update() { - string[] x; - var now = new DateTime.now_local(); - - x = ascii.get('0' + (char) (now.get_hour() / 10)); - win.mvaddstr(1,1, x[0]); - win.mvaddstr(2,1, x[1]); - win.mvaddstr(3,1, x[2]); - - x = ascii.get('0' + (char) (now.get_hour() % 10)); - win.mvaddstr(1,4, x[0]); - win.mvaddstr(2,4, x[1]); - win.mvaddstr(3,4, x[2]); - - x = ascii.get(':'); - win.mvaddstr(1,7, x[0]); - win.mvaddstr(2,7, x[1]); - win.mvaddstr(3,7, x[2]); - - x = ascii.get('0' + (char) (now.get_minute() / 10)); - win.mvaddstr(1,10, x[0]); - win.mvaddstr(2,10, x[1]); - win.mvaddstr(3,10, x[2]); - - x = ascii.get('0' + (char) (now.get_minute() % 10)); - win.mvaddstr(1,13, x[0]); - win.mvaddstr(2,13, x[1]); - win.mvaddstr(3,13, x[2]); - - - win.clrtobot(); - win.box(0, 0); - - win.mvaddstr(5,4, now.format("%Y-%m-%d")); - - win.refresh(); - } - - public void redraw() { - win.touchwin(); - win.refresh(); - } - -} diff --git a/src/ui/curses-ui.vala b/src/ui/curses-ui.vala deleted file mode 100644 index 6676eea..0000000 --- a/src/ui/curses-ui.vala +++ /dev/null @@ -1,68 +0,0 @@ -public class CursesUI { - MessageBox messages; - Dialog dialog; - Logo banner; - ClockWindow clkwin; - StatusPanel statuswin; - - public CursesUI() { - /* unicode support */ - Intl.setlocale(LocaleCategory.CTYPE, ""); - - /* initialize curses */ - Curses.initscr(); - - /* disable cursor */ - Curses.curs_set(0); - - /* initialize color mode and define color pairs */ - Curses.start_color(); - Curses.init_pair(0, Curses.Color.WHITE, Curses.Color.BLACK); - Curses.init_pair(1, Curses.Color.GREEN, Curses.Color.BLACK); - Curses.init_pair(2, Curses.Color.WHITE, Curses.Color.RED); - - /* initialize widgets */ - banner = new Logo(); - statuswin = new StatusPanel(); - messages = new MessageBox(); - clkwin = new ClockWindow(); - - clkwin.update(); - - Timeout.add_seconds(10, update_time); - } - - ~CursesUI() { - exit(); - } - - public void exit() { - /* Reset the terminal mode */ - Curses.endwin(); - } - - bool update_time() { - clkwin.update(); - return true; - } - - public void status(string message) { - statuswin.set(message); - } - - public void log(string message) { - messages.add(message); - } - - public void dialog_open(string title, string message) { - dialog = new Dialog(message, title); - } - - public void dialog_close() { - dialog = null; - messages.redraw(); - banner.redraw(); - clkwin.redraw(); - statuswin.redraw(); - } -} diff --git a/src/ui/dialog.vala b/src/ui/dialog.vala deleted file mode 100644 index 29782e5..0000000 --- a/src/ui/dialog.vala +++ /dev/null @@ -1,36 +0,0 @@ -using Curses; - -public class Dialog { - Window win; - Window subwin; - - public Dialog(string message, string title = "KtT Shopsystem Error", int h=16, int w=60) - requires (title.length <= w-4) - { - int y = LINES/2-h/2; - int x = COLS/2-w/2; - - int title_x = (w-title.length)/2; - - win = new Window(h, w, y, x); - - /* make the dialog white on red */ - win.bkgdset(COLOR_PAIR(2) | Attribute.BOLD); - win.clrtobot(); - - /* message subwindow */ - subwin = win.derwin(h-4, w-4, 2, 2); - subwin.clrtobot(); - subwin.printw(message); - subwin.refresh(); - - /* dialog title */ - win.box(0,0); - win.mvaddstr(0, title_x, title); - win.mvaddch(0, title_x-2, Acs.RTEE); - win.mvaddch(0, title_x-1, ' '); - win.mvaddch(0, title_x+title.length, ' '); - win.mvaddch(0, title_x+title.length+1, Acs.LTEE); - win.refresh(); - } -} diff --git a/src/ui/logo.vala b/src/ui/logo.vala deleted file mode 100644 index 5a94422..0000000 --- a/src/ui/logo.vala +++ /dev/null @@ -1,30 +0,0 @@ -using Curses; - -public class Logo { - Window win; - - public Logo() { - win = new Window(8, COLS - 2, 0, 1); - win.bkgdset(COLOR_PAIR(1) | Attribute.BOLD); - - win.addstr("\n"); - win.addstr(" _ ___ _____ ____ _ \n"); - win.addstr(" | |/ / ||_ _| / ___|| |__ ___ _ __ \n"); - win.addstr(" | ' /| __|| | \\___ \\| '_ \\ / _ \\| '_ \\ \n"); - win.addstr(" | . \\| |_ | | ___) | | | | (_) | |_) )\n"); - win.addstr(" |_|\\_\\\\__||_| |____/|_| |_|\\___/| .__/ \n"); - win.addstr(" |_| \n"); - - win.clrtobot(); - - win.box(0, 0); - - win.refresh(); - } - - public void redraw() { - win.touchwin(); - win.refresh(); - } - -} diff --git a/src/ui/message_box.vala b/src/ui/message_box.vala deleted file mode 100644 index 4f9c3f6..0000000 --- a/src/ui/message_box.vala +++ /dev/null @@ -1,45 +0,0 @@ -using Curses; - -public class MessageBox { - Window win; - Window subwin; - DateTime last; - - public MessageBox() { - win = new Window(LINES-9, COLS - 2, 8, 1); - win.bkgdset(COLOR_PAIR(0)); - - win.clrtobot(); - win.box(0, 0); - win.refresh(); - - subwin = win.derwin(LINES-11, COLS-4, 1, 1); - subwin.scrollok(true); - subwin.clrtobot(); - subwin.refresh(); - - last = new DateTime.from_unix_utc(0); - } - - public void add(string msg) { - var now = new DateTime.now_local(); - - if(now.get_day_of_year() != last.get_day_of_year() || now.get_year() != last.get_year()) { - string curtime = now.format("%Y-%m-%d"); - subwin.addstr("\nDate Changed: " + curtime); - } - - last = now; - - string curtime = now.format("%H:%M:%S"); - subwin.addstr("\n[" + curtime + "] " + msg); - subwin.refresh(); - } - - public void redraw() { - win.touchwin(); - win.refresh(); - subwin.touchwin(); - subwin.refresh(); - } -} diff --git a/src/ui/numbers.vala b/src/ui/numbers.vala deleted file mode 100644 index 8ee00d5..0000000 --- a/src/ui/numbers.vala +++ /dev/null @@ -1,98 +0,0 @@ -public class AsciiNumbers { - - public string[] zero = { - " _ ", - "/ \\", - "\\_/" - }; - - public string[] one = { - " ", - " /|", - " |" - }; - - public string[] two = { - "__ ", - " _)", - "(__" - }; - - public string[] three = { - "__ ", - " _)", - "__)" - }; - - public string[] four = { - " ", - "|_|", - " |" - }; - - public string[] five = { - " __", - "|_ ", - "__)" - }; - - public string[] six = { - " _ ", - "/_ ", - "\\_)" - }; - - public string[] seven = { - "___", - " /", - " / " - }; - - public string[] eight = { - " _ ", - "(_)", - "(_)" - }; - - public string[] nine = { - " _ ", - "(_\\", - " _/" - }; - - public string[] colon = { - " ", - " o ", - " o " - }; - - public string[] get(char c) { - switch(c) { - case '0': - return zero; - case '1': - return one; - case '2': - return two; - case '3': - return three; - case '4': - return four; - case '5': - return five; - case '6': - return six; - case '7': - return seven; - case '8': - return eight; - case '9': - return nine; - case ':': - return colon; - default: - return {}; - } - } - -} diff --git a/src/ui/status.vala b/src/ui/status.vala deleted file mode 100644 index db33820..0000000 --- a/src/ui/status.vala +++ /dev/null @@ -1,24 +0,0 @@ -using Curses; - -public class StatusPanel { - Window win; - - public StatusPanel() { - win = new Window(1, COLS - 2, LINES-1, 1); - win.bkgdset(COLOR_PAIR(2) | Attribute.BOLD); - - win.clrtobot(); - win.refresh(); - } - - public void set(string msg) { - win.mvaddstr(0,1, msg); - win.clrtobot(); - win.refresh(); - } - - public void redraw() { - win.touchwin(); - win.refresh(); - } -} diff --git a/src/web.vala b/src/web.vala deleted file mode 100644 index 7140c22..0000000 --- a/src/web.vala +++ /dev/null @@ -1,854 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public class WebServer { - private Soup.Server srv; - - void handler_default(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var l = new WebSession(server, msg, path, query, client); - var t = new WebTemplate("index.html", l); - t.replace("TITLE", "KtT Shop System"); - t.menu_set_active("home"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_logout(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var l = new WebSession(server, msg, path, query, client); - l.logout(); - var t = new WebTemplate("logout.html", l); - t.replace("TITLE", "KtT Shop System"); - t.menu_set_active("home"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_users(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - string[] pathparts = path.split("/"); - - if(pathparts.length <= 2) { - handler_user_list(server, msg, path, query, client); - } else { - int id = int.parse(pathparts[2]); - - if(pathparts.length <= 3) { - handler_user_entry(server, msg, path, query, client, id); - } else { - switch(pathparts[3]) { - case "invoice": - uint16 selectedyear = (pathparts.length >= 5 && pathparts[4] != "") ? (uint16) int.parse(pathparts[4]) : (uint16) (new DateTime.now_local()).get_year(); - uint8 selectedmonth = (pathparts.length >= 6 && pathparts[5] != "") ? (uint8) int.parse(pathparts[5]) : (uint8) (new DateTime.now_local()).get_month(); - uint8 selectedday = (pathparts.length >= 7 && pathparts[6] != "") ? (uint8) int.parse(pathparts[6]) : (uint8) (new DateTime.now_local()).get_day_of_month(); - handler_user_invoice(server, msg, path, query, client, id, selectedyear, selectedmonth, selectedday); - break; - case "stats": - handler_todo(server, msg, path, query, client); - break; - default: - handler_404(server, msg, path, query, client); - break; - } - } - } - } - - void handler_user_list(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var session = new WebSession(server, msg, path, query, client); - if(!session.superuser) { - handler_403(server, msg, path, query, client); - return; - } - - var t = new WebTemplate("users/index.html", session); - t.replace("TITLE", "KtT Shop System: User"); - t.menu_set_active("users"); - var data = ""; - foreach(var m in db.get_member_ids()) { - try { - var name = db.get_username(m); - data += @"$m$name"; - } catch(WebSessionError e) { - } - } - t.replace("DATA", data); - - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_user_pgp_import(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var session = new WebSession(server, msg, path, query, client); - if(!session.superuser) { - handler_403(server, msg, path, query, client); - return; - } - - var t = new WebTemplate("users/import-pgp.html", session); - t.replace("TITLE", "KtT Shop System: PGP Key Import"); - t.menu_set_active("users"); - - Soup.Buffer filedata; - var postdata = Soup.Form.decode_multipart(msg, "file", null, null, out filedata); - - if(postdata == null || !postdata.contains("step")) { - t.replace("DATA", ""); - t.replace("STEP1", "block"); - t.replace("STEP2", "none"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - return; - } else { - var keylist = pgp.import_archive(filedata.data); - string keylisttemplate; - - if(keylist.length > 0) { - keylisttemplate = "
    \n"; - foreach(string s in keylist) { - keylisttemplate += "
  • "+s+"
  • \n"; - } - keylisttemplate += "
\n"; - } else { - keylisttemplate = "

No new keys!

"; - } - - t.replace("DATA", keylisttemplate); - t.replace("STEP1", "none"); - t.replace("STEP2", "block"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - return; - } - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_user_import(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var session = new WebSession(server, msg, path, query, client); - if(!session.superuser) { - handler_403(server, msg, path, query, client); - return; - } - var t = new WebTemplate("users/import.html", session); - t.replace("TITLE", "KtT Shop System: User Import"); - t.menu_set_active("users"); - - Soup.Buffer filedata; - var postdata = Soup.Form.decode_multipart(msg, "file", null, null, out filedata); - if(postdata == null || !postdata.contains("step")) { - t.replace("DATA1", ""); - t.replace("DATA2", ""); - t.replace("STEP1", "block"); - t.replace("STEP2", "none"); - t.replace("STEP23", "none"); - t.replace("STEP3", "none"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - return; - } else { - if(filedata != null) { - string text = (string) filedata.data; - text = text.substring(0,(long) filedata.length-1); - csvimport = new CSVMemberFile(text); - } - - if(csvimport == null) { - handler_403(server, msg, path, query, client); - return; - } - - /* new & changed users */ - string data1 = ""; - foreach(var member in csvimport.get_members()) { - if(member.exists_in_db() && !member.equals_db()) { - var dbmember = db.get_user_info(member.id); - data1 += @"$(dbmember.id)$(dbmember.firstname)$(dbmember.lastname)$(dbmember.email)$(dbmember.gender)$(dbmember.street)$(dbmember.postcode)$(dbmember.city)$(dbmember.pgp)"; - } - if(!member.exists_in_db() || !member.equals_db()) { - data1 += @"$(member.id)$(member.firstname)$(member.lastname)$(member.email)$(member.gender)$(member.street)$(member.postcode)$(member.city)$(member.pgp)"; - } - } - t.replace("DATA1", data1); - - /* removed users */ - Gee.List blockedusers = csvimport.missing_unblocked_members(); - if(blockedusers.size > 0) { - string data2 = "Disabling the following users, because they are no longer found in the member CSV:
    "; - - foreach(var member in blockedusers) { - try { - string name = db.get_username(member); - data2 += @"
  • $name ($member)
  • "; - } catch(Error e) {} - } - - data2 += "
"; - t.replace("DATA2", data2); - } else { - t.replace("DATA2", ""); - } - - /* show correct blocks */ - t.replace("STEP1", "none"); - t.replace("STEP23", "block"); - if(postdata["step"] == "1") { - t.replace("STEP2", "block"); - t.replace("STEP3", "none"); - } else { - t.replace("STEP2", "none"); - t.replace("STEP3", "block"); - } - - if(postdata["step"] == "2") { - /* disable users */ - foreach(var member in csvimport.missing_unblocked_members()) { - db.user_disable(member, true); - } - - /* update users */ - foreach(var member in csvimport.get_members()) { - db.user_replace(member); - } - - csvimport = null; - } - } - - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_user_entry(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, int id) { - try { - var session = new WebSession(server, msg, path, query, client); - if(id != session.user && !session.superuser) { - handler_403(server, msg, path, query, client); - return; - } - var t = new WebTemplate("users/entry.html", session); - t.replace("TITLE", "KtT Shop System: User Info %llu".printf(id)); - t.menu_set_active("users"); - - var userinfo = db.get_user_info(id); - - t.replace("UID", "%d".printf(userinfo.id)); - t.replace("FIRSTNAME", userinfo.firstname); - t.replace("LASTNAME", userinfo.lastname); - t.replace("EMAIL", userinfo.email); - t.replace("GENDER", userinfo.gender); - t.replace("STREET", userinfo.street); - t.replace("POSTALCODE", "%d".printf(userinfo.postcode)); - t.replace("CITY", userinfo.city); - t.replace("PGPKEYID", userinfo.pgp); - - var userauth = db.get_user_auth(id); - t.replace("DISABLED", userauth.disabled ? "true" : "false"); - t.replace("ISSUPERUSER", userauth.superuser ? "true" : "false"); - - var postdata = Soup.Form.decode_multipart(msg, null, null, null, null); - if(postdata != null && postdata.contains("password1") && postdata.contains("password2")) { - if(postdata["password1"] != postdata["password2"]) { - t.replace("MESSAGE", "
Error! Passwords do not match!
"); - } else if(postdata["password1"] == "") { - t.replace("MESSAGE", "
Error! Empty Password not allowed!
"); - } else { - db.set_user_password(id, postdata["password1"]); - t.replace("MESSAGE", "
Password Changed!
"); - } - } else { - t.replace("MESSAGE", ""); - } - - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_user_invoice(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, int id, uint16 selectedyear, uint8 selectedmonth, uint8 selectedday) { - DateTime start, stop; - - DateYear y = (DateYear) selectedyear; - if(!y.valid() || y > 8000) { - selectedyear = (uint16) new DateTime.now_local().get_year(); - y = (DateYear) selectedyear; - } - - DateMonth m = (DateMonth) selectedmonth; - if(selectedmonth != 0 && !m.valid()) { - selectedmonth = (uint8) new DateTime.now_local().get_month(); - m = (DateMonth) selectedmonth; - } - - DateDay d = (DateDay) selectedday; - if(selectedday != 0 && !d.valid()) { - selectedday = (uint8) new DateTime.now_local().get_day_of_month(); - d = (DateDay) selectedday; - } - - try { - var l = new WebSession(server, msg, path, query, client); - if(id != l.user && !l.superuser) { - handler_403(server, msg, path, query, client); - return; - } - var t = new WebTemplate("users/invoice.html", l); - t.replace("TITLE", "KtT Shop System: User Invoice %llu".printf(id)); - t.menu_set_active("users"); - - /* years, in which something has been purchased by the user */ - var first = db.get_first_purchase(id); - var last = db.get_last_purchase(id); - string years = ""; - for(int i=first.get_year(); i <= last.get_year(); i++) { - years += @"
  • $i
  • "; - } - t.replace("YEARS", years); - t.replace("SELECTEDYEAR", @"$selectedyear"); - - /* months, in which something has been purchased by the user */ - string[] monthnames = { "All Months", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; - string months = @"
  • All Months
  • "; - for(int i=1; i 0 && i < first.get_month()) - months += @"
  • $(monthnames[i])
  • "; - else if(selectedyear < first.get_year()) - months += @"
  • $(monthnames[i])
  • "; - else if(last.get_year() == selectedyear && i > last.get_month()) - months += @"
  • $(monthnames[i])
  • "; - else if(selectedyear > last.get_year()) - months += @"
  • $(monthnames[i])
  • "; - else - months += @"
  • $(monthnames[i])
  • "; - } - t.replace("MONTHS", months); - t.replace("SELECTEDMONTH", @"$(monthnames[selectedmonth])"); - - int dim = m.valid() ? Date.get_days_in_month(m, y) : 0; - string days = @"
  • All Days
  • "; - for(int i=1; i<=dim; i++) { - if(first.get_year() == selectedyear && first.get_month() == selectedmonth && i < first.get_day_of_month()) - days += @"
  • $i
  • "; - else if(selectedyear < first.get_year() || (selectedyear == first.get_year() && selectedmonth < first.get_month())) - days += @"
  • $i
  • "; - else if(last.get_year() == selectedyear && last.get_month() == selectedmonth && i > last.get_day_of_month()) - days += @"
  • $i
  • "; - else if(selectedyear > last.get_year() || (selectedyear == last.get_year() && selectedmonth > last.get_month())) - days += @"
  • $i
  • "; - else - days += @"
  • $i
  • "; - } - t.replace("DAYS", days); - if(selectedday > 0) - t.replace("SELECTEDDAY", @"$selectedday"); - else - t.replace("SELECTEDDAY", "All Days"); - - if(selectedday != 0) { - start = new DateTime.local(selectedyear, selectedmonth, selectedday, 8, 0, 0); - stop = start.add_days(1); - } else if(selectedmonth != 0) { - start = new DateTime.local(selectedyear, selectedmonth, 1, 0, 0, 0); - stop = start.add_months(1); - } else { - start = new DateTime.local(selectedyear, 1, 1, 0, 0, 0); - stop = start.add_years(1); - } - - string table = ""; - Price sum = 0; - foreach(var e in db.get_invoice(id, start.to_unix(), stop.to_unix())) { - var timestamp = new DateTime.from_unix_utc(e.timestamp); - var date = timestamp.format("%d.%m.%Y"); - var time = timestamp.format("%H:%M:%S"); - var product = e.product.name; - var price = e.price; - table += @"$date$time$product$price€"; - sum += e.price; - } - - t.replace("DATA", table); - t.replace("SUM", @"$sum €"); - - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_products(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - string[] pathparts = path.split("/"); - - if(pathparts.length <= 2 || pathparts[2] == "") { - handler_product_list(server, msg, path, query, client); - } else { - uint64 id = uint64.parse(pathparts[2]); - - if(pathparts.length <= 3) { - handler_product_entry(server, msg, path, query, client, id); - } else { - switch(pathparts[3]) { - case "restock": - handler_product_restock(server, msg, path, query, client, id); - break; - case "newprice": - handler_product_newprice(server, msg, path, query, client, id); - break; - default: - handler_product_entry(server, msg, path, query, client, id); - break; - } - } - } - } - - void handler_product_list(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var l = new WebSession(server, msg, path, query, client); - var t = new WebTemplate("products/index.html", l); - t.replace("TITLE", "KtT Shop System: Product List"); - t.menu_set_active("products"); - - string table = ""; - foreach(var e in db.get_stock()) { - table += "%s%s%d%s€%s€".printf( - e.id, e.id, e.id, e.name, e.amount, e.memberprice, e.guestprice - ); - } - - t.replace("DATA", table); - - if(l.superuser) - t.replace("NEWPRODUCT", "block"); - else - t.replace("NEWPRODUCT", "none"); - - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_product_entry(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, uint64 id) { - try { - var l = new WebSession(server, msg, path, query, client); - var t = new WebTemplate("products/entry.html", l); - t.replace("TITLE", "KtT Shop System: Product %llu".printf(id)); - t.menu_set_active("products"); - - /* ean */ - t.replace("EAN", "%llu".printf(id)); - - /* name */ - string name = db.get_product_name(id); - t.replace("NAME", name); - - /* amount */ - t.replace("AMOUNT", "%d".printf(db.get_product_amount(id))); - - if(l.superuser) - t.replace("ISADMIN", "block"); - else - t.replace("ISADMIN", "none"); - - /* prices */ - string prices = ""; - foreach(var e in db.get_prices(id)) { - var time = new DateTime.from_unix_local(e.valid_from); - prices += @"%s$(e.memberprice)€$(e.guestprice)€".printf( - time.format("%Y-%m-%d %H:%M") - ); - } - t.replace("PRICES", prices); - - /* restocks */ - string restocks = ""; - foreach(var e in db.get_restocks(id)) { - var time = new DateTime.from_unix_local(e.timestamp); - var supplier = db.get_supplier(e.supplier).name; - if(supplier == "Unknown") - supplier = ""; - string bbd; - if(e.best_before_date > 0) - bbd = (new DateTime.from_unix_local(e.best_before_date)).format("%Y-%m-%d"); - else - bbd = ""; - restocks += "%s%d%s€%s%s".printf( - time.format("%Y-%m-%d %H:%M"), e.amount, e.price, supplier, bbd - ); - } - t.replace("RESTOCKS", restocks); - - /* suppliers */ - string suppliers = ""; - foreach(var e in db.get_supplier_list()) { - suppliers += "".printf(e.id, e.name); - } - t.replace("SUPPLIERS", suppliers); - - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_products_new(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var session = new WebSession(server, msg, path, query, client); - var template = new WebTemplate("products/new.html", session); - template.replace("TITLE", "KtT Shop System: New Product"); - template.menu_set_active("products"); - - if(!session.superuser) { - handler_403(server, msg, path, query, client); - return; - } - - if(query != null && query.contains("name") && query.contains("id") && query.contains("memberprice") && query.contains("guestprice")) { - var name = query["name"]; - var ean = uint64.parse(query["id"]); - Price memberprice = Price.parse(query["memberprice"]); - Price guestprice = Price.parse(query["guestprice"]); - - if(ean > 0 && memberprice > 0 && guestprice > 0 && db.new_product(ean, name, memberprice, guestprice)) { - template.replace("NAME", name); - template.replace("EAN", @"$ean"); - template.replace("MEMBERPRICE", @"$memberprice€"); - template.replace("GUESTPRICE", @"$guestprice€"); - template.replace("NEW.OK", "block"); - template.replace("NEW.FAIL", "none"); - } else { - template.replace("NAME", "..."); - template.replace("NEW.OK", "none"); - template.replace("NEW.FAIL", "block"); - } - } else { - template.replace("NAME", "..."); - template.replace("NEW.OK", "none"); - template.replace("NEW.FAIL", "block"); - } - - msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_product_restock(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, uint64 id) { - try { - var session = new WebSession(server, msg, path, query, client); - - if(!session.superuser) { - handler_403(server, msg, path, query, client); - return; - } - - var template = new WebTemplate("products/restock.html", session); - template.replace("TITLE", "KtT Shop System: Restock Product %llu".printf(id)); - template.replace("NAME", db.get_product_name(id)); - template.menu_set_active("products"); - - if(query != null && query.contains("amount") && query.contains("price")) { - int amount = int.parse(query["amount"]); - int supplier = int.parse(query["supplier"]); - string best_before_date = query["best_before_date"]; - Price price = Price.parse(query["price"]); - DateTime bbd; - - var dateparts = best_before_date.split("-"); - if(dateparts.length == 3) { - bbd = new DateTime.local(int.parse(dateparts[0]), int.parse(dateparts[1]), int.parse(dateparts[2]), 0, 0, 0); - } else { - bbd = new DateTime.from_unix_local(0); - } - - if(amount >= 1 && price >= 1) { - if(db.restock(session.user, id, amount, price, supplier, bbd.to_unix())) { - template.replace("AMOUNT", @"$amount"); - template.replace("PRICE", @"$price"); - template.replace("BESTBEFORE", bbd.format("%Y-%m-%d")); - template.replace("SUPPLIER", db.get_supplier(supplier).name); - template.replace("RESTOCK.OK", "block"); - template.replace("RESTOCK.FAIL", "none"); - msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); - return; - } - } - } - - template.replace("RESTOCK.OK", "none"); - template.replace("RESTOCK.FAIL", "block"); - msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); - return; - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_product_newprice(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, uint64 id) { - try { - var session = new WebSession(server, msg, path, query, client); - int64 timestamp = (new DateTime.now_utc()).to_unix(); - - if(!session.superuser) { - handler_403(server, msg, path, query, client); - return; - } - - var template = new WebTemplate("products/newprice.html", session); - template.replace("TITLE", "KtT Shop System: New Price for Product %llu".printf(id)); - template.replace("NAME", db.get_product_name(id)); - template.menu_set_active("products"); - - if(query != null && query.contains("guest") && query.contains("member")) { - Price member = Price.parse(query["member"]); - Price guest = Price.parse(query["guest"]); - - if(guest >= 1 && member >= 1) { - if(db.new_price(id, timestamp, member, guest)) { - template.replace("GUEST", @"$guest"); - template.replace("MEMBER", @"$member"); - template.replace("NEWPRICE.OK", "block"); - template.replace("NEWPRICE.FAIL", "none"); - msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); - return; - } - } - } - - template.replace("NEWPRICE.OK", "none"); - template.replace("NEWPRICE.FAIL", "block"); - msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); - return; - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - - void handler_stats(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var l = new WebSession(server, msg, path, query, client); - var t = new WebTemplate("stats/index.html", l); - t.replace("TITLE", "KtT Shop System: Statistics"); - t.menu_set_active("stats"); - - var stats = db.get_stats_info(); - - t.replace("NUMBER_OF_ARTICLES", @"$(stats.count_articles)"); - t.replace("NUMBER_OF_USERS", @"$(stats.count_users)"); - t.replace("STOCK_VALUE", @"$(stats.stock_value)€"); - t.replace("TOTAL_SALES", @"$(stats.sales_total)€"); - t.replace("TOTAL_PROFIT", @"$(stats.profit_total)€"); - t.replace("SALES_TODAY", @"$(stats.sales_today)€"); - t.replace("PROFIT_TODAY", @"$(stats.profit_today)€"); - t.replace("SALES_THIS_MONTH", @"$(stats.sales_this_month)€"); - t.replace("PROFIT_THIS_MONTH", @"$(stats.profit_this_month)€"); - t.replace("SALES_PER_DAY", @"$(stats.sales_per_day)€"); - t.replace("PROFIT_PER_DAY", @"$(stats.profit_per_day)€"); - t.replace("SALES_PER_MONTH", @"$(stats.sales_per_month)€"); - t.replace("PROFIT_PER_MONTH", @"$(stats.profit_per_month)€"); - - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_stats_stock(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var l = new WebSession(server, msg, path, query, client); - var t = new WebTemplate("stats/stock.html", l); - string data = db.get_stats_stock().json; - t.replace("DATA", data); - t.replace("TITLE", "KtT Shop System: Statistics: Stock"); - t.menu_set_active("stats"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_stats_profit_per_day(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var l = new WebSession(server, msg, path, query, client); - var t = new WebTemplate("stats/profit_per_day.html", l); - string data = db.get_stats_profit_per_day().json; - t.replace("DATA", data); - t.replace("TITLE", "KtT Shop System: Statistics: Profit"); - t.menu_set_active("stats"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_stats_profit_per_weekday(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var l = new WebSession(server, msg, path, query, client); - var t = new WebTemplate("stats/profit_per_weekday.html", l); - string data = db.get_stats_profit_per_weekday().json; - t.replace("DATA", data); - t.replace("TITLE", "KtT Shop System: Statistics: Profit/Weekday"); - t.menu_set_active("stats"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_stats_profit_per_product(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var l = new WebSession(server, msg, path, query, client); - var t = new WebTemplate("stats/profit_per_product.html", l); - string data = db.get_stats_profit_per_products().json; - t.replace("DATA", data); - t.replace("TITLE", "KtT Shop System: Statistics: Profit/Product"); - t.menu_set_active("stats"); - msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_js(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var t = new WebTemplate.DATA(path); - msg.set_response("text/javascript", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_css(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var t = new WebTemplate.DATA(path); - msg.set_response("text/css", Soup.MemoryUse.COPY, t.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - void handler_img(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var f = File.new_for_path("templates/"+path); - uint8[] data = null; - - if(f.query_exists() && f.load_contents(null, out data, null)) { - msg.set_response("image/png", Soup.MemoryUse.COPY, data); - return; - } - } catch(Error e) { - error("there has been some error: %s!\n", e.message); - } - - handler_404(server, msg, path, query, client); - return; - } - - void handler_404(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - string result = "Page not Found\n"; - msg.set_status(404); - msg.set_response("text/plain", Soup.MemoryUse.COPY, result.data); - } - - void handler_403(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var session = new WebSession(server, msg, path, query, client); - var template = new WebTemplate("errors/403.html", session); - template.replace("TITLE", "Access Denied"); - template.menu_set_active(""); - msg.set_status(403); - msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } } - - void handler_todo(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - try { - var session = new WebSession(server, msg, path, query, client); - var template = new WebTemplate("errors/todo.html", session); - template.replace("TITLE", "KtT Shop System: ToDo"); - template.menu_set_active(""); - msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); - } catch(TemplateError e) { - stderr.printf(e.message+"\n"); - handler_404(server, msg, path, query, client); - } - } - - public WebServer(int port = 8080) { - srv = new Soup.Server(Soup.SERVER_PORT, port); - - /* index */ - srv.add_handler("/", handler_default); - - /* logout */ - srv.add_handler("/logout", handler_logout); - - /* data (js, css, img) */ - srv.add_handler("/js", handler_js); - srv.add_handler("/css", handler_css); - srv.add_handler("/img", handler_img); - - /* products */ - srv.add_handler("/products", handler_products); - srv.add_handler("/products/new", handler_products_new); - - /* stats */ - srv.add_handler("/stats", handler_stats); - srv.add_handler("/stats/stock", handler_stats_stock); - srv.add_handler("/stats/profit_per_day", handler_stats_profit_per_day); - srv.add_handler("/stats/profit_per_weekday", handler_stats_profit_per_weekday); - srv.add_handler("/stats/profit_per_product", handler_stats_profit_per_product); - - /* users */ - srv.add_handler("/users", handler_users); - srv.add_handler("/users/import", handler_user_import); - srv.add_handler("/users/import-pgp", handler_user_pgp_import); - - srv.run_async(); - } -} diff --git a/src/web/Makefile b/src/web/Makefile new file mode 100644 index 0000000..cc9d77e --- /dev/null +++ b/src/web/Makefile @@ -0,0 +1,9 @@ +all: web + +web: main.vala web.vala websession.vala csv.vala template.vala ../database/db-interface.vala ../pgp/pgp-interface.vala ../price.vapi + valac -o $@ --vapidir=../../vapi --pkg gee-1.0 --pkg gio-2.0 --pkg libsoup-2.4 --pkg posix --pkg libarchive --pkg gpgme $^ + +clean: + rm -rf web + +.PHONY: all clean diff --git a/src/web/csv.vala b/src/web/csv.vala new file mode 100644 index 0000000..4e32a0a --- /dev/null +++ b/src/web/csv.vala @@ -0,0 +1,75 @@ +/* Copyright 2012, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public class CSVMemberFile { + private UserInfo[] members; + + public Gee.List missing_unblocked_members() { + var result = new Gee.ArrayList(); + var dbusers = db.get_member_ids(); + + foreach(var u in dbusers) { + bool found=false; + foreach(var m in members) { + if(u == m.id) { + found=true; + break; + } + } + + if(!found) { + if(!db.user_is_disabled(u)) + result.add(u); + } + } + + return result; + } + + private string[] csv_split(string line) { + return /;(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/.split(line); + } + + private string csv_value(string value) { + if(value[0] == '"' && value[value.length-1] == '"') + return value.substring(1,value.length-2); + else + return value; + } + + public CSVMemberFile(string data) { + foreach(var line in data.split("\n")) { + var linedata = csv_split(line); + if(linedata.length >= 9) { + var m = UserInfo(); + m.id = int.parse(csv_value(linedata[0])); + m.email = csv_value(linedata[1]); + m.firstname = csv_value(linedata[2]); + m.lastname = csv_value(linedata[3]); + m.street = csv_value(linedata[4]); + m.postcode = int.parse(csv_value(linedata[5])); + m.city = csv_value(linedata[6]); + m.gender = csv_value(linedata[7]) == "m" ? "masculinum" : csv_value(linedata[7]) == "w" ? "femininum" : "unknown"; + m.pgp = csv_value(linedata[8]); + if(csv_value(linedata[0]) != "EXTERNEMITGLIEDSNUMMER") + members += m; + } + } + } + + public UserInfo[] get_members() { + return members; + } +} diff --git a/src/web/main.vala b/src/web/main.vala new file mode 100644 index 0000000..07dbb03 --- /dev/null +++ b/src/web/main.vala @@ -0,0 +1,35 @@ +/* Copyright 2013, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +Database db; +public CSVMemberFile csvimport; +public PGP pgp; + +public static int main(string[] args) { + try { + db = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.Database", "/io/mainframe/shopsystem/database"); + pgp = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.PGP", "/io/mainframe/shopsystem/pgp"); + } catch(IOError e) { + error("IOError: %s\n", e.message); + } + + /* attach WebServer to MainLoop */ + new WebServer(); + + /* start MainLoop */ + new MainLoop().run(); + + return 0; +} diff --git a/src/web/template.vala b/src/web/template.vala new file mode 100644 index 0000000..8fe56d0 --- /dev/null +++ b/src/web/template.vala @@ -0,0 +1,104 @@ +/* Copyright 2012, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public errordomain TemplateError { + NOT_FOUND, + NOT_LOADABLE, + NOT_ALLOWED, +} + +public class WebTemplate { + private string template; + public uint8[] data { get { return template.data; } } + + public WebTemplate(string file, WebSession login) throws TemplateError { + var b = File.new_for_path("templates/base.html"); + var m = File.new_for_path("templates/menu.html"); + var f = File.new_for_path("templates/"+file); + File fauth; + + if(login.logged_in) + fauth = File.new_for_path("templates/menu_logout.html"); + else + fauth = File.new_for_path("templates/menu_login.html"); + + uint8[] basis, menu, template, auth; + + if(!b.query_exists()) + throw new TemplateError.NOT_FOUND("templates/base.html not found!"); + + if(!m.query_exists()) + throw new TemplateError.NOT_FOUND("templates/menu.html not found!"); + + if(!fauth.query_exists()) + throw new TemplateError.NOT_FOUND(fauth.get_path()+" not found!"); + + if(!f.query_exists()) + throw new TemplateError.NOT_FOUND("templates/"+file+" not found!"); + + try { + if(!b.load_contents(null, out basis, null)) + throw new TemplateError.NOT_LOADABLE("templates/base.html could not be loaded!"); + if(!m.load_contents(null, out menu, null)) + throw new TemplateError.NOT_LOADABLE("templates/menu.html could not be loaded!"); + if(!fauth.load_contents(null, out auth, null)) + throw new TemplateError.NOT_LOADABLE(fauth.get_path()+" could not be loaded!"); + if(!f.load_contents(null, out template, null)) + throw new TemplateError.NOT_LOADABLE("templates/"+file+" could not be loaded!"); + } catch(Error e) { + throw new TemplateError.NOT_LOADABLE("could not load templates!"); + } + + this.template = ((string) basis).replace("{{{NAVBAR}}}", ((string) menu)); + this.template = this.template.replace("{{{AUTH}}}", ((string) auth)); + this.template = this.template.replace("{{{CONTENT}}}", ((string) template)); + this.template = this.template.replace("{{{USERNAME}}}", login.name); + this.template = this.template.replace("{{{USERID}}}", "%d".printf(login.user)); + this.template = this.template.replace("{{{SUPERUSER}}}", login.superuser ? "" : "hidden"); + } + + public WebTemplate.DATA(string file) throws TemplateError { + var f = File.new_for_path("templates/"+file); + uint8[] template; + + if(!f.query_exists()) + throw new TemplateError.NOT_FOUND("templates/"+file+" not found!"); + + try { + if(!f.load_contents(null, out template, null)) + throw new TemplateError.NOT_LOADABLE("templates/"+file+" could not be loaded!"); + } catch(Error e) { + throw new TemplateError.NOT_LOADABLE("could not load templates!"); + } + + this.template = (string) template; + } + + public void replace(string key, string value) { + template = template.replace("{{{"+key+"}}}", value); + } + + public void menu_set_active(string key) { + try { + var regex_active = new Regex("{{{MENU\\."+key+"}}}"); + var regex_other = new Regex("{{{MENU\\..*}}}"); + + template = regex_active.replace(template, -1, 0, "active"); + template = regex_other.replace(template, -1, 0, ""); + } catch(RegexError e) { + warning ("%s", e.message); + } + } +} diff --git a/src/web/web.vala b/src/web/web.vala new file mode 100644 index 0000000..9dfb1ae --- /dev/null +++ b/src/web/web.vala @@ -0,0 +1,858 @@ +/* Copyright 2012, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public class WebServer { + private Soup.Server srv; + + void handler_default(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("index.html", l); + t.replace("TITLE", "KtT Shop System"); + t.menu_set_active("home"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_logout(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + l.logout(); + var t = new WebTemplate("logout.html", l); + t.replace("TITLE", "KtT Shop System"); + t.menu_set_active("home"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_users(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + string[] pathparts = path.split("/"); + + if(pathparts.length <= 2) { + handler_user_list(server, msg, path, query, client); + } else { + int id = int.parse(pathparts[2]); + + if(pathparts.length <= 3) { + handler_user_entry(server, msg, path, query, client, id); + } else { + switch(pathparts[3]) { + case "invoice": + uint16 selectedyear = (pathparts.length >= 5 && pathparts[4] != "") ? (uint16) int.parse(pathparts[4]) : (uint16) (new DateTime.now_local()).get_year(); + uint8 selectedmonth = (pathparts.length >= 6 && pathparts[5] != "") ? (uint8) int.parse(pathparts[5]) : (uint8) (new DateTime.now_local()).get_month(); + uint8 selectedday = (pathparts.length >= 7 && pathparts[6] != "") ? (uint8) int.parse(pathparts[6]) : (uint8) (new DateTime.now_local()).get_day_of_month(); + handler_user_invoice(server, msg, path, query, client, id, selectedyear, selectedmonth, selectedday); + break; + case "stats": + handler_todo(server, msg, path, query, client); + break; + default: + handler_404(server, msg, path, query, client); + break; + } + } + } + } + + void handler_user_list(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + if(!session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + + var t = new WebTemplate("users/index.html", session); + t.replace("TITLE", "KtT Shop System: User"); + t.menu_set_active("users"); + var data = ""; + foreach(var m in db.get_member_ids()) { + try { + var name = db.get_username(m); + data += @"$m$name"; + } catch(DatabaseError e) { + /* TODO: write error to log */ + } + } + t.replace("DATA", data); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_user_pgp_import(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + if(!session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + + var t = new WebTemplate("users/import-pgp.html", session); + t.replace("TITLE", "KtT Shop System: PGP Key Import"); + t.menu_set_active("users"); + + Soup.Buffer filedata; + var postdata = Soup.Form.decode_multipart(msg, "file", null, null, out filedata); + + if(postdata == null || !postdata.contains("step")) { + t.replace("DATA", ""); + t.replace("STEP1", "block"); + t.replace("STEP2", "none"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + return; + } else { + var keylist = pgp.import_archive(filedata.data); + string keylisttemplate; + + if(keylist.length > 0) { + keylisttemplate = "
      \n"; + foreach(string s in keylist) { + keylisttemplate += "
    • "+s+"
    • \n"; + } + keylisttemplate += "
    \n"; + } else { + keylisttemplate = "

    No new keys!

    "; + } + + t.replace("DATA", keylisttemplate); + t.replace("STEP1", "none"); + t.replace("STEP2", "block"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + return; + } + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_user_import(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + if(!session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + var t = new WebTemplate("users/import.html", session); + t.replace("TITLE", "KtT Shop System: User Import"); + t.menu_set_active("users"); + + Soup.Buffer filedata; + var postdata = Soup.Form.decode_multipart(msg, "file", null, null, out filedata); + if(postdata == null || !postdata.contains("step")) { + t.replace("DATA1", ""); + t.replace("DATA2", ""); + t.replace("STEP1", "block"); + t.replace("STEP2", "none"); + t.replace("STEP23", "none"); + t.replace("STEP3", "none"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + return; + } else { + if(filedata != null) { + string text = (string) filedata.data; + text = text.substring(0,(long) filedata.length-1); + csvimport = new CSVMemberFile(text); + } + + if(csvimport == null) { + handler_403(server, msg, path, query, client); + return; + } + + /* new & changed users */ + string data1 = ""; + foreach(var member in csvimport.get_members()) { + if(db.user_exists(member.id) && db.user_equals(member)) { + var dbmember = db.get_user_info(member.id); + data1 += @"$(dbmember.id)$(dbmember.firstname)$(dbmember.lastname)$(dbmember.email)$(dbmember.gender)$(dbmember.street)$(dbmember.postcode)$(dbmember.city)$(dbmember.pgp)"; + } + if(!db.user_exists(member.id) || !db.user_equals(member)) { + data1 += @"$(member.id)$(member.firstname)$(member.lastname)$(member.email)$(member.gender)$(member.street)$(member.postcode)$(member.city)$(member.pgp)"; + } + } + t.replace("DATA1", data1); + + /* removed users */ + Gee.List blockedusers = csvimport.missing_unblocked_members(); + if(blockedusers.size > 0) { + string data2 = "Disabling the following users, because they are no longer found in the member CSV:
      "; + + foreach(var member in blockedusers) { + try { + string name = db.get_username(member); + data2 += @"
    • $name ($member)
    • "; + } catch(Error e) {} + } + + data2 += "
    "; + t.replace("DATA2", data2); + } else { + t.replace("DATA2", ""); + } + + /* show correct blocks */ + t.replace("STEP1", "none"); + t.replace("STEP23", "block"); + if(postdata["step"] == "1") { + t.replace("STEP2", "block"); + t.replace("STEP3", "none"); + } else { + t.replace("STEP2", "none"); + t.replace("STEP3", "block"); + } + + if(postdata["step"] == "2") { + /* disable users */ + foreach(var member in csvimport.missing_unblocked_members()) { + db.user_disable(member, true); + } + + /* update users */ + foreach(var member in csvimport.get_members()) { + db.user_replace(member); + } + + csvimport = null; + } + } + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_user_entry(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, int id) { + try { + var session = new WebSession(server, msg, path, query, client); + if(id != session.user && !session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + var t = new WebTemplate("users/entry.html", session); + t.replace("TITLE", "KtT Shop System: User Info %llu".printf(id)); + t.menu_set_active("users"); + + var userinfo = db.get_user_info(id); + + t.replace("UID", "%d".printf(userinfo.id)); + t.replace("FIRSTNAME", userinfo.firstname); + t.replace("LASTNAME", userinfo.lastname); + t.replace("EMAIL", userinfo.email); + t.replace("GENDER", userinfo.gender); + t.replace("STREET", userinfo.street); + t.replace("POSTALCODE", "%d".printf(userinfo.postcode)); + t.replace("CITY", userinfo.city); + t.replace("PGPKEYID", userinfo.pgp); + + var userauth = db.get_user_auth(id); + t.replace("DISABLED", userauth.disabled ? "true" : "false"); + t.replace("ISSUPERUSER", userauth.superuser ? "true" : "false"); + + var postdata = Soup.Form.decode_multipart(msg, null, null, null, null); + if(postdata != null && postdata.contains("password1") && postdata.contains("password2")) { + if(postdata["password1"] != postdata["password2"]) { + t.replace("MESSAGE", "
    Error! Passwords do not match!
    "); + } else if(postdata["password1"] == "") { + t.replace("MESSAGE", "
    Error! Empty Password not allowed!
    "); + } else { + db.set_user_password(id, postdata["password1"]); + t.replace("MESSAGE", "
    Password Changed!
    "); + } + } else { + t.replace("MESSAGE", ""); + } + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_user_invoice(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, int id, uint16 selectedyear, uint8 selectedmonth, uint8 selectedday) { + DateTime start, stop; + + DateYear y = (DateYear) selectedyear; + if(!y.valid() || y > 8000) { + selectedyear = (uint16) new DateTime.now_local().get_year(); + y = (DateYear) selectedyear; + } + + DateMonth m = (DateMonth) selectedmonth; + if(selectedmonth != 0 && !m.valid()) { + selectedmonth = (uint8) new DateTime.now_local().get_month(); + m = (DateMonth) selectedmonth; + } + + DateDay d = (DateDay) selectedday; + if(selectedday != 0 && !d.valid()) { + selectedday = (uint8) new DateTime.now_local().get_day_of_month(); + d = (DateDay) selectedday; + } + + try { + var l = new WebSession(server, msg, path, query, client); + if(id != l.user && !l.superuser) { + handler_403(server, msg, path, query, client); + return; + } + var t = new WebTemplate("users/invoice.html", l); + t.replace("TITLE", "KtT Shop System: User Invoice %llu".printf(id)); + t.menu_set_active("users"); + + /* years, in which something has been purchased by the user */ + var first = new DateTime.from_unix_local(db.get_first_purchase(id)); + var last = new DateTime.from_unix_local(db.get_last_purchase(id)); + string years = ""; + for(int i=first.get_year(); i <= last.get_year(); i++) { + years += @"
  • $i
  • "; + } + t.replace("YEARS", years); + t.replace("SELECTEDYEAR", @"$selectedyear"); + + /* months, in which something has been purchased by the user */ + string[] monthnames = { "All Months", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; + string months = @"
  • All Months
  • "; + for(int i=1; i 0 && i < first.get_month()) + months += @"
  • $(monthnames[i])
  • "; + else if(selectedyear < first.get_year()) + months += @"
  • $(monthnames[i])
  • "; + else if(last.get_year() == selectedyear && i > last.get_month()) + months += @"
  • $(monthnames[i])
  • "; + else if(selectedyear > last.get_year()) + months += @"
  • $(monthnames[i])
  • "; + else + months += @"
  • $(monthnames[i])
  • "; + } + t.replace("MONTHS", months); + t.replace("SELECTEDMONTH", @"$(monthnames[selectedmonth])"); + + int dim = m.valid() ? Date.get_days_in_month(m, y) : 0; + string days = @"
  • All Days
  • "; + for(int i=1; i<=dim; i++) { + if(first.get_year() == selectedyear && first.get_month() == selectedmonth && i < first.get_day_of_month()) + days += @"
  • $i
  • "; + else if(selectedyear < first.get_year() || (selectedyear == first.get_year() && selectedmonth < first.get_month())) + days += @"
  • $i
  • "; + else if(last.get_year() == selectedyear && last.get_month() == selectedmonth && i > last.get_day_of_month()) + days += @"
  • $i
  • "; + else if(selectedyear > last.get_year() || (selectedyear == last.get_year() && selectedmonth > last.get_month())) + days += @"
  • $i
  • "; + else + days += @"
  • $i
  • "; + } + t.replace("DAYS", days); + if(selectedday > 0) + t.replace("SELECTEDDAY", @"$selectedday"); + else + t.replace("SELECTEDDAY", "All Days"); + + if(selectedday != 0) { + start = new DateTime.local(selectedyear, selectedmonth, selectedday, 8, 0, 0); + stop = start.add_days(1); + } else if(selectedmonth != 0) { + start = new DateTime.local(selectedyear, selectedmonth, 1, 0, 0, 0); + stop = start.add_months(1); + } else { + start = new DateTime.local(selectedyear, 1, 1, 0, 0, 0); + stop = start.add_years(1); + } + + string table = ""; + Price sum = 0; + foreach(var e in db.get_invoice(id, start.to_unix(), stop.to_unix())) { + var timestamp = new DateTime.from_unix_utc(e.timestamp); + var date = timestamp.format("%d.%m.%Y"); + var time = timestamp.format("%H:%M:%S"); + var product = e.product.name; + var price = e.price; + table += @"$date$time$product$price€"; + sum += e.price; + } + + t.replace("DATA", table); + t.replace("SUM", @"$sum €"); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_products(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + string[] pathparts = path.split("/"); + + if(pathparts.length <= 2 || pathparts[2] == "") { + handler_product_list(server, msg, path, query, client); + } else { + uint64 id = uint64.parse(pathparts[2]); + + if(pathparts.length <= 3) { + handler_product_entry(server, msg, path, query, client, id); + } else { + switch(pathparts[3]) { + case "restock": + handler_product_restock(server, msg, path, query, client, id); + break; + case "newprice": + handler_product_newprice(server, msg, path, query, client, id); + break; + default: + handler_product_entry(server, msg, path, query, client, id); + break; + } + } + } + } + + void handler_product_list(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("products/index.html", l); + t.replace("TITLE", "KtT Shop System: Product List"); + t.menu_set_active("products"); + + string table = ""; + foreach(var e in db.get_stock()) { + table += "%s%s%d%s€%s€".printf( + e.id, e.id, e.id, e.name, e.amount, e.memberprice, e.guestprice + ); + } + + t.replace("DATA", table); + + if(l.superuser) + t.replace("NEWPRODUCT", "block"); + else + t.replace("NEWPRODUCT", "none"); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_product_entry(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, uint64 id) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("products/entry.html", l); + t.replace("TITLE", "KtT Shop System: Product %llu".printf(id)); + t.menu_set_active("products"); + + /* ean */ + t.replace("EAN", "%llu".printf(id)); + + /* name */ + string name = db.get_product_name(id); + t.replace("NAME", name); + + /* amount */ + t.replace("AMOUNT", "%d".printf(db.get_product_amount(id))); + + if(l.superuser) + t.replace("ISADMIN", "block"); + else + t.replace("ISADMIN", "none"); + + /* prices */ + string prices = ""; + foreach(var e in db.get_prices(id)) { + var time = new DateTime.from_unix_local(e.valid_from); + prices += @"%s$(e.memberprice)€$(e.guestprice)€".printf( + time.format("%Y-%m-%d %H:%M") + ); + } + t.replace("PRICES", prices); + + /* restocks */ + string restocks = ""; + foreach(var e in db.get_restocks(id)) { + var time = new DateTime.from_unix_local(e.timestamp); + var supplier = db.get_supplier(e.supplier).name; + if(supplier == "Unknown") + supplier = ""; + string bbd; + if(e.best_before_date > 0) + bbd = (new DateTime.from_unix_local(e.best_before_date)).format("%Y-%m-%d"); + else + bbd = ""; + restocks += "%s%d%s€%s%s".printf( + time.format("%Y-%m-%d %H:%M"), e.amount, e.price, supplier, bbd + ); + } + t.replace("RESTOCKS", restocks); + + /* suppliers */ + string suppliers = ""; + foreach(var e in db.get_supplier_list()) { + suppliers += "".printf(e.id, e.name); + } + t.replace("SUPPLIERS", suppliers); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_products_new(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + var template = new WebTemplate("products/new.html", session); + template.replace("TITLE", "KtT Shop System: New Product"); + template.menu_set_active("products"); + + if(!session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + + if(query != null && query.contains("name") && query.contains("id") && query.contains("memberprice") && query.contains("guestprice")) { + var name = query["name"]; + var ean = uint64.parse(query["id"]); + Price memberprice = Price.parse(query["memberprice"]); + Price guestprice = Price.parse(query["guestprice"]); + + if(ean > 0 && memberprice > 0 && guestprice > 0 && db.new_product(ean, name, memberprice, guestprice)) { + template.replace("NAME", name); + template.replace("EAN", @"$ean"); + template.replace("MEMBERPRICE", @"$memberprice€"); + template.replace("GUESTPRICE", @"$guestprice€"); + template.replace("NEW.OK", "block"); + template.replace("NEW.FAIL", "none"); + } else { + template.replace("NAME", "..."); + template.replace("NEW.OK", "none"); + template.replace("NEW.FAIL", "block"); + } + } else { + template.replace("NAME", "..."); + template.replace("NEW.OK", "none"); + template.replace("NEW.FAIL", "block"); + } + + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_product_restock(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, uint64 id) { + try { + var session = new WebSession(server, msg, path, query, client); + + if(!session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + + var template = new WebTemplate("products/restock.html", session); + template.replace("TITLE", "KtT Shop System: Restock Product %llu".printf(id)); + template.replace("NAME", db.get_product_name(id)); + template.menu_set_active("products"); + + if(query != null && query.contains("amount") && query.contains("price")) { + int amount = int.parse(query["amount"]); + int supplier = int.parse(query["supplier"]); + string best_before_date = query["best_before_date"]; + Price price = Price.parse(query["price"]); + DateTime bbd; + + var dateparts = best_before_date.split("-"); + if(dateparts.length == 3) { + bbd = new DateTime.local(int.parse(dateparts[0]), int.parse(dateparts[1]), int.parse(dateparts[2]), 0, 0, 0); + } else { + bbd = new DateTime.from_unix_local(0); + } + + if(amount >= 1 && price >= 1) { + if(db.restock(session.user, id, amount, price, supplier, bbd.to_unix())) { + template.replace("AMOUNT", @"$amount"); + template.replace("PRICE", @"$price"); + template.replace("BESTBEFORE", bbd.format("%Y-%m-%d")); + template.replace("SUPPLIER", db.get_supplier(supplier).name); + template.replace("RESTOCK.OK", "block"); + template.replace("RESTOCK.FAIL", "none"); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + return; + } + } + } + + template.replace("RESTOCK.OK", "none"); + template.replace("RESTOCK.FAIL", "block"); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + return; + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_product_newprice(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, uint64 id) { + try { + var session = new WebSession(server, msg, path, query, client); + int64 timestamp = (new DateTime.now_utc()).to_unix(); + + if(!session.superuser) { + handler_403(server, msg, path, query, client); + return; + } + + var template = new WebTemplate("products/newprice.html", session); + template.replace("TITLE", "KtT Shop System: New Price for Product %llu".printf(id)); + template.replace("NAME", db.get_product_name(id)); + template.menu_set_active("products"); + + if(query != null && query.contains("guest") && query.contains("member")) { + Price member = Price.parse(query["member"]); + Price guest = Price.parse(query["guest"]); + + if(guest >= 1 && member >= 1) { + if(db.new_price(id, timestamp, member, guest)) { + template.replace("GUEST", @"$guest"); + template.replace("MEMBER", @"$member"); + template.replace("NEWPRICE.OK", "block"); + template.replace("NEWPRICE.FAIL", "none"); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + return; + } + } + } + + template.replace("NEWPRICE.OK", "none"); + template.replace("NEWPRICE.FAIL", "block"); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + return; + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + +#if 0 + void handler_stats(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/index.html", l); + t.replace("TITLE", "KtT Shop System: Statistics"); + t.menu_set_active("stats"); + + var stats = db.get_stats_info(); + + t.replace("NUMBER_OF_ARTICLES", @"$(stats.count_articles)"); + t.replace("NUMBER_OF_USERS", @"$(stats.count_users)"); + t.replace("STOCK_VALUE", @"$(stats.stock_value)€"); + t.replace("TOTAL_SALES", @"$(stats.sales_total)€"); + t.replace("TOTAL_PROFIT", @"$(stats.profit_total)€"); + t.replace("SALES_TODAY", @"$(stats.sales_today)€"); + t.replace("PROFIT_TODAY", @"$(stats.profit_today)€"); + t.replace("SALES_THIS_MONTH", @"$(stats.sales_this_month)€"); + t.replace("PROFIT_THIS_MONTH", @"$(stats.profit_this_month)€"); + t.replace("SALES_PER_DAY", @"$(stats.sales_per_day)€"); + t.replace("PROFIT_PER_DAY", @"$(stats.profit_per_day)€"); + t.replace("SALES_PER_MONTH", @"$(stats.sales_per_month)€"); + t.replace("PROFIT_PER_MONTH", @"$(stats.profit_per_month)€"); + + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats_stock(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/stock.html", l); + string data = db.get_stats_stock().json; + t.replace("DATA", data); + t.replace("TITLE", "KtT Shop System: Statistics: Stock"); + t.menu_set_active("stats"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats_profit_per_day(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/profit_per_day.html", l); + string data = db.get_stats_profit_per_day().json; + t.replace("DATA", data); + t.replace("TITLE", "KtT Shop System: Statistics: Profit"); + t.menu_set_active("stats"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats_profit_per_weekday(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/profit_per_weekday.html", l); + string data = db.get_stats_profit_per_weekday().json; + t.replace("DATA", data); + t.replace("TITLE", "KtT Shop System: Statistics: Profit/Weekday"); + t.menu_set_active("stats"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_stats_profit_per_product(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var l = new WebSession(server, msg, path, query, client); + var t = new WebTemplate("stats/profit_per_product.html", l); + string data = db.get_stats_profit_per_products().json; + t.replace("DATA", data); + t.replace("TITLE", "KtT Shop System: Statistics: Profit/Product"); + t.menu_set_active("stats"); + msg.set_response("text/html", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } +#endif + + void handler_js(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var t = new WebTemplate.DATA(path); + msg.set_response("text/javascript", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_css(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var t = new WebTemplate.DATA(path); + msg.set_response("text/css", Soup.MemoryUse.COPY, t.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + void handler_img(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var f = File.new_for_path("templates/"+path); + uint8[] data = null; + + if(f.query_exists() && f.load_contents(null, out data, null)) { + msg.set_response("image/png", Soup.MemoryUse.COPY, data); + return; + } + } catch(Error e) { + error("there has been some error: %s!\n", e.message); + } + + handler_404(server, msg, path, query, client); + return; + } + + void handler_404(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + string result = "Page not Found\n"; + msg.set_status(404); + msg.set_response("text/plain", Soup.MemoryUse.COPY, result.data); + } + + void handler_403(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + var template = new WebTemplate("errors/403.html", session); + template.replace("TITLE", "Access Denied"); + template.menu_set_active(""); + msg.set_status(403); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } } + + void handler_todo(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + try { + var session = new WebSession(server, msg, path, query, client); + var template = new WebTemplate("errors/todo.html", session); + template.replace("TITLE", "KtT Shop System: ToDo"); + template.menu_set_active(""); + msg.set_response("text/html", Soup.MemoryUse.COPY, template.data); + } catch(TemplateError e) { + stderr.printf(e.message+"\n"); + handler_404(server, msg, path, query, client); + } + } + + public WebServer(int port = 8080) { + srv = new Soup.Server(Soup.SERVER_PORT, port); + + /* index */ + srv.add_handler("/", handler_default); + + /* logout */ + srv.add_handler("/logout", handler_logout); + + /* data (js, css, img) */ + srv.add_handler("/js", handler_js); + srv.add_handler("/css", handler_css); + srv.add_handler("/img", handler_img); + + /* products */ + srv.add_handler("/products", handler_products); + srv.add_handler("/products/new", handler_products_new); + +#if 0 + /* stats */ + srv.add_handler("/stats", handler_stats); + srv.add_handler("/stats/stock", handler_stats_stock); + srv.add_handler("/stats/profit_per_day", handler_stats_profit_per_day); + srv.add_handler("/stats/profit_per_weekday", handler_stats_profit_per_weekday); + srv.add_handler("/stats/profit_per_product", handler_stats_profit_per_product); +#endif + + /* users */ + srv.add_handler("/users", handler_users); + srv.add_handler("/users/import", handler_user_import); + srv.add_handler("/users/import-pgp", handler_user_pgp_import); + + srv.run_async(); + } +} diff --git a/src/web/websession.vala b/src/web/websession.vala new file mode 100644 index 0000000..5c562c6 --- /dev/null +++ b/src/web/websession.vala @@ -0,0 +1,142 @@ +/* Copyright 2012, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public errordomain WebSessionError { + SESSION_NOT_FOUND, + USER_NOT_FOUND +} + +public class WebSession { + public int user { + get; + private set; + default = 0; + } + public string name { + get; + private set; + default = "Guest"; + } + public bool failed { + get; + private set; + default = false; + } + public bool logged_in { + get; + private set; + default = false; + } + public bool superuser { + get; + private set; + default = false; + } + public bool disabled { + get; + private set; + default = false; + } + + private string generate_session_id(int user) { + const string charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; + string result = ""; + + Random.set_seed((uint32) time_t() + (uint32) Posix.getpid() + (uint32) user); + + for(int i=0; i<19; i++) { + int character_position = Random.int_range(0,charset.length); + string character = charset[character_position].to_string(); + result += character; + } + + /* TODO: make sure, that session id is unique */ + + return result; + } + + private void setup_auth(int user) { + var auth = db.get_user_auth(user); + this.disabled = auth.disabled; + this.superuser = auth.superuser; + this.logged_in = true; + } + + public void logout() { + if(logged_in) { + db.set_sessionid(user, ""); + superuser = false; + logged_in = false; + } + } + + public WebSession(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { + var cookies = Soup.cookies_from_request(msg); + + /* Check for existing session */ + foreach(var cookie in cookies) { + if(cookie.name == "session") { + var sessionid = cookie.value; + + try { + user = db.get_user_by_sessionid(sessionid); + name = db.get_username(user); + setup_auth(user); + return; + } catch(WebSessionError e) { + /* invalid session, ignore */ + } + } + } + + /* check for login query */ + if(query == null || !query.contains("user") || !query.contains("password")) + return; + + /* get credentials */ + var userid = int.parse(query["user"]); + var password = query["password"]; + + /* check credentials */ + if(db.check_user_password(userid, password)) { + /* generate session */ + var sessionid = generate_session_id(userid); + + /* set session in database */ + db.set_sessionid(userid, sessionid); + + /* set session in reply cookie */ + cookies = new SList(); + var sessioncookie = new Soup.Cookie("session", sessionid, "", "/", -1); + sessioncookie.domain = null; + cookies.append(sessioncookie); + Soup.cookies_to_response(cookies, msg); + + /* login successful */ + user = userid; + try { + name = db.get_username(user); + } catch(WebSessionError e) { + name = "Unknown User"; + } + + setup_auth(user); + } else { + /* login failed */ + failed=true; + } + } +} + diff --git a/src/websession.vala b/src/websession.vala deleted file mode 100644 index 5c562c6..0000000 --- a/src/websession.vala +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright 2012, Sebastian Reichel - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -public errordomain WebSessionError { - SESSION_NOT_FOUND, - USER_NOT_FOUND -} - -public class WebSession { - public int user { - get; - private set; - default = 0; - } - public string name { - get; - private set; - default = "Guest"; - } - public bool failed { - get; - private set; - default = false; - } - public bool logged_in { - get; - private set; - default = false; - } - public bool superuser { - get; - private set; - default = false; - } - public bool disabled { - get; - private set; - default = false; - } - - private string generate_session_id(int user) { - const string charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; - string result = ""; - - Random.set_seed((uint32) time_t() + (uint32) Posix.getpid() + (uint32) user); - - for(int i=0; i<19; i++) { - int character_position = Random.int_range(0,charset.length); - string character = charset[character_position].to_string(); - result += character; - } - - /* TODO: make sure, that session id is unique */ - - return result; - } - - private void setup_auth(int user) { - var auth = db.get_user_auth(user); - this.disabled = auth.disabled; - this.superuser = auth.superuser; - this.logged_in = true; - } - - public void logout() { - if(logged_in) { - db.set_sessionid(user, ""); - superuser = false; - logged_in = false; - } - } - - public WebSession(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) { - var cookies = Soup.cookies_from_request(msg); - - /* Check for existing session */ - foreach(var cookie in cookies) { - if(cookie.name == "session") { - var sessionid = cookie.value; - - try { - user = db.get_user_by_sessionid(sessionid); - name = db.get_username(user); - setup_auth(user); - return; - } catch(WebSessionError e) { - /* invalid session, ignore */ - } - } - } - - /* check for login query */ - if(query == null || !query.contains("user") || !query.contains("password")) - return; - - /* get credentials */ - var userid = int.parse(query["user"]); - var password = query["password"]; - - /* check credentials */ - if(db.check_user_password(userid, password)) { - /* generate session */ - var sessionid = generate_session_id(userid); - - /* set session in database */ - db.set_sessionid(userid, sessionid); - - /* set session in reply cookie */ - cookies = new SList(); - var sessioncookie = new Soup.Cookie("session", sessionid, "", "/", -1); - sessioncookie.domain = null; - cookies.append(sessioncookie); - Soup.cookies_to_response(cookies, msg); - - /* login successful */ - user = userid; - try { - name = db.get_username(user); - } catch(WebSessionError e) { - name = "Unknown User"; - } - - setup_auth(user); - } else { - /* login failed */ - failed=true; - } - } -} - -- cgit v1.2.3