Adds config file (quanlib.ini) support.
authorChris Jaekl <cejaekl@yahoo.com>
Mon, 20 Nov 2017 12:36:24 +0000 (21:36 +0900)
committerChris Jaekl <cejaekl@yahoo.com>
Mon, 20 Nov 2017 12:36:24 +0000 (21:36 +0900)
Also adds a test for browser compatibility, and display an error message
if the user is using an unsupported browser (likely suspects are IE11
and Android Browser 4.x).

THIRD_PARTY [new file with mode: 0644]
app/index.html
app/modernizr-custom.js [new file with mode: 0644]
js/BooksModel.js
js/Main.js
js/SearchController.js
main/config.go
main/db.go
main/handler.go
main/main.go

diff --git a/THIRD_PARTY b/THIRD_PARTY
new file mode 100644 (file)
index 0000000..e61916e
--- /dev/null
@@ -0,0 +1,29 @@
+===========================================================================
+Other terms and conditions
+===========================================================================
+
+Portions of this software (libraries) were developed by other people, 
+and are subject to the licence terms that those people chose.
+
+---------------------------------------------------------------------------
+Modernizr
+
+This JavaScript library, available at https://modernizr.com/, 
+is distributed under the MIT licence:
+
+    Copyright © 2009-2017
+    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.
index 26a0374394ad6c710463cef14e3e5ecd17b39fbf..d417fefeb24eacddb96274f95fda3ec4d63ea89b 100644 (file)
@@ -1,4 +1,4 @@
-<html>
+<html class="no-js">
   <head>
     <title>eBook Library</title>
     <link href="lib.css" rel="stylesheet" type="text/css"/>
@@ -6,7 +6,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
   </head>
   
-  <body> 
+  <body onload="onSearch();"
     <form>
       <input id="search" onclick="onSearch();" type="button" value="Search"/> 
       <span class="term">Author: <input id="aut" type="text"/></span>
@@ -25,6 +25,7 @@
 
     <div id="details" class="tooltip" onclick="hideDetails();">(No information available)</div>
 
-    <script src="lib.min.js"></script>
+    <script src="modernizr-custom.js"></script>
+    <script src="all.js"></script>
   </body>
 </html>
diff --git a/app/modernizr-custom.js b/app/modernizr-custom.js
new file mode 100644 (file)
index 0000000..9fdeb1c
--- /dev/null
@@ -0,0 +1,3 @@
+/*! modernizr 3.5.0 (Custom Build) | MIT *
+ * https://modernizr.com/download/?-fetch-setclasses !*/
+!function(n,e,s){ function o(n,e){ return typeof n===e; } function a(){ var n,e,s,a,t,l,r; for(var c in f) if(f.hasOwnProperty(c)) { if(n=[],e=f[c],e.name && (n.push(e.name.toLowerCase()),e.options && e.options.aliases && e.options.aliases.length)) for(s=0;s<e.options.aliases.length;s++) n.push(e.options.aliases[s].toLowerCase()); for(a=o(e.fn,'function')?e.fn():e.fn,t=0;t<n.length;t++) l=n[t],r=l.split('.'),1===r.length?Modernizr[r[0]]=a:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=a),i.push((a?'':'no-')+r.join('-'));}} function t(n){var e=r.className,s=Modernizr._config.classPrefix||'';if(c&&(e=e.baseVal),Modernizr._config.enableJSClass){var o=new RegExp('(^|\\s)'+s+'no-js(\\s|$)');e=e.replace(o,'$1'+s+'js$2');}Modernizr._config.enableClasses&&(e+=' '+s+n.join(' '+s),c?r.className.baseVal=e:r.className=e);}var i=[],f=[],l={_version:'3.5.0',_config:{classPrefix:'',enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(n,e){var s=this;setTimeout(function(){e(s[n]);},0);},addTest:function(n,e,s){f.push({name:n,fn:e,options:s});},addAsyncTest:function(n){f.push({name:null,fn:n});}},Modernizr=function(){};Modernizr.prototype=l,Modernizr=new Modernizr;var r=e.documentElement,c='svg'===r.nodeName.toLowerCase();Modernizr.addTest('fetch','fetch'in n),a(),t(i),delete l.addTest,delete l.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();n.Modernizr=Modernizr;}(window,document);
index 984519d137409329027f20ca894beeeb18409ab0..75e065194ccdc49602033971208598b9bcde7c64 100644 (file)
@@ -71,13 +71,12 @@ var BooksModel = (function() {
         }
 
         fetch(url, {method:'GET', cache:'default'})
-            .then(response => response.json())
-            .then((jsonValue) => {
-                console.log('JSON response for info:  ', jsonValue);
+            .then(function(response) {return response.json();})
+            .then(function(jsonValue) {
                 my.cache = jsonValue;
                 notifyAll();    // inform all subscribers that the model has been updated
             })
-            .catch(err => {
+            .catch(function(err) {
                 var msg = 'Error fetching book details via URL:  ' + url + ': ' + err;
                 console.log(msg, err.stack);
                 report(msg);
index 6ceac8256665e4fbd49b5aaaf3661ccf918d1d5f..5f253bcc1e9fe486362d1106232d40030dee82a5 100644 (file)
@@ -1,5 +1,5 @@
-//QuanLib:  eBook Library
-//(C) 2017 by Christian Jaekl (cejaekl@yahoo.com)
+// QuanLib:  eBook Library
+// Copyright (C) 2017 by Christian Jaekl (cejaekl@yahoo.com)
 
 'use strict';
 
@@ -20,6 +20,16 @@ BooksView.init(BooksModel);
 PagingController.init(BooksModel);
 SearchController.init(BooksModel);
 
+if (Modernizr.fetch) {
+    console.log('quanweb:  browser feature check:  OK');
+}
+else {
+    // If we cared about supporting older browsers (at this point, IE11 and Adroid 4.x's built-in browser,
+    // neither of which is due to receive security patch support for much longer), then we would insert a 
+    // shim here to implement the fetch API.  But, in this case, we don't and won't.
+    alert('Sorry, this page will not work in your browser.\nPlease use a recent version of Chrome, Edge or Firefox instead.');
+}
+
 // ================
 // Global functions
 // 
index 5b770b59673661d11a4cb0e9cb872e8595622dbb..b95c951b3c09250fc0f0d2b23d0d9705385b13e8 100644 (file)
@@ -22,8 +22,8 @@ var SearchController = (function () {
         var url = constructSearchUrl();
 
         fetch(url, {method:'GET', cache:'default'})
-            .then(response => response.json())
-            .then((jsonValue) => {
+            .then(function(response) {return response.json();})
+            .then(function(jsonValue) {
                 // console.log('JSON response:  ', jsonValue);
                 booksModel.ids = jsonValue;
                 booksModel.count = booksModel.ids.length;
@@ -34,7 +34,7 @@ var SearchController = (function () {
         
                 PagingController.adjustPos(0);
             })
-            .catch(err => { 
+            .catch(function(err) { 
                 var msg = 'Error fetching JSON from URL:  ' + url + ': ' + err + ':' + err.stack;
                 console.log(msg);
                 report(msg);
index 3d0a6cfb69cb52e1df132bab809ae2cfbddd5597..64ae01066139f38fd8ad601879ad29bffc776d55 100644 (file)
 package main
 
+import (
+  "errors"
+  "fmt"
+  "github.com/alyu/configparser"
+  "os"
+  "strconv"
+)
+
 type qwConfig struct {
-  user, pass, dbName, efsBasePath  string
+  user, pass, dbName string 
+  basePath string
+  port int
+}
+
+func checkedSection(config *configparser.Configuration, name string) (*configparser.Section, error) {
+  section, err := config.Section(name)
+  if nil != err {
+    fmt.Println("ERROR!  Could not locate section [" + name + "] in config file:", err)
+    return nil, err
+  }
+  return section, nil
+}
+
+func checkedValue(section *configparser.Section, name string) (string, error) {
+  if ! section.Exists(name) {
+    return "", errors.New("ERROR!  Value named \"" + name + "\" not found in config file.")
+  }
+
+  value := section.ValueOf(name)
+  return value, nil
+}
+
+// Note:  Will exit if config cannot be loaded
+func GetConfig() (*qwConfig) {
+  config, err := loadConfig()
+  if nil != err {
+    fmt.Println("FATAL!  Cannot load config.  Unable to continue.", err)
+    os.Exit(1)
+  }
+
+  return config
 }
 
-func getConfig() qwConfig {
-  // TODO:  use a real password, and load config info from a file
-  return qwConfig{user:"quanlib", pass:"quanlib", dbName:"quanlib", efsBasePath:"/arc/quanlib/efs"}
+func loadConfig() (*qwConfig, error) {
+  // TODO:  Make the path to the config file configurable (environment variable?)
+  // TODO:  Load the config file once, and cache the values for subsequent getConfig() calls
+
+  const configFile = "quanlib.ini"
+
+  config, err := configparser.Read(configFile)
+  if nil != err {
+    fmt.Println("ERROR!  Failed to read config file:", configFile, err)
+    return nil, err
+  }
+
+  var section *configparser.Section
+
+  // ------------------
+  // Section:  database
+
+  var dbName, user, pass string
+
+  section, err = checkedSection(config, "database")
+  if nil != err {
+    return nil, err
+  }
+
+  dbName, err = checkedValue(section, "name")
+  if nil != err {
+    return nil, err
+  }
+
+  user, err = checkedValue(section, "user")
+  if nil != err {
+    return nil, err
+  }
+
+  pass, err = checkedValue(section, "pass")
+  if nil != err {
+    return nil, err
+  }
+
+  // --------------------
+  // Section:  filesystem
+
+  var basePath string
+
+  section, err = checkedSection(config, "filesystem")
+  if nil != err {
+    return nil, err
+  }
+
+  basePath, err = checkedValue(section, "basePath")
+  if nil != err {
+    return nil, err
+  }
+
+  // -------------
+  // Section:  web
+
+  var port int
+  var portStr string
+
+  section, err = checkedSection(config, "web")
+  if nil != err {
+    return nil, err
+  }
+
+  portStr, err = checkedValue(section, "port")
+  if nil != err {
+    return nil, err
+  }
+  port, err = strconv.Atoi(portStr)
+  if nil != err {
+    return nil, err
+  }
+
+  return &qwConfig{ user:user, pass:pass, dbName:dbName, 
+                    basePath:basePath,
+                    port:port }, 
+         nil 
 }
index 53c0a2c37b0959cf9ca522067e1bc463ea6a1671..809634a24eb371680ab6ec55f3b64a3d3820477f 100644 (file)
@@ -57,7 +57,7 @@ func getDb() (*sql.DB) {
     g_mutex.Lock()
     defer g_mutex.Unlock()
     if nil == g_db {
-      config := getConfig()
+      config := GetConfig()
       g_db = openDb(config.user, config.pass, config.dbName)
     }
   }
index 2d8076e35720c20e5422ecd87fe9eca83f70c7c9..cd35a7640c124d2690f390ff9d9f1c92ce2fc251 100644 (file)
@@ -29,10 +29,10 @@ func bookMimeType(bookPath string) string {
 }
 
 func efsPathForId(efsId int) string {
-  config := getConfig()
+  config := GetConfig()
 
   idStr := fmt.Sprintf("%010d", efsId)
-  path := fmt.Sprintf("%s/%s/%s/%s/%s/%s.dat", config.efsBasePath, idStr[0:2], idStr[2:4], idStr[4:6], idStr[6:8], idStr)
+  path := fmt.Sprintf("%s/efs/%s/%s/%s/%s/%s.dat", config.basePath, idStr[0:2], idStr[2:4], idStr[4:6], idStr[6:8], idStr)
 
   return path
 }
index d30f84b9e76138b41883797c91cb474d5761b786..b54712f7efaf7fbb0ffc80a4893527614cc61bc4 100644 (file)
@@ -3,6 +3,7 @@ package main
 import (
   "database/sql"
   "net/http"
+  "strconv"
 )
 
 // ============================================================================
@@ -17,5 +18,6 @@ func main() {
 
   http.HandleFunc("/", handler)
 
-  http.ListenAndServe(":8001", nil)
+  config := GetConfig()
+  http.ListenAndServe(":" + strconv.Itoa(config.port), nil)
 }