From 90f211c5f863e76cc7306b674f4afdb806249e9b Mon Sep 17 00:00:00 2001 From: Relintai Date: Fri, 25 Dec 2020 21:06:33 +0100 Subject: [PATCH] Initial commit. --- .clang-format | 128 +++++++++ .gitignore | 29 ++ HEADS | 1 + LICENSE | 19 ++ Readme.md | 9 + SConstruct | 492 +++++++++++++++++++++++++++++++++ app/rdn_application.cpp | 98 +++++++ app/rdn_application.h | 28 ++ build.config.example | 44 +++ main.cpp | 95 +++++++ www/android-chrome-192x192.png | Bin 0 -> 7312 bytes www/android-chrome-512x512.png | Bin 0 -> 18067 bytes www/apple-touch-icon.png | Bin 0 -> 6382 bytes www/favicon-16x16.png | Bin 0 -> 427 bytes www/favicon-32x32.png | Bin 0 -> 900 bytes www/favicon.ico | Bin 0 -> 15406 bytes www/test_file.html | 5 + 17 files changed, 948 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 HEADS create mode 100644 LICENSE create mode 100644 Readme.md create mode 100644 SConstruct create mode 100644 app/rdn_application.cpp create mode 100644 app/rdn_application.h create mode 100644 build.config.example create mode 100644 main.cpp create mode 100644 www/android-chrome-192x192.png create mode 100644 www/android-chrome-512x512.png create mode 100644 www/apple-touch-icon.png create mode 100644 www/favicon-16x16.png create mode 100644 www/favicon-32x32.png create mode 100644 www/favicon.ico create mode 100644 www/test_file.html diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..cb67d39 --- /dev/null +++ b/.clang-format @@ -0,0 +1,128 @@ +# Commented out parameters are those with the same value as base LLVM style +# We can uncomment them if we want to change their value, or enforce the +# chosen value in case the base style changes (last sync: Clang 6.0.1). +--- +### General config, applies to all languages ### +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +# AlignConsecutiveAssignments: false +# AlignConsecutiveDeclarations: false +# AlignEscapedNewlines: Right +# AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +# AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +# AllowShortLoopsOnASingleLine: false +# AlwaysBreakAfterDefinitionReturnType: None +# AlwaysBreakAfterReturnType: None +# AlwaysBreakBeforeMultilineStrings: false +# AlwaysBreakTemplateDeclarations: false +# BinPackArguments: true +# BinPackParameters: true +# BraceWrapping: +# AfterClass: false +# AfterControlStatement: false +# AfterEnum: false +# AfterFunction: false +# AfterNamespace: false +# AfterObjCDeclaration: false +# AfterStruct: false +# AfterUnion: false +# AfterExternBlock: false +# BeforeCatch: false +# BeforeElse: false +# IndentBraces: false +# SplitEmptyFunction: true +# SplitEmptyRecord: true +# SplitEmptyNamespace: true +# BreakBeforeBinaryOperators: None +# BreakBeforeBraces: Attach +# BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +# BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +# BreakStringLiterals: true +ColumnLimit: 0 +# CommentPragmas: '^ IWYU pragma:' +# CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +# DerivePointerAlignment: false +# DisableFormat: false +# ExperimentalAutoDetectBinPacking: false +# FixNamespaceComments: true +# ForEachMacros: +# - foreach +# - Q_FOREACH +# - BOOST_FOREACH +# IncludeBlocks: Preserve +IncludeCategories: + - Regex: '".*"' + Priority: 1 + - Regex: '^<.*\.h>' + Priority: 2 + - Regex: '^<.*' + Priority: 3 +# IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +# IndentPPDirectives: None +IndentWidth: 4 +# IndentWrappedFunctionNames: false +# JavaScriptQuotes: Leave +# JavaScriptWrapImports: true +# KeepEmptyLinesAtTheStartOfBlocks: true +# MacroBlockBegin: '' +# MacroBlockEnd: '' +# MaxEmptyLinesToKeep: 1 +# NamespaceIndentation: None +# PenaltyBreakAssignment: 2 +# PenaltyBreakBeforeFirstCallParameter: 19 +# PenaltyBreakComment: 300 +# PenaltyBreakFirstLessLess: 120 +# PenaltyBreakString: 1000 +# PenaltyExcessCharacter: 1000000 +# PenaltyReturnTypeOnItsOwnLine: 60 +# PointerAlignment: Right +# RawStringFormats: +# - Delimiter: pb +# Language: TextProto +# BasedOnStyle: google +# ReflowComments: true +# SortIncludes: true +# SortUsingDeclarations: true +# SpaceAfterCStyleCast: false +# SpaceAfterTemplateKeyword: true +# SpaceBeforeAssignmentOperators: true +# SpaceBeforeParens: ControlStatements +# SpaceInEmptyParentheses: false +# SpacesBeforeTrailingComments: 1 +# SpacesInAngles: false +# SpacesInContainerLiterals: true +# SpacesInCStyleCastParentheses: false +# SpacesInParentheses: false +# SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Always +--- +### C++ specific config ### +Language: Cpp +Standard: Cpp03 +--- +### ObjC specific config ### +Language: ObjC +Standard: Cpp03 +ObjCBlockIndentWidth: 4 +# ObjCSpaceAfterProperty: false +# ObjCSpaceBeforeProtocolList: true +--- +### Java specific config ### +Language: Java +# BreakAfterJavaFieldAnnotations: false +JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax'] +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ba8de7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +engine +modules/* +logs/* + +*.d +*.o +*.meta +game/.import/** +game/.prop_tool_temp/** +.sconsign.dblite +.DS_Store + +export/** +release/** + +.vs/* +.kdev4/* +.vscode/* + +TestRWTextures + +_build/* +_binaries/* +game/android/build/* + +*.blend1 +.dir-locals.el + +build.config \ No newline at end of file diff --git a/HEADS b/HEADS new file mode 100644 index 0000000..8165d6c --- /dev/null +++ b/HEADS @@ -0,0 +1 @@ +{"engine": {"master": "4ea3702768e45f1d408737ebfcf74a6335bbf7d8"}} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..190891a --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Péter Magyar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..b7f2f57 --- /dev/null +++ b/Readme.md @@ -0,0 +1,9 @@ +# rcpp_cms_project + +Sample project for rcpp cms. + + +clone this repo, then call `scons`, it will clone rcpp cms into a new engine directory, then you can build using build words, like: `scons bl`. + + +Proper explanation for the time being: https://github.com/Relintai/broken_seals (Compiling section). (I'm using the same script.) diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..f31f11b --- /dev/null +++ b/SConstruct @@ -0,0 +1,492 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2019-2020 Péter Magyar +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +EnsureSConsVersion(0, 98, 1) + +import sys +import os +import subprocess +import json +import shutil +import traceback + + +folders = [ + 'app', +] + +main_file = 'main.cpp' + +repository_index = 0 +module_clone_path = '/modules/' +clone_command = 'git clone {0} {1}' + +visual_studio_call_vcvarsall = False +visual_studio_vcvarsall_path = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat' +visual_studio_arch = 'amd64' + +exports = { + 'global': [], + 'linux': [], + 'windows': [], + 'android': [], + 'javascript': [], +} + +engine_repository = [ ['https://github.com/Relintai/rcpp_cms.git', 'git@github.com:Relintai/rcpp_cms.git'], 'engine', '' ] + +module_repositories = [ +] + +addon_repositories = [ +] + +third_party_addon_repositories = [ +] + +target_commits = {} + +engine_branch = 'master' + +def onerror(func, path, exc_info): + """ + https://stackoverflow.com/questions/2656322/shutil-rmtree-fails-on-windows-with-access-is-denied + + Because Windows. + + Error handler for ``shutil.rmtree``. + + If the error is due to an access error (read only file) + it attempts to add write permission and then retries. + + If the error is for another reason it re-raises the error. + + Usage : ``shutil.rmtree(path, onerror=onerror)`` + """ + import stat + if not os.access(path, os.W_OK): + # Is the error an access error ? + os.chmod(path, stat.S_IWUSR) + func(path) + else: + raise + +def load_target_commits_array(): + global target_commits + + if os.path.isfile('./HEADS'): + with open('./HEADS', 'r') as infile: + target_commits = json.load(infile) + else: + target_commits = {} + +def save_target_commits_array(): + with open('./HEADS', 'w') as outfile: + json.dump(target_commits, outfile) + +def update_repository(data, clone_path, branch = 'master'): + cwd = os.getcwd() + + full_path = cwd + clone_path + data[1] + '/' + + if not os.path.isdir(full_path): + os.chdir(cwd + clone_path) + + subprocess.call(clone_command.format(data[0][repository_index], data[1]), shell=True) + + os.chdir(full_path) + + subprocess.call('git reset', shell=True) + subprocess.call('git reset --hard', shell=True) + subprocess.call('git clean -f -d', shell=True) + subprocess.call('git checkout -B ' + branch + ' origin/' + branch, shell=True) + subprocess.call('git reset', shell=True) + subprocess.call('git reset --hard', shell=True) + subprocess.call('git clean -f -d', shell=True) + subprocess.call('git pull origin ' + branch, shell=True) + + process = subprocess.Popen('git rev-parse HEAD', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + output = process.communicate()[0].decode().strip() + + if data[1] not in target_commits: + target_commits[data[1]] = {} + + target_commits[data[1]][branch] = output + + os.chdir(cwd) + +def setup_repository(data, clone_path, branch = 'master'): + cwd = os.getcwd() + + full_path = cwd + clone_path + data[1] + '/' + + if not os.path.isdir(full_path): + os.chdir(cwd + clone_path) + + subprocess.call(clone_command.format(data[0][repository_index], data[1]), shell=True) + + os.chdir(full_path) + + subprocess.call('git reset', shell=True) + subprocess.call('git reset --hard', shell=True) + subprocess.call('git clean -f -d', shell=True) + subprocess.call('git checkout -B ' + branch + ' origin/' + branch, shell=True) + subprocess.call('git pull origin ' + branch, shell=True) + subprocess.call('git reset', shell=True) + subprocess.call('git reset --hard', shell=True) + + if data[1] in target_commits: + target = target_commits[data[1]][branch] + + subprocess.call('git checkout -B ' + branch + ' ' + target, shell=True) + subprocess.call('git clean -f -d', shell=True) + subprocess.call('git reset', shell=True) + subprocess.call('git reset --hard', shell=True) + + os.chdir(cwd) + +def copy_repository(data, target_folder, clone_path): + copytree(os.path.abspath(clone_path + data[1] + '/' + data[2]), os.path.abspath(target_folder + data[1])) + +def copytree(src, dst): + for item in os.listdir(src): + sp = os.path.join(src, item) + dp = os.path.join(dst, item) + + if os.path.isdir(sp): + if os.path.isdir(dp): + shutil.rmtree(dp, onerror=onerror) + + shutil.copytree(sp, dp) + else: + if not os.path.isdir(dst): + os.makedirs(dst) + + shutil.copy2(sp, dp) + +def update_engine(): + update_repository(engine_repository, '/', engine_branch) + +def update_modules(): + for rep in module_repositories: + update_repository(rep, module_clone_path) + copy_repository(rep, './engine/modules/', '.' + module_clone_path) + +def update_addons(): + for rep in addon_repositories: + update_repository(rep, module_clone_path) + copy_repository(rep, './game/addons/', '.' + module_clone_path) + +def update_addons_third_party_addons(): + for rep in third_party_addon_repositories: + update_repository(rep, module_clone_path) + copy_repository(rep, './game/addons/', '.' + module_clone_path) + +def update_all(): + update_engine() + update_modules() + update_addons() + update_addons_third_party_addons() + + save_target_commits_array() + + +def setup_engine(): + setup_repository(engine_repository, '/', engine_branch) + +def setup_modules(): + for rep in module_repositories: + setup_repository(rep, module_clone_path) + copy_repository(rep, './engine/modules/', '.' + module_clone_path) + +def setup_addons(): + for rep in addon_repositories: + setup_repository(rep, module_clone_path) + copy_repository(rep, './game/addons/', '.' + module_clone_path) + +def setup_addons_third_party_addons(): + for rep in third_party_addon_repositories: + setup_repository(rep, module_clone_path) + copy_repository(rep, './game/addons/', '.' + module_clone_path) + +def setup_all(): + setup_engine() + setup_modules() + setup_addons() + setup_addons_third_party_addons() + +def format_path(path): + if 'win' in sys.platform: + path = path.replace('/', '\\') + path = path.replace('~', '%userprofile%') + + return path + +def get_exports_for(platform): + export_command = 'export ' + command_separator = ';' + + if 'win' in sys.platform: + command_separator = '&' + export_command = 'set ' + + command = '' + + for p in exports[platform]: + command += export_command + p + command_separator + + return command + + +def parse_config(): + global visual_studio_vcvarsall_path + global visual_studio_arch + global visual_studio_call_vcvarsall + global exports + + if not os.path.isfile('build.config'): + return + + with open('build.config', 'r') as f: + + for line in f: + ls = line.strip() + if ls == '' or ls.startswith('#'): + continue + + words = line.split() + + if (len(words) < 2): + print('This build.config line is malformed, and got ignored: ' + ls) + continue + + if words[0] == 'visual_studio_vcvarsall_path': + visual_studio_vcvarsall_path = format_path(ls[29:]) + elif words[0] == 'visual_studio_arch': + visual_studio_arch = format_path(ls[19:]) + elif words[0] == 'visual_studio_call_vcvarsall': + visual_studio_call_vcvarsall = words[1].lower() in [ 'true', '1', 't', 'y', 'yes' ] + elif words[0] == 'export': + if (len(words) < 3) or not words[1] in exports: + print('This build.config line is malformed, and got ignored: ' + ls) + continue + + export_path = format_path(ls[8 + len(words[1]):]) + + exports[words[1]].append(export_path) + +parse_config() + +env = Environment() + +if len(sys.argv) > 1: + + arg = sys.argv[1] + + arg_split = arg.split('_') + arg = arg_split[0] + arg_split = arg_split[1:] + + if arg[0] == 'b': + build_string = get_exports_for('global') + 'scons ' + + build_string += 'target=' + if 'r' in arg: + build_string += 'release' + elif 'd' in arg: + build_string += 'debug' + else: + build_string += 'release_debug' + build_string += ' ' + + if 'm' in arg: + build_string += 'use_mingw=yes' + else: + if 'win' in sys.platform and visual_studio_call_vcvarsall: + build_string = 'call "{0}" {1}&'.format(visual_studio_vcvarsall_path, visual_studio_arch) + build_string + + if 'o' in arg: + build_string += 'use_llvm=yes' + + if 'v' in arg: + build_string += 'vsproj=yes' + + build_string += 'folders="' + + for f in folders: + build_string += '../' + f + build_string += ';' + + build_string += '" ' + + build_string += 'main_file="../' + main_file + '" ' + + for i in range(2, len(sys.argv)): + build_string += ' ' + sys.argv[i] + ' ' + + cwd = os.getcwd() + full_path = cwd + '/engine/' + + if not os.path.isdir(full_path): + print('engine directory doesnt exists.') + exit() + + os.chdir(full_path) + + if 'l' in arg: + build_string += 'platform=x11' + + build_string = get_exports_for('linux') + build_string + + print('Running command: ' + build_string) + + subprocess.call(build_string, shell=True) + elif 'w' in arg: + build_string += 'platform=windows' + + build_string = get_exports_for('windows') + build_string + + print('Running command: ' + build_string) + + subprocess.call(build_string, shell=True) + elif 'a' in arg: + build_string += 'platform=android' + + build_string = get_exports_for('android') + build_string + + print('Running command: ' + build_string + ' android_arch=armv7') + subprocess.call(build_string + ' android_arch=armv7', shell=True) + print('Running command: ' + build_string + ' android_arch=arm64v8') + subprocess.call(build_string + ' android_arch=arm64v8', shell=True) + print('Running command: ' + build_string + ' android_arch=x86') + subprocess.call(build_string + ' android_arch=x86', shell=True) + + os.chdir(full_path + 'platform/android/java/') + + print('Running command: ' + get_exports_for('global') + get_exports_for('android') + './gradlew generateGodotTemplates') + subprocess.call(get_exports_for('global') + get_exports_for('android') + './gradlew generateGodotTemplates', shell=True) + elif 'j' in arg: + build_string += 'platform=javascript' + + build_string = get_exports_for('javascript') + build_string + + print('Running command: ' + build_string) + subprocess.call(build_string, shell=True) + + else: + print('No platform specified') + exit() + + exit() + elif arg[0] == 'p': + if arg == 'p': + #print("Applies a patch. Append c for the compilation database patch. For example: pc") + print("Applies a patch. No Patches right now.") + exit() + + cwd = os.getcwd() + full_path = cwd + '/engine/' + + if not os.path.isdir(full_path): + print('engine directory doesnt exists.') + exit() + + os.chdir(full_path) + + #apply the patch to just the working directory, without creating a commit + + #if 'c' in arg: + # subprocess.call('git apply --index ../patches/compilation_db.patch', shell=True) + + #unstage all files + subprocess.call('git reset', shell=True) + + exit() + +opts = Variables(args=ARGUMENTS) + +opts.Add('a', 'What to do', '') +opts.Add(EnumVariable('action', 'What to do', 'setup', ('setup', 'update'))) +opts.Add('t', 'Action target', '') +opts.Add(EnumVariable('target', 'Action target', 'all', ('all', 'engine', 'modules', 'all_addons', 'addons', 'third_party_addons'))) +opts.Add(EnumVariable('repository_type', 'Type of repositories to clone from first', 'http', ('http', 'ssh'))) + +opts.Update(env) +Help(opts.GenerateHelpText(env)) + +load_target_commits_array() + +rt = env['repository_type'] + +if rt == 'ssh': + repository_index = 1 + +action = env['action'] +target = env['target'] + +if env['a']: + action = env['a'] + +if env['t']: + target = env['t'] + +if not os.path.isdir('./modules'): + os.mkdir('./modules') + +if 'm' in action: + godot_branch = 'master' + +if 'setup' in action or action[0] == 's': + if target == 'all': + setup_all() + elif target == 'engine': + setup_engine() + elif target == 'modules': + setup_modules() + elif target == 'all_addons': + setup_addons() + setup_addons_third_party_addons() + elif target == 'addons': + setup_addons() + elif target == 'third_party_addons': + setup_addons_third_party_addons() +elif 'update' in action or action[0] == 'u': + if target == 'all': + update_all() + elif target == 'engine': + update_engine() + save_target_commits_array() + elif target == 'modules': + update_modules() + save_target_commits_array() + elif target == 'all_addons': + update_addons() + update_addons_third_party_addons() + save_target_commits_array() + elif target == 'addons': + update_addons() + save_target_commits_array() + elif target == 'third_party_addons': + update_addons_third_party_addons() + save_target_commits_array() + diff --git a/app/rdn_application.cpp b/app/rdn_application.cpp new file mode 100644 index 0000000..9fcf05a --- /dev/null +++ b/app/rdn_application.cpp @@ -0,0 +1,98 @@ +#include "rdn_application.h" + +#include "core/request.h" + +#include + +#include "core/file_cache.h" + +#include "core/handler_instance.h" + +#include "core/database_manager.h" + +#include "core/html_builder.h" + +void RDNApplication::index(Object *instance, Request *request) { + std::string body; + + if (FileCache::get_singleton()->get_cached_body("index", &body)) { + request->response->setBody(body); + + return; + } + + HTMLBuilder b; + + b.h1(); + b.w("Testh1"); + b.ch1(); + + b.h2()->cls("tcls")->id("tid"); + b.w("Testh2"); + b.ch2(); + + b.br(); + + b.p(); + b.w("Test HTML Body HTMLBuilder"); + b.cp(); + + b.form()->method("post")->href("/"); + + //->str("/") is a temp hack + b.input()->type("text")->str("/"); + b.input()->type("submit")->str("/"); + b.cform(); + + request->body = b.result; + + //request->body.append("

Test HTML Body

"); + request->compile_body(); + + FileCache::get_singleton()->set_cached_body("index", request->compiled_body); + + request->send(); +} + +void RDNApplication::session_middleware_func(Object *instance, Request *request) { + std::cout << "test: session_middleware_func called" << std::endl; + + //if fail + //request->send(); in middleware + + request->next_stage(); +} + +void RDNApplication::message_page_func(Object *instance, Request *request) { + dynamic_cast(instance)->index(request); +} + +void RDNApplication::setup_routes() { + Application::setup_routes(); + + index_func = HandlerInstance(index); + + main_route_map["asd"] = HandlerInstance(index); + main_route_map["message_page"] = HandlerInstance(message_page_func, message_page); +} + +void RDNApplication::setup_middleware() { + Application::setup_middleware(); + + //middlewares.push_back(RDNApplication::session_middleware_func); +} + +void RDNApplication::migrate() { + message_page->migrate(); +} + +RDNApplication::RDNApplication() : + Application() { + + message_page = new MessagePage(); + message_page->db = DatabaseManager::get_singleton()->databases[0]; +} + +RDNApplication::~RDNApplication() { + delete message_page; +} \ No newline at end of file diff --git a/app/rdn_application.h b/app/rdn_application.h new file mode 100644 index 0000000..c9eefec --- /dev/null +++ b/app/rdn_application.h @@ -0,0 +1,28 @@ +#ifndef RDN_APPLICATION_H +#define RDN_APPLICATION_H + +#include "core/application.h" +#include "core/object.h" + +#include "modules/message_page/message_page.h" + +class RDNApplication : public Application { +public: + static void index(Object *instance, Request *request); + + static void session_middleware_func(Object* instance, Request *request); + + static void message_page_func(Object *instance, Request *request); + + virtual void setup_routes(); + virtual void setup_middleware(); + + virtual void migrate(); + + RDNApplication(); + ~RDNApplication(); + + MessagePage *message_page; +}; + +#endif \ No newline at end of file diff --git a/build.config.example b/build.config.example new file mode 100644 index 0000000..317567f --- /dev/null +++ b/build.config.example @@ -0,0 +1,44 @@ +# Copyright (c) 2019-2020 Péter Magyar +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Rename this file to build.config to use it + +# Lines starting with # are comments. (Only works at the start of the line!) + +# Note: +# ~ will be converted to %userprofile% on windows +# / will be converted to \ on windows +# so you don't have to worry about it + +# Visual studio related setup: +visual_studio_call_vcvarsall True +visual_studio_vcvarsall_path C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Auxiliary/Build/vcvarsall.bat +visual_studio_arch amd64 + +# export related setup +# available export targets: global, linux, windows, android, javascript + +export global SCONS_CACHE=~/.scons_cache +export global SCONS_CACHE_LIMIT=5000 + +export android ANDROID_NDK_ROOT=~/SDKs/Android/NDK/android-ndk-r20b +export android ANDROID_NDK_HOME=~/SDKs/Android/NDK/android-ndk-r20b +export android ANDROID_HOME=~/SDKs/Android/SDK + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..4453305 --- /dev/null +++ b/main.cpp @@ -0,0 +1,95 @@ +#include +#include +#include + +#include "core/application.h" +#include "core/file_cache.h" +#include "core/http_server.h" + +#include "app/rdn_application.h" + +#include "core/database_manager.h" + +#include "database/db_init.h" + +#include "core/settings.h" + +#define MAIN_CLASS RDNApplication + +void create_databases() { + + Settings *settings = Settings::get_singleton(); + + if (!settings) { + printf("create_databases: Settings singleton is null!"); + return; + } + +/* + rapidjson::Value dbs = settings->settings["databases"]; + + if (!dbs.IsArray()) { + printf("create_databases: dbs !dbs.IsArray()!"); + return; + } +*/ + + DatabaseManager *dbm = DatabaseManager::get_singleton(); + + uint32_t index = dbm->create_database("mysql"); + Database *db = dbm->databases[0]; + //db->_builder_creation_func = MysqlQueryBuilder::create; + db->connect(""); +} + +int main(int argc, char **argv) { + bool migrate = false; + + for (int i = 1; i < argc; ++i) { + const char *a = argv[i]; + + if (a[0] == 'm') { + migrate = true; + } + } + + initialize_database_backends(); + + Settings *settings = new Settings(true); + + settings->parse_file("settings.json"); + + FileCache *file_cache = new FileCache(true); + file_cache->wwwroot = "./www"; + file_cache->wwwroot_refresh_cache(); + + DatabaseManager *dbm = new DatabaseManager(); + + create_databases(); + + Application *app = new MAIN_CLASS(); + + app->load_settings(); + app->setup_routes(); + app->setup_middleware(); + + HTTPServer *server = new HTTPServer(); + + server->port = 8080; + server->initialize(); + + if (!migrate) { + server->main_loop(); + } else { + printf("Running migrations.\n"); + app->migrate(); + } + + delete server; + delete app; + delete dbm; + delete file_cache; + delete settings; + + return 0; +} \ No newline at end of file diff --git a/www/android-chrome-192x192.png b/www/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..bed03078bdec17495eaea1e026c6d0e3e486b7b6 GIT binary patch literal 7312 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 literal 0 HcmV?d00001 diff --git a/www/apple-touch-icon.png b/www/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e1aeb2dc5f21babe06113001fe225a57dcd43c6b GIT binary patch literal 6382 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 Vc2QQvz;g^7HDzsB8PqEL{{Vh}Dv1C9 literal 0 HcmV?d00001 diff --git a/www/favicon-16x16.png b/www/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..32f0c856679103a0b2a3be4401f772a4f94012f9 GIT binary patch literal 427 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 + +

Welcome

+ + \ No newline at end of file