AWS : how to play a video file from s3 bucket in browser

URI

all you have to do is to sign a request like:

https://s3.amazonaws.com/yourbucket/key?
response-content-disposition=inline&
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=AKIAJWWH7EGSUBWE34IQ/20181228/us-east-1/s3/aws4_request&
X-Amz-Date=20181228T074135Z&
X-Amz-Expires=86400&X-Amz-SignedHeaders=host&
X-Amz-Signature=b3143f8442812e176fd61838813a33f53cfd7e198e1c6e68fab5d03d4a642403
 
and set it as src attribue to a video element.

Player component

import React, { Component } from 'react';
import PropTypes from 'prop-types';

const AWS_SERVICE = 's3';
const crypto = require('crypto');

Date.prototype.toYMDString = function() {
    let year = this.getFullYear().toString();
    let month = this.getUTCMonth() + 1;
    if (month < 10)
      month = "0" + month;
    let day = this.getUTCDate();
    if (day < 10)
      day = "0" + day;
    return String(year) + String(month) 
           + String(day);
}

Date.prototype.toTZString = function() {
    let hour = this.getUTCHours();
    if (hour < 10)
      hour = "0" + hour;
    let minute = this.getUTCMinutes();
    if (minute < 10)
      minute = "0" + minute;
    let second = this.getUTCSeconds();
    if (second < 10)
      second = "0" + second;
    return this.toYMDString() + "T"
      + String(hour) + String(minute)
      + String(second) + "Z";
}

class Player extends Component {
  constructor(props) {
    super(props);
  }

  Hmac(key, string) {
      const hmac = crypto.createHmac('sha256', key);
      hmac.end(string);
      return hmac.read();
  }
  
  Signature(date, region, service, toSign) {
    const { aws_secret_key } = this.props;
      let dateKey = this.Hmac('AWS4' + aws_secret_key, date);
      let dateRegionKey = this.Hmac(dateKey, region);
      let dateRegionServiceKey = this.Hmac(dateRegionKey, service);
      let signingKey = this.Hmac(dateRegionServiceKey, 'aws4_request');
      let signature = this.Hmac(signingKey, toSign).toString('hex');
      return signature;
  }
  
  stringToSign(timeStamp, scope, canonicalRequest) {
      var signParts = [];
      signParts.push('AWS4-HMAC-SHA256');
      signParts.push(timeStamp);
      signParts.push(scope);
      signParts.push(crypto.createHash('sha256').update(canonicalRequest).digest(
  'hex'));
      var result = signParts.join('\n');
      console.log('string to sign');
      console.log(result);
      return result;
  };
  
  canonicalRequest(method, uri, queryString, headers, signedHeaders, hashedPayload) {
    var canonicalParts = [];
    canonicalParts.push(method);
    canonicalParts.push(uri);
    canonicalParts.push(queryString);
    canonicalParts.push(headers);
    canonicalParts.push(signedHeaders);
    canonicalParts.push(hashedPayload);
    var result = canonicalParts.join('\n');
    console.log('canonical request');
    console.log(result);
    return result;
  }

  render() {
    const { aws_key, aws_region, bucket, key } = this.props;
    let uri = "/" + bucket + "/" + key;
    let time_stamp = new Date();
    let tz_time_stamp = time_stamp.toTZString();
    let ymd_time_stamp = time_stamp.toYMDString();
    let scope = ymd_time_stamp + "/" + aws_region + "/" + AWS_SERVICE + "/aws4_request";
    let queryString = "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + encodeURIComponent(aws_key + "/" + scope) + "&X-Amz-Date=" + tz_time_stamp + "&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&response-content-disposition=inline";
    let headers = "host:s3.amazonaws.com";
    let signedHeaders = "";
    let hashedPayload = "host\nUNSIGNED-PAYLOAD";
    let request = this.canonicalRequest('GET', uri, queryString, headers, signedHeaders, hashedPayload);
    let signature = this.Signature(ymd_time_stamp, aws_region, AWS_SERVICE, this.stringToSign(tz_time_stamp, scope, request));
    let url = 'https://s3.amazonaws.com' + uri + '?response-content-disposition=inline&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=' + aws_key + "/" + scope + '&X-Amz-Date=' + tz_time_stamp + '&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=' + signature;
    console.log(url);
    return (
      <video src={url}
        autoPlay playsInline controls
        style={{width:320,height:240}}>
      </video>
    )
  }
}

Player.propTypes = {
  aws_key: PropTypes.string.isRequired,
  aws_secret_key: PropTypes.string.isRequired,
  aws_region: PropTypes.string.isRequired,
  bucket: PropTypes.string.isRequired,
  key: PropTypes.string.isRequired
};

export default Player;

Usage

    return (
        <Player aws_key={aws_key} bucket={bucket} aws_secret_key={aws_secret_key}
          key={key} aws_region={aws_region}>
        </Player>
    );

example

WebRTC recording via Kurento media server

Official guide

I encountered some errors while following the Official guide.
Lucky, I fixed all the errors and make it work finally.
Notes my workaround here, hope it work for you if you have the same problem.

gpg: keyserver receive failed: keyserver error

local install guide

solution from stackoverflow

sudo apt-key adv --keyserver hkp://keys.gnupg.net:80 --recv-keys 5AFA7A83

WebSocket connection to 'wss://<local_ip>:8433/kurento' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED

cause

the application server is started with https if you follow the guide:

http-server -p 8443 -S -C keys/server.crt -K keys/server.key 

but kurento media server start without ssl by default config.
You can get from the config and the default port is 8888.
/etc/kurento/kurento.conf.json

solution

modify kurento media server config to enable ssl and use the same cert that used for your application server. it is a little complex.
but we have a easy solution, just start your application without ssl and test with firefox(if use chrome, you will got a dom exception, then you have to enable ssl for kms).

modify js/index.js
ws_uri: 'ws://' + location.hostname + ':8888/kurento'

start application server with
http-server -p 8443

record callback not triggered

if you use kurento-hello-world-recorder-generator as the example, then everything works well.
but if you use kurento-tutorial-js/kurento-recorder, you might found record not work.

https://github.com/Kurento/kurento-tutorial-js/pull/3

Below patch is work solution for it, but it was not accepted, strange thing.
https://github.com/Kurento/kurento-tutorial-js/pull/3/commits/ccab45818f3336639eec3c02a56f14209124901c

AWS: how to upload file to your s3 bucket via EvaporateJS in browser

Evaporate and config s3 bucket and IAM user

EvaporateJS
Config AWS s3 bucket
Managing Access Permissions to Your Amazon S3 Resources
User Access Key
In summary, create a s3 bucket, set up CORS settings and bucket policy.
 <CORSConfiguration>
     <CORSRule>
         <AllowedOrigin>https://*.yourdomain.com</AllowedOrigin>
         <AllowedOrigin>http://*.yourdomain.com</AllowedOrigin>
         <AllowedMethod>PUT</AllowedMethod>
         <AllowedMethod>POST</AllowedMethod>
         <AllowedMethod>DELETE</AllowedMethod>
         <AllowedMethod>GET</AllowedMethod>
         <ExposeHeader>ETag</ExposeHeader>
         <AllowedHeader>*</AllowedHeader>
     </CORSRule>
 </CORSConfiguration>
 
{
    "Version": "2012-10-17",
    "Id": "Policy145337ddwd",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::6681765859115:user/me"
            },
            "Action": [
                "s3:AbortMultipartUpload",
                "s3:ListMultipartUploadParts",
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::mybucket/*"
        }
    ]
} 

Main codes

an example app created via 'create-reat-app' tool.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

const Evaporate = require('evaporate');
const crypto = require('crypto');

class App extends Component {
  onFileSelected(files) {
    var config = {
      signerUrl: 'YOURSIGNEDURL',
      aws_key: 'YOURAWSKEY',
      bucket: 'YOURBUCKETNAME',
      cloudfront: true,
      computeContentMd5: true,
      cryptoMd5Method: function (data) {
        return crypto.createHash('md5').update(Buffer.from(data)).digest('base64');  
      },
      cryptoHexEncodedHash256: function (data) {
        return crypto.createHash('sha256').update(Buffer.from(data)).digest('hex');
      }
    };
    return Evaporate.create(config)
      .then(function (evaporate) {
         var addConfig = {
           name: files[0].name,
           file: files[0],
           progress: function (progressValue) {
             console.log('Progress', progressValue);
           },
           complete: function (_xhr, awsKey) {
             console.log('Complete!');
           },
         };
         var overrides = {
            bucket: 'recordingsfortalkmeup'
         };
         evaporate.add(addConfig, overrides)
           .then(function (awsObjectKey) {
                console.log('File successfully uploaded to:', awsObjectKey);
           },
           function (reason) {
             console.log('File did not upload sucessfully:', reason);
           });
      });
  }
  render() {
    return (
      <div className="App">
        <input type='file' onChange={(e) => this.onFileSelected(e.target.files)
 }/>
      </div>
    );
  }
}

export default App;

Signer URL

Create a lambda function to do the signature process.
Signing AWS Requests with Signature Version 4
'use strict';
console.log('Loading upload signature function');

const crypto = require("crypto");

function hmac(key, string){
    const hmac = crypto.createHmac('sha256', key);
    hmac.end(string);
    return hmac.read();
}

exports.handler = function(event, context, callback) {
    console.log(JSON.stringify(event));
    let to_sign = event.queryStringParameters.to_sign;
    let timestamp = event.queryStringParameters.datetime.substr(0, 8);
    console.log(to_sign);    
    console.log('date:', timestamp);
    console.log('aws secret key:', process.env.AWS_SECRET);
    console.log('region:', process.env.AWS_REGION);
    console.log('service:s3');
    let dateKey = hmac('AWS4' + process.env.AWS_SECRET, timestamp);
    let dateRegionKey = hmac(dateKey, process.env.AWS_REGION);
    let dateRegionServiceKey = hmac(dateRegionKey, 's3');
    let signingKey = hmac(dateRegionServiceKey, 'aws4_request');
    let signature = hmac(signingKey, to_sign).toString('hex');
    console.log('Created signature "' + signature + '" from ' + to_sign);
    var response = {
        statusCode: 200,
        headers: {
            "Access-Control-Allow-Origin" : "*",
            "Content-Type": "text/html"
        },
        body: signature
    };
    callback(null, response);
};
 
Build an rest API bind with this Lambda function and deploy it, then you will get an invoke url which will be your signer url.
Build an API Gateway API with Lambda Integration
APIGATEWAY

Test

upload
Everything is ok, you will find new uploaded file in your s3 bucket.
s3bucket

Download file from s3 bucket

AWS : how to get cognito user attributes in Lambda/cloud logic

Lambda function(js)

const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider();

function getUser(Username) {
    return new Promise((resolve, reject) => {
        cognito.adminGetUser({
            UserPoolId: process.env.COGNITO_USER_POOL_ID,
            Username: Username
        }, (err, data) => {
            if (err)
                reject(err.stack);
            else
                resolve(data.UserAttributes[2].Value);
        });
    });
}

async function getEmail(Username) {
  return await getUser(Username);
}

exports.handler = async (event) => {
    console.log(await getEmail('abcdef'));
    console.log(await getEmail('hijklmn'));
    console.log('done');
    return 'ok';
};
set Execution role with correct policy as below descripted.
set an environment 'COGNITO_USER_POOL_ID' with the user pool id.

Role policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "cognito-idp:AdminGetUser",
            "Resource": "your user pool arn"
        }
    ]
}
userpool

Eexcution Result

Response:
"ok"

Request ID:
"a327a22f-0363-11e9-a897-6f0106540a1d"

Function Logs:
START RequestId: a327a22f-0363-11e9-a897-6f0106540a1d Version: $LATEST
2018-12-19T07:56:49.880Z    a327a22f-0363-11e9-a897-6f0106540a1d    errong.leng
@gmail.com
2018-12-19T07:56:50.056Z    a327a22f-0363-11e9-a897-6f0106540a1d    errong.leng
@hotmail.com
2018-12-19T07:56:50.056Z    a327a22f-0363-11e9-a897-6f0106540a1d    done
END RequestId: a327a22f-0363-11e9-a897-6f0106540a1d
REPORT RequestId: a327a22f-0363-11e9-a897-6f0106540a1d  Duration: 1149.09 ms
 Billed Duration: 1200 ms    Memory Size: 128 MB Max Memory Used: 30 MB

Refers

AWS.CognitoIdentityServiceProvider.adminGetUser

node.js download file from aws s3 bucket via http request with AWS Signature Version 4

Main codes

function getYMD(d) {
    let r = d.getFullYear().toString();
    let m = d.getUTCMonth() + 1;
    if (m < 10)
      m = "0" + m.toString();
    else
      m = m.toString();
    r = r + m;
    let day = d.getUTCDate();
    if (day < 10)
      day = "0" + day.toString();
    else
      day = day.toString();
    r = r + day;
    return r;
}

function getTZ(d) {
    let r = d.getFullYear().toString();
    let m = d.getUTCMonth() + 1;
    if (m < 10)
      m = "0" + m.toString();
    else
      m = m.toString();
    r = r + m;
    let day = d.getUTCDate();
    if (day < 10)
      day = "0" + day.toString();
    else
      day = day.toString();
    r = r + day + "T";
    let h = d.getUTCHours();
    if (h < 10)
      h = "0" + h.toString();
    else
      h = h.toString();
    r = r + h;
    let min = d.getUTCMinutes();
    if (min < 10)
      min = "0" + min.toString();
    else
      min = min.toString();
    r = r + min;
    let s = d.getUTCSeconds();
    if (s < 10)
      s = "0" + s.toString();
    else
      s = s.toString();
    r = r + s;
    r = r + "Z";
    return r;
}

const crypto = require('crypto');
function Hmac(key, string){
    const hmac = crypto.createHmac('sha256', key);
    hmac.end(string);
    return hmac.read();
}

function Signature(date, region, service, toSign) {
    let dateKey = Hmac('AWS4' + AWS_SECRETACCESS_KEY, date);
    let dateRegionKey = Hmac(dateKey, region);
    let dateRegionServiceKey = Hmac(dateRegionKey, service);
    let signingKey = Hmac(dateRegionServiceKey, 'aws4_request');
    let signature = Hmac(signingKey, toSign).toString('hex');
    return signature;
}

function stringToSign(timeStamp, scope, canonicalRequest) {
    var signParts = [];
    signParts.push('AWS4-HMAC-SHA256');
    signParts.push(timeStamp);
    signParts.push(scope);
    signParts.push(crypto.createHash('sha256').update(canonicalRequest).digest(
'hex'));
    var result = signParts.join('\n');
    console.log('string to sign');
    console.log(result);
    return result;
};

function canonicalRequest(method, uri, queryString, headers, signedHeaders, has
hedPayload) {
  var canonicalParts = [];
  canonicalParts.push(method);
  canonicalParts.push(uri);
  canonicalParts.push(queryString);
  canonicalParts.push(headers);
  canonicalParts.push(signedHeaders);
  canonicalParts.push(hashedPayload);
  var result = canonicalParts.join('\n');
  console.log('canonical request');
  console.log(result);
  return result;
}

function download(filepath) {
    let filename = filepath;
    let position = filename.lastIndexOf('/');
    if (position != -1) {
      filename = filename.substr(position+1);
    }
    let d = new Date();
    let dtz = getTZ(d);
    let dymd = getYMD(d);
    let scope = dymd + '/' + AWS_REGION + '/' + AWS_SERVICE + '/aws4_request';
    let uri = filepath;
    let queryString = "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + en
codeURIComponent(AWS_KEY + "/" + scope) + "&X-Amz-Date=" + dtz + "&X-Amz-Expire
s=400&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3Bfilena
me%3D" + filename;
    let headers = "host:s3.amazonaws.com";
    let signedHeaders = "";
    let hashedPayload = "host\nUNSIGNED-PAYLOAD";
    let request = canonicalRequest('GET', uri, queryString, headers, signedHead
ers, hashedPayload);
    let signature = Signature(dymd, AWS_REGION, AWS_SERVICE, stringToSign(dtz,
scope, request));
    let url = 'https://s3.amazonaws.com' + filepath + '?response-content-dispos
ition=attachment;filename=' + filename + '&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-A
mz-Credential=' + AWS_KEY + "/" + scope + '&X-Amz-Date=' + dtz + '&X-Amz-Expire
s=400&X-Amz-SignedHeaders=host&X-Amz-Signature=' + signature;
    console.log(url);
    var link = document.createElement('a');
    link.innerHTML = 'download';
    link.href = url;
    document.body.appendChild(link);
}

Usage

const AWS_KEY = 'your aws key';
const AWS_SECRETACCESS_KEY = 'your aws secret access key';
const AWS_REGION = 'your bucket region'; // default us-east-1
const AWS_SERVICE = 's3';

download('/yourbucket/objectkey');

examle

download

Refers

GET Object
Signing AWS Requests with Signature Version 4
Authenticating Requests: Using Query Parameters (AWS Signature Version 4)
Key point:
?response-content-disposition=attachment;filename=objectname
Using this header, browser will open an save dialog once the down load url is correct signed.
You will get a url link like:
https://s3.amazonaws.com/bucket/objectkey?response-content-disposition=attachment;filename=objectname&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJWWH7EGSUBWE34IQ/20181218/us-east-1/s3/aws4_request&X-Amz-Date=20181218T053905Z&X-Amz-Expires=400&X-Amz-SignedHeaders=host&X-Amz-Signature=71d82d4a99338d777bfa3517315d34cad94619a9ca891222808e16dd02de7835

easy tab component, pure js + css, without react


This is tab example 1
Passion Organization Content Engagement this is passion pannel this is organization pannel this is engagement pannel
This is tab example 2
Passion Organization Content Engagement this is passion pannel 2 this is organization pannel 2 this is engagement pannel 2
end test
This is haha node

styles

<style>
.container {
    padding-right: 15px;
    padding-left: 15px;
    margin-right: auto;
    margin-left: auto;
}
.tabs {
    padding-left: 0;
    margin-bottom: 0;
    list-style: none; 
}
ol, ul {
    margin-top: 0;
    margin-bottom: 10px;
}
.tabs > li {    
    margin-bottom: -1px;
    position: relative;
    display: inline-block; 
 padding: 6px 12px;
 border-radius: 5px 5px 0 0;
 background-color: #aac4bd;
 border: 2px solid #7ea299;
 margin-right: 2px;
 color: #fff;
 cursor: pointer;
}
.tabs > li.active {
 background-color: #fbfbfc;
 border-bottom: 0;
 padding-bottom: 8px;
 color: #7ea299; 
}
.tabs > li:hover {
 background-color: #fbfbfc;
 color: #7ea299;  
}
.tabcontent {
 border: 2px solid #7ea299;
 -webkit-box-shadow: .055rem .055rem 1.11rem hsla(0,0%,8%,.27);
 box-shadow: .055rem .055rem 1.11rem hsla(0,0%,8%,.27);
 background-color: #fbfbfc;
 margin: -1px 0 0;
 padding: 4px; 
}
.tabcontent > .panel {
    display: none;
}
.tabcontent > .active {
    display: block;
}
</style>

use example

<div>
This is tab example 1
</div>
<Tabs>
<TabList>
  <Tab>Passion</Tab>
  <Tab>Organization Content</Tab>
  <Tab>Engagement</Tab>
</TabList>
<TabPanel>
  this is passion pannel
</TabPanel> 
<TabPanel>
this is organization pannel
</TabPanel>
<TabPanel>
this is engagement pannel
</TabPanel> 
</Tabs>
<div>
This is tab example 2
</div>
<Tabs>
<TabList>
  <Tab>Passion</Tab>
  <Tab>Organization Content</Tab>
  <Tab class='active'>Engagement</Tab>
</TabList>
<TabPanel>
  this is passion pannel 2
</TabPanel> 
<TabPanel>
this is organization pannel 2
</TabPanel>
<TabPanel>
this is engagement pannel 2
</TabPanel> 
</Tabs>
<div>
end test
</div>

javascript

<script type="text/javascript">
document.querySelectorAll('Tabs').forEach(function(tabs) {
  let pn = tabs.parentNode;
  let ct = document.createElement('div');
  ct.className = 'container';
  let ul = document.createElement('ul');
  ul.className = 'tabs';
  ct.appendChild(ul);
  let ai = 0;
  let index = 0;
  tabs.querySelectorAll('Tab').forEach(function(tab) {
    let li = document.createElement('li');
 li.className = tab.className;
 if (li.className.indexOf('active') != -1) {
   ai = index;
 }
 li.innerText = tab.innerText;
 li.id = index;
 li.onclick = function(e) {
   e = e || window.event;
      let target = e.target || e.srcElement;
   if (target.className.indexOf('active') != -1)
     return;  
   let ft = target.parentNode.querySelector('.active');
   ft.className = ft.className.replace('active', '');
   target.className = target.className + " active";
   let cp = target.parentNode.parentNode.querySelector('.tabcontent .active');
      cp.className = cp.className.replace('active', '');
   cp = target.parentNode.parentNode.querySelector('.tabcontent #panel'+target.id);
      cp.className = cp.className + " active";
   // target.parentNode.parentNode.style.display = 'block';
 }
    ul.appendChild(li); 
 index = index+1;
  });
  if (ai == 0) {
    if (ul.firstChild.className.indexOf('active') == -1) {
   ul.firstChild.className += ' active';
 }
  }
  let tc = document.createElement('div');
  tc.className = 'tabcontent';
  ct.appendChild(tc);
  index = 0;
  tabs.querySelectorAll('TabPanel').forEach(function(tabpanel) {
    let pl = document.createElement('div');
 pl.className = 'panel';
 pl.id = 'panel'+index;
 if (index == ai) {
   pl.className += ' active';
 }
 pl.innerHTML = tabpanel.innerHTML;
 tc.appendChild(pl);
 index = index+1;
  });
  pn.insertBefore(ct, tabs);
  pn.removeChild(tabs);
});
</script>

How to Record video use getUserMedia and MediaRecorder API

example


sample codes

  <div>
    <video id='camera'></video>
  </div>
  <script type='text/javascript'>
    var p = navigator.mediaDevices.getUserMedia({ audio: true, video: true });
    p.then(function(mediaStream) {
      var video = document.querySelector('video');
      video.src = window.URL.createObjectURL(mediaStream);
      video.onloadedmetadata = function() {
        video.muted = true;
        video.play();
      }
      var mediaRecorder = new MediaRecorder(mediaStream);
      var chunks = [];
      mediaRecorder.ondataavailable = function(e) {
        chunks.push(e.data);
      }
      mediaRecorder.onstop = function() {
        var blob = new Blob(chunks, {'type' : 'video/webm'});
        chunks = [];
        var hyperlink = document.createElement('a');
        hyperlink.href = URL.createObjectURL(blob);
        video.src = hyperlink.href;
        video.muted = false;
        video.controls = true;
        hyperlink.download = 'record.mp4';
        hyperlink.style = 'display:none;opacity:0;color:transparent;';
        (document.body || document.documentElement).appendChild(hyperlink);
if (typeof hyperlink.click === 'function') {
  hyperlink.click();
} else {
            hyperlink.target = '_blank';
            hyperlink.dispatchEvent(new MouseEvent('click', {
                view: window,
                bubbles: true,
                cancelable: true
            }));
        }     
    }
      mediaRecorder.start();      
      setTimeout(function() {
        mediaRecorder.stop();
        video.src = '';
        video.muted = false;
        video.stop();
      }, 10000);
    });
</script>

Ghost publishing platform sources: how hbs template engine is set to its site app

core js codes

in core/server/services/themes/active.js:

        // Set the views and engine         siteApp.set('views', this.path);         siteApp.engine('hbs', engine.configure(this.partialsPath)); 

Refers

https://expressjs.com/en/guide/using-template-engines.html
https://docs.ghost.org/api/handlebars-themes/

sqlite3 show all tables of a database

show all tables of a database

open database file via sqlite3
sqlite3 content/data/ghost-dev.db
command to list all tables
.tables || .table

$sqlite3  content/data/ghost-dev.db       SQLite version 3.11.0 2016-02-15 17:29:24 Enter ".help" for usage hints. sqlite> .tables accesstokens            migrations              refreshtokens          api_keys                migrations_lock         roles                  app_fields              mobiledoc_revisions     roles_users            app_settings            permissions             sessions               apps                    permissions_apps        settings               brute                   permissions_roles       subscribers            client_trusted_domains  permissions_users       tags                   clients                 posts                   users                  integrations            posts_authors           webhooks               invites                 posts_tags             

show table structure

select * from tablename;

sqlite> select * from posts; id|uuid|title|slug|mobiledoc|html|comment_id|plaintext|feature_image|featured|page|status|locale|visibility|meta_title|meta_description|author_id|created_at|created_by|updated_at|updated_by|published_at|published_by|custom_excerpt|codeinjection_head|codeinjection_foot|og_image|og_title|og_description|twitter_image|twitter_title|twitter_description|custom_template 

Hack to ghost core: mount/add new apps/sites on your ghost server

Install ghost from source

First, you should know how to develop with ghost.

create a new react app under core/server folder

simple example
core/server$ mkdir newapp
core/server$ cd newapp
core/server/newapp$ 
Ghost/core/server/web/newapp$ ls
app.js  controller.js  index.js
Ghost/core/server/web/newapp$ cat index.js 
module.exports = require('./app');
Ghost/core/server/web/newapp$ cat app.js 
const express = require('express');
module.exports = function setupNewApp() {
    const newApp = express();
    newApp.get('*', require('./controller'));
    return newApp;
};
Ghost/core/server/web/newapp$ cat controller.js 
const path = require('path');

module.exports = function newAppController(req, res) {
    res.send('Welcome to new App!');
};

Add route for your new site/app

./core/server/app.js
    // Mount the  apps on the parentApp
  
    // ADMIN
    parentApp.use('/ghost', require('./admin')());

    // import new app/site
    parentApp.use('/newsite', require('./newapp')());
    
    // BLOG
    parentApp.use(require('./site')()); 

Notes

above are for ghost before 2.0 version.
for latest ghost, version 2.0,
The folder for new app should be "Ghost/core/server/web/newapp"
The hacked core js is Ghost/core/server/web/parent-app.js
The code for add route is same as above.

Work your new app

grunt dev
navigate to http://localhost:2368/newapp, you will get:
newapphacktoghost

A simple web audio player, bind to <span/>, one click, audio play, click again, audio stop

example

All you have to is to write below html code:
a span with class name of word-audio and attribute of data-src pointed to a audio stream resource
 
<span class='word-audio audio' style='display:inline-block' 
data-src='https://cdn.mp3xa.pw/proxy/cs1-43v4.vkuseraudio.net/
p17/fe6d95af2cee33.mp3'></span> 
 

Bertie Higgins — Casablanca

word.js

  
  function startAnimation(e) {
    if (e.className == 'word-audio audio')
      e.className = 'word-audio audio-light';
    else if (e.className == 'word-audio audio-light')
      e.className = 'word-audio audio-playing';
    else
      e.className = 'word-audio audio'
    console.log(e.className);
  }
  function play(e, context, audioBuffer) {
    if (e.state == 1) {
      e.source.stop();
      e.source.onended();
      e.source = null;
    } else {
      e.state = 1;
      const source = context.createBufferSource();
      e.source = source;
      source.buffer = audioBuffer;
      source.connect(context.destination);
      source.start();
      let it = setInterval(function() {startAnimation(e)}, 300);    
      source.onended = function() {
        e.state = 0;
        clearInterval(it);
        e.className = 'word-audio audio';
      }
    }
  }
  document.querySelectorAll('.word-audio').forEach(function(e, index) {
    let url = e.attributes['data-src'].nodeValue;
    let context = new AudioContext();
    e.state = 0;
    let wordBuffer;
    window.fetch(url)
      .then(response => response.arrayBuffer())
      .then(arrayBuffer => context.decodeAudioData(arrayBuffer))
      .then(audioBuffer => {
        e.disabled = false;
        wordBuffer = audioBuffer;
        //play(e, context, wordBuffer);
      });
    e.onclick = function() {
      play(e, context, wordBuffer);       
    }
  });

word.css

.audio {
    display: inline-block;
    width: 20px;
    height: 20px;
    position: relative;
    overflow: hidden;
    cursor: pointer;
    vertical-align: middle;
    background: url(audio.png) no-repeat -40px 0/auto 100%;
}

.audio-playing {
    display: inline-block;
    width: 20px;
    height: 20px;
    position: relative;
    overflow: hidden;
    cursor: pointer;
    vertical-align: middle;
    background: url(audio.png) no-repeat -20px 0/auto 100%;
}

.audio-light {
    display: inline-block;
    width: 20px;
    height: 20px;
    position: relative;
    overflow: hidden;
    cursor: pointer;
    vertical-align: middle;
    background: url(audio.png) no-repeat 0px 0/auto 100%;
}

How to extract media files from a anki flashcard package(*.apkg)

*.apkg

The .apkg file is a zip file (as you know). Inside, you'll find the collection (an sqlite3 database of notes/cards), the media file which you were trying to open, and a bunch of numbered files 0-whatever number(these are media files).

Just use 7zip tool open the apkg file and extract into a folder.
open the "media" file via a text editor tool to find the media file extension.

example of "media"

{"16486": "COCA_16486.mp3", } 

rename "16486" file to a mp3 file, then you got the media file.

how media used in flash card

Export your flash cards into a plain text.
export_anki

[sound:COCA_16486.mp3]

spatially	<div style=''>英['speɪʃəlɪ]  美['speɪʃəlɪ]</div>	<div style=''>adv.空间地,存在于空间地;</div>	"<div><br /></div><div style='color:RosyBrown'>ADJ</div><div style='color:OrangeRed'>空间的;与空间有关的</div><div style=""font-weight:bold;"">Spatial is used to describe things relating to areas.</div><div style=''><b>例:</b>...the spatial distribution of black employment and population in South Africa.南非黑人就业与人口的空间分布</div><div style=''><b>例:</b>...spatial constraints.空间的限制</div><div><br /></div><div style='color:RosyBrown'>ADJ</div><div style='color:OrangeRed'>(能力)理解立体空间的</div><div style=""font-weight:bold;"">Your spatial ability is your ability to see and understand the relationships between shapes, spaces, and areas.</div><div style=''><b>例:</b>His manual dexterity and fine spatial skills were wasted on routine tasks.他灵巧的动手能力和杰出的空间识别能力都浪费在日常事务上了。</div><div style=''><b>例:</b>...spatial awareness.空间方位感</div>"	<div style='color:DarkOrange; font-style:italic;'>adj:spatial; </div>		[sound:COCA_16486.mp3]	 

example of web flash card

spatially

英['speɪʃəlɪ] 美['speɪʃəlɪ]
adv.空间地,存在于空间地;
"

ADJ
空间的;与空间有关的
<div style=""font-weight:bold;"">Spatial is used to describe things relating to areas.
例:...the spatial distribution of black employment and population in South Africa.南非黑人就业与人口的空间分布
例:...spatial constraints.空间的限制

ADJ
(能力)理解立体空间的
<div style=""font-weight:bold;"">Your spatial ability is your ability to see and understand the relationships between shapes, spaces, and areas.
例:His manual dexterity and fine spatial skills were wasted on routine tasks.他灵巧的动手能力和杰出的空间识别能力都浪费在日常事务上了。
例:...spatial awareness.空间方位感
"
adj:spatial;

fix up chromium m47 build error: unsupported reloc 42 against global symbol gmon_start

Error:
ld.gold: error: /usr/lib/gcc/x86_64-linux-gnu/5.3.1/../../../x86_64-linux-gnu/crti.o: unsupported reloc 42 against global symbol gmon_start

Solution:
export GYP_DEFINES="linux_use_bundled_gold=0"

bizcharts example: Line Chart

Chart UI

Line_Chart

Codes

ChartComponent.js
import React, { Component } from 'react';
import {Chart, Axis, Tooltip, Geom, Coord, Label} from 'bizcharts';
import DataSet from '@antv/data-set';
const { DataView } = DataSet;

export class LineChart extends Component {
  render() {
    const { data, width, height } = this.props;
    if (!data || data.length <= 0)
      return null;
    const ytitle = {
      autoRotate: true,
      offset: -10,
      textStyle: {
        fontSize: '22',
        textAlign: 'left',
        fill: 'rgb(75,83,87)',
        rotate: 0
      },
      position: 'end',
    };
    const xtitle = {
      autoRotate: true,
      offset: -20,
      textStyle: {
        fontSize: '22',
        textAlign: 'center',
        fill: 'rgb(75,83,87)',
        rotate: 0
      },
      position: 'end',
    };
    const line = {
      stroke: 'rgb(197,197,200)',
      lineWidth: 2
    };
    const axis = [];
    let i = 0;
    for (let key in data[0]) {
      axis[i++] = key;
    }
    let cols = {};
    cols[axis[1]] = { min: 0};
    cols[axis[0]] = { range: [ 0, 1 ] };
    const position = axis[0] + '*' + axis[1];
    return (
      <div style={{width:width,height:height}}>
      <Chart width={width} height={height} data={data} scale={cols} padding='auto'>
        <Axis name={axis[0]} title={xtitle} tickLine={null} line={line} label={null}/>
        <Axis name={axis[1]} title={ytitle} tickLine={null} line={line} label={null}/>
        <Tooltip/>
        <Geom type="line" position={position} size={2} color='rgb(82,63,91)'/>
        <Geom type='point' position={position} size={4} shape={'circle'}
          color='rgb(236,142,91)' style={{ stroke: 'rgb(236,142,91)', lineWidth: 2}}>
        </Geom>
      </Chart>
      </div>
   )
  }
};
 
App.js
import React, { Component } from 'react';
import {LineChart} from './ChartComponent';
import './App.css';

class App extends Component {
  render() {
    const data = [];
    let i = 0;
    for (i = 0; i < 10; i++) {
      let time = 1000*Math.random();
      let score = 10*Math.random();
      data.push({'Timeline':time, 'Score':score});
    }
    return (
      <div className="App">
        <LineChart data={data} width={400} height={400}>
        </LineChart>
      </div>
    );
  }
}

export default App;
 

Dependences

yarn add bizcharts yarn add @antv/data-set

Refers

https://alibaba.github.io/BizCharts/demo-detail.html?code=demo/g2/clock
https://github.com/alibaba/BizCharts/tree/master/doc/tutorial
 

bizcharts example : Doughnut Chart

Chart UI

chart_examples

key codes explaintion:

<Coord type='theta' innerRadius={0.45} />
without 'innerRadius={0.45}', UI will looks like:
chart_examples_full
without below codes, UI will looks like:
<Geom select={[false,{}]} type='intervalStack' position='percent' 
  color={['type', ['rgba(255, 255, 255, 0)']]} 
  style={{stroke: 'rgba(152,191,182,1)', lineWidth: 1}}> 
</Geom> 
chart_examples_null

Codes

ChartComponent.js
import React, { Component } from 'react';
import {Chart, Axis, Tooltip, Geom, Coord, Label} from 'bizcharts';
import DataSet from '@antv/data-set';
const { DataView } = DataSet;

export class ScoreChart extends Component {
  render() {
    const { width, height, score } = this.props;
    const scoreData = [
      { type: 'Score', value: score },
      { type: '', value: 10 - score },
    ];
    const scoreDv = new DataView();
    scoreDv.source(scoreData)
      .transform({
      type: 'percent',
      field: 'value',
      dimension: 'type',
      as: 'percent'
    });
    const scoreColor = (type) => {
      if (type === 'Score')
        return 'rgb(152,191,182)';
      return 'white';
    };
    return (
      <Chart data={scoreDv} width={width} height={height} padding='auto'>
        <Coord type='theta' innerRadius={0.45} />
        <Geom select={[false,{}]} type='intervalStack' position='percent'
          color={['type', ['rgba(255, 255, 255, 0)']]}
          style={{stroke: 'rgba(152,191,182,1)', lineWidth: 1}}>
        </Geom>
        <Geom select={[false,{}]} type='intervalStack' position='percent'
          color={['type', scoreColor]}>
        </Geom>
      </Chart>
    )
  }
};

export class StarChart extends Component {
  render() {
    let { width, height, situation, action, task, result } = this.props;
    if (width < 200)
      width = 200;
    if (height < 200)
      height = 200;
    const starData = [
      { type: 'Situation', value: situation },
      { type: 'Action', value: action },
      { type: 'Task', value: task },
      { type: 'Result', value: result },
    ];
    const starDv = new DataView();
    starDv.source(starData)
      .transform({
      type: 'percent',
      field: 'value',
      dimension: 'type',
      as: 'percent'
    });
    const starColor = (type) => {
      if (type === 'Situation')
        return 'rgb(208,210,211)';
      if (type === 'Action')
        return 'rgb(151,191,182)';
      if (type === 'Task')
        return 'rgb(236,142,91)';
      if (type === 'Result')
        return 'rgb(64,43,74)';
      return 'transparent';
    };
    return (
      <Chart data={starDv} width={width} height={height} padding={['10%', '22%']}>
        <Coord type='theta' innerRadius={0.45} />
        <Geom select={[false,{}]} type='intervalStack' position='percent'
          color={['type', starColor]}>
          <Label content='type' offset={20}/>
        </Geom>
      </Chart>
    )
  }
}
App.js
import React, { Component } from 'react';
import {ScoreChart, StarChart} from './ChartComponent';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <StarChart width={200} height={200}
          situation={20} action={40} task={40} result={20}>
        </StarChart>
        <ScoreChart width={120} height={120}
          score={7}>
        </ScoreChart>
      </div>
    );
  }
}

export default App;

Dependences

yarn add bizcharts yarn add @antv/data-set 

Refers

https://alibaba.github.io/BizCharts/demo-detail.html?code=demo/g2/clock
https://github.com/alibaba/BizCharts/tree/master/doc/tutorial

sed replace shell variable which include slash ('/')

Test content

cat test-sed.txt
aaa/bbb/ccc/ddd/eee
fff/ggg/ss

replace "ccc/ddd" to "CCC/DDD" via sed

NEW_STRING="CCC/DDD"
sed -i "s:ccc/ddd:${NEW_STRING}:" test-sed.txt

Notes:

  1. use ':' as seperate char rather than '/', which is default
  2. use "" include the replace command

Await is a reserved word error

Solution:

In order to use await, the function directly enclosing it needs to be async.
example codes:

async tryGetUserName() {   let session = await Auth.currentSession();   ...... } 

async should be there, otherwise you will get the error.

Fix up: Can't Load URL: The domain of this URL isn't included in the app's domains.

Issue

cannotloadurl_fb_share
Can't Load URL: The domain of this URL isn't included in the app's domains. To be able to load this URL, add all domains and subdomains of your app to the App Domains field in your app settings.

Scenario

  1. create a facebook app to get an appid
  2. use that appid in website to initialized the facebook javascript SDK
  3. use FB.ui to share content to facebook

full source codes

<!-- init facebook js sdk via your appid -->
<script>
  window.fbAsyncInit = function() {
    FB.init({
      appId            : 'yourappid',
      autoLogAppEvents : true,
      xfbml            : true,
      version          : 'v3.1'
    });
  };
  (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "https://connect.facebook.net/en_US/sdk.js";
     fjs.parentNode.insertBefore(js, fjs);
   }(document, 'script', 'facebook-jssdk'));
</script>
<script>
function share() {
    // use FB.ui() to share content to facebook
    FB.ui(
      {
        method: 'share',
        href: 'urlyouwanttoshare',
      },
      // callback
      function(response) {
        if (response && !response.error_message) {
          alert('Posting completed.');
        } else {
          alert('Error while posting.');
        }
      }
    );
}
</script>

<button onclick="share()">share</button>

Solution

1.Go to your app in facebook developers page(Settings->Basic)
2.Scroll down and Click on Add platform, choose Web sites
addplatform_website
3.Input your website domain
if you are testing via http://localhost:8080 then input http://localhost:8080
4.Save the changes
Then test again. you will find everything goes well.
sharewell

Notes

App Domains is not working for web site share to facebook.
That might be used for mobile apps.

connect shadowsocks server on ubuntu via ss-local

#Server config
[Set up your own shadowsocks server](https://errong.win/2018/06/05/setup-shadowsocks-on-ubuntu-16-04/) or found/buy one.
You should have a shadowsocks server with below informations:
* server ip
* server port
* password
* method

#install
~~~
sudo apt-get install software-properties-common -y
sudo add-apt-repository ppa:max-c-lv/shadowsocks-libev -y
sudo apt-get update
sudo apt install shadowsocks-libev
~~~

#configure
match to the server's info.
~~~
sudo vim /etc/shadowsocks-libev/config.json
{
    "server":"xx.xx.xx.xx", // server IP
    "server_port":8911,
    "local_port":1008, // local port
    "password":"jesuslove",
    "timeout":60,
    "method":"chacha20-ietf-poly1305"
}
~~~

#run ss-local
```sudo ss-local -c  /etc/shadowsocks-libev/config.json```

#export socks proxy system wide
~~~
export http_proxy=socks5://127.0.0.1:1008
export https_proxy=socks5://127.0.0.1:1008
~~~

#use proxy extension set for chrome/firefox browser
config your proxy extension in your browser to use socks5 proxy
127.0.0.1:1008

c++: Max Heap Template

https://en.wikipedia.org/wiki/Min-max_heap
My simple implementation of max heap.
Change the compare from > to < in below codes, you will get a min heap.

template<typename T>
class MaxHeap {
public:
 MaxHeap() {
  size_ = 0;
  capacity_ = 2;
  heap_ = new T[capacity_];
 }
 MaxHeap(int capacity) {
  if (capacity >= 2) {
   heap_ = new T[capacity];
   capacity_ = capacity;
   size_ = 0;
  }
  else {
   size_ = 0;
   capacity_ = 2;
   heap_ = new T[capacity_];
  }
 }
 ~MaxHeap() {
  if (heap_)
   delete[] heap_;
 }
public:
 int size() { return size_; }
 void insert(const T& t) {
  if ((size_+1) >= capacity_) {
   capacity_ *= 2;
   T* tmp = new T[capacity_];
   for (int i = 0; i <= size_; i++)
    tmp[i] = heap_[i];
   delete[] heap_;
   heap_ = tmp;
  }
  size_++;
  heap_[size_] = t;
  int i = size_;
  int tmp = heap_[i];
  while (i > 1 && tmp > heap_[i / 2]) {
   heap_[i] = heap_[i / 2];
   heap_[i / 2] = tmp;
   tmp = heap_[i / 2];
   i /= 2;
  }
  heap_[i] = tmp;
 }
 T max() {
  return heap_[1];
 }
 int index(const T& t) {
  return index(1, t);
 }
 void removeByIndex(int i) {
  if (i < 1)
   return;
  if (heap_[size_] == heap_[i]) {
   heap_[i] = heap_[size_];
   size_--;
   return;
  }
  if (heap_[size_] < heap_[i]) {
   heap_[i] = heap_[size_];
   size_--;
   maxDown(i);
   return;
  }
  heap_[i] = heap_[size_];
  size_--;
  maxUp(i);
 }
 void remove(const T& t) {
  return removeByIndex(index(t));
 }
private:
 inline int index(int i, const T& t) {
  if (heap_[i] < t)
   return -1;
  if (heap_[i] == t)
   return i;
  int r = index(2 * i, t);
  if (r != -1)
   return r;
  return index(2 * i + 1, t);
 }
 inline void maxDown(int i) {
  if (i * 2 > size_ && (i * 2 + 1) > size_)
   return;
  int l = i * 2;
  int r = l + 1;
  int m = l;
  if (r <= size_ && heap_[r] > heap_[l])
   m = r;
  if (heap_[m] <= heap_[i])
   return;
  T tmp = heap_[i];
  heap_[i] = heap_[m];
  heap_[m] = tmp;
  maxDown(m);
 }
 inline void maxUp(int i) {
  if (i <= 1)
   return;
  int p = i / 2;
  if (heap_[p] >= heap_[i])
   return;
  T tmp = heap_[i];
  heap_[i] = heap_[p];
  heap_[p] = tmp;
  maxUp(p);
 }
private:
 T* heap_;
 int capacity_;
 int size_;
};

static MaxHeap<int>* all[101] = { 0, };
static int mt = 0;

void Init()
{
 for (int i = 1; i < mt; i++)
  if (all[i]) {
   delete all[i];
   all[i] = 0;
  }
 mt = 0;
}

void insertID(int ProductType, int ProductID)
{
 if (!all[ProductType])
  all[ProductType] = new MaxHeap <int>(50001) ;
 all[ProductType]->insert(ProductID);
 if ((ProductType + 1) > mt)
  mt = ProductType + 1;
}

int highestID(int ProductType)
{
 if (!all[ProductType] || all[ProductType]->size() <= 0)
  return -1;
 return all[ProductType]->max();
}

int findhighestID(int type)
{
 int maxType = -1;
 int maxId = -1;
 for (int i = 1; i < mt; i++)
  if (all[i] && all[i]->size() > 0) {
   int max = all[i]->max();
   if (max > maxId) {
    maxId = max;
    maxType = i;
   }
  }
 if (type == 0)
  return maxType;
 return maxId;
}

void demarket(int ProductID)
{
 for (int i = 1; i < mt; i++)
  if (all[i]) {
   int index = all[i]->index(ProductID);
   if (index >= 1) {
    all[i]->removeByIndex(index);
    break;
   }
  }
}

Ember.js : Custom "index" route's path and Custom "/" path's route

"index" Route

The index route, which will handle requests to the root URI (/) of our site.
How to generate a new route called index:
ember g route index
The index route is special: it does NOT require an entry in the router's mapping.

a Nested "index" Route

An index nested route works similarly to the base index route. It is the default route that renders when no route is provided. Use 'rentals' as example, when we navigate to /rentals, Ember will attempt to load the rentals index route as a nested route.
To create an index nested route, run the following command:
ember g route rentals/index
If you open up your Router (app/router.js) you may notice that the rentals line has changed. This extra function() {} is required because it needs a child route, the this.route('index', { path: '/'}); is implied.
Router.map(function() {
  this.route('about');
  this.route('contact');
  this.route('rentals', function() {
    // implied code
    // this.route('index', { path: '/'});
  });
});

replaceWith and transitionTo

Use "replaceWidth" and "transitionTo", We can simply forward "/" to the "rentals" route. You will found the url displayed in url bar will changed from "http://localhost" to "http://localhost/rentals".
Well, everything is fine here except one little fault.
I do not want this redirect from "/" to "/rentals".
I hope it always be "/".
Can we make it ?

Is "index/index" possible ?

I tried below command:
ember g route index/index
installing route
  create app/routes/index/index.js
  create app/templates/index/index.hbs
updating router
  add route index/index
installing route-test
  create tests/unit/routes/index/index-test.js
Command execute without errors, but blank page showed with "http://localhost".
It seems not work. Then I remember the impled code:
this.route('index', { path: '/'});
However, you can add the index route if you want to customize it. For example, you can modify the index route's path by specifying this.route('index', { path: '/custom-path'}).

Custom "index" route's path

We can custom "index" route's path by the impled code.
I guess We can custom the path's route also.
this.route('rentals', {path:'/'});
Ember server will handle requests to the root URI (/) to route "rentals" rather than the default "index" route now.
Can it works ? Let's try

Custom path's route

I use super-rentals as a example.
First I destroy "index" route.
ember d route index
Modify the app router.
git diff app/router.js
diff --git a/app/router.js b/app/router.js
index 254d53d..9f14d85 100644
--- a/app/router.js
+++ b/app/router.js
@@ -9,7 +9,7 @@ const Router = EmberRouter.extend({
 Router.map(function() {
   this.route('about');
   this.route('contact');
-  this.route('rentals', function() {
+  this.route('rentals', {path:'/'}, function() {
     this.route('show', { path: '/:rental_id' });
   });
 });
 
Modify templates, all link to "index" replaced with "rentals".
-  {{#link-to 'index' class="button"}}
+  {{#link-to 'rentals' class="button"}}
 
ember serve
I got what I wished.
The url bar is clean now.
Only "http://localhost", no "rentals" anymore.
super-rental

Refers

Routes and templates
Adding Nested Routes

Set up gitweb server on nginx

Precondition

please setup your git server via git-http-backend on nginx.

Setup gitweb conf

sudo apt-get install gitweb 
/etc/gitweb.conf is Gitweb (Git web interface) configuration file
The default project root is /usr/lib/git.
Here, I changed to my own path.
our $projectroot = "/home/errong_leng/www/git";

Change nginx conf

cat /etc/nginx/sites-enabled/git.errong.win.conf
server {
    listen  80;
    listen [::]:80;
    server_name git.errong.win;
    auth_basic "Restricted";
    auth_basic_user_file /home/errong_leng/.gitpasswd;
 location ~ ^.*\.git/(HEAD|info/refs|objects/info/.*|git-(upload|receive)-pack)$ {
  root /home/errong_leng/www/git;
  fastcgi_pass unix:/var/run/fcgiwrap.socket;
  fastcgi_param SCRIPT_FILENAME   /usr/lib/git-core/git-http-backend;
  fastcgi_param PATH_INFO         $uri;
  fastcgi_param GIT_PROJECT_ROOT  /home/errong_leng/www/git;
  fastcgi_param GIT_HTTP_EXPORT_ALL "";
  fastcgi_param REMOTE_USER $remote_user;
  include fastcgi_params;
 }
 location /index.cgi {
  root /usr/share/gitweb;
  include fastcgi_params;
  gzip off;
  fastcgi_param SCRIPT_NAME $uri;
  fastcgi_param GITWEB_CONFIG /etc/gitweb.conf;
  fastcgi_pass unix:/var/run/fcgiwrap.socket;
 }
 location / {
  root /usr/share/gitweb;
  index index.cgi;
 }
}
cat /etc/nginx/sites-enabled/git.errong.win-ssl.conf
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name git.errong.win;
    ssl_certificate /etc/letsencrypt/git.errong.win/fullchain.cer;
    ssl_certificate_key /etc/letsencrypt/git.errong.win/git.errong.win.key;
    auth_basic "Restricted";
    auth_basic_user_file /home/errong_leng/.gitpasswd;
 location ~ ^.*\.git/(HEAD|info/refs|objects/info/.*|git-(upload|receive)-pack)$ {
  root /home/errong_leng/www/git;
  fastcgi_pass unix:/var/run/fcgiwrap.socket;
  fastcgi_param SCRIPT_FILENAME   /usr/lib/git-core/git-http-backend;
  fastcgi_param PATH_INFO         $uri;
  fastcgi_param GIT_PROJECT_ROOT  /home/errong_leng/www/git;
  fastcgi_param GIT_HTTP_EXPORT_ALL "";
  fastcgi_param REMOTE_USER $remote_user;
  include fastcgi_params;
 }
 location /index.cgi {
  root /usr/share/gitweb;
  include fastcgi_params;
  gzip off;
  fastcgi_param SCRIPT_NAME $uri;
  fastcgi_param GITWEB_CONFIG /etc/gitweb.conf;
  fastcgi_pass unix:/var/run/fcgiwrap.socket;
    }
 location / {
  root /usr/share/gitweb;
  index index.cgi;
 }
}
Restart nginx server.

gitweb done

git.errong.win

errors handle

If your gitweb server can't work.
You can check the error log to analysis the cause.
cat /var/log/nginx/error.log
In my case, I encountered below errors.
It turned out that my system did not install perl module for CGI and HTML::Entities.
I installed them manually from sources.
Download CGI source
Download HTML::Entities source
I have a post for this. Install perl module from source
2018/06/13 04:35:36 [error] 28827#28827: *6986 FastCGI sent in stderr: "Can't locate CGI.pm in @INC (you may nee
d to install the CGI module) (@INC contains: /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1 /usr/local/sh
are/perl/5.22.1 /usr/lib/x86_64-linux-gnu/perl5/5.22 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.22 /usr/s
hare/perl/5.22 /usr/local/lib/site_perl /usr/lib/x86_64-linux-gnu/perl-base .) at /usr/share/gitweb/index.cgi li
ne 13.
BEGIN failed--compilation aborted at /usr/share/gitweb/index.cgi line 13" while reading response header from ups
tream, client: 58.213.161.114, server: git.errong.win, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var
/run/fcgiwrap.socket:", host: "git.errong.win"
2018/06/13 04:35:36 [error] 28827#28827: *6986 upstream prematurely closed FastCGI stdout while reading response
 header from upstream, client: 58.213.161.114, server: git.errong.win, request: "GET / HTTP/1.1", upstream: "fas
tcgi://unix:/var/run/fcgiwrap.socket:", host: "git.errong.win"


2018/06/13 04:58:03 [error] 28827#28827: *7008 FastCGI sent in stderr: "[Wed Jun 13 04:58:03 2018] index.cgi: Ca
n't locate HTML/Entities.pm in @INC (you may need to install the HTML::Entities module) (@INC contains: /etc/per
l /usr/local/lib/x86_64-linux-gnu/perl/5.22.1 /usr/local/share/perl/5.22.1 /usr/lib/x86_64-linux-gnu/perl5/5.22 
/usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.22 /usr/share/perl/5.22 /usr/local/lib/site_perl /usr/lib/x86_
64-linux-gnu/perl-base .) at /usr/local/share/perl/5.22.1/CGI.pm line 2219" while reading response header from u
pstream, client: 58.213.161.114, server: git.errong.win, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/v
ar/run/fcgiwrap.socket:", host: "git.errong.win"
2018/06/13 04:58:30 [error] 28827#28827: *7010 FastCGI sent in stderr: "[Wed Jun 13 04:58:30 2018] index.cgi: Ca
n't locate HTML/Entities.pm in @INC (you may need to install the HTML::Entities module) (@INC contains: /etc/per
l /usr/local/lib/x86_64-linux-gnu/perl/5.22.1 /usr/local/share/perl/5.22.1 /usr/lib/x86_64-linux-gnu/perl5/5.22 
/usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.22 /usr/share/perl/5.22 /usr/local/lib/site_perl /usr/lib/x86_
64-linux-gnu/perl-base .) at /usr/local/share/perl/5.22.1/CGI.pm line 2219" while reading response header from u
pstream, client: 210.94.41.89, server: git.errong.win, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/var
/run/fcgiwrap.socket:", host: "git.errong.win"

Install Perl module on ubuntu

Install by cpan

cpan -i foo
Replace foo with the module name you want to install.

Install from source

Search the module on cpan.org
Download the source.
Let's use HTML::Entities for example.
You will get your search url:
http://search.cpan.org/~gaas/HTML-Parser-3.72/lib/HTML/Entities.pm
The source url will be:
http://www.cpan.org/authors/id/G/GA/GAAS/HTML-Parser-3.72.tar.gz

Make and install perl module

  tar -xvf HTML-Parser-3.72.tar.gz   
  cd HTML-Parser-3.72/ 
  perl Makefile.PL
  make
  sudo make install 

setup http/https git server on nginx via git-http-backend

Precondition

sudo apt-get install nginx fcgiwrap git apache2-utils 

Set up https server

First, please setup your https server by your self.
You can refer to my guide

git-http-backend

git-http-backend is a Server side implementation of Git over HTTP.
/usr/lib/git-core/git-http-backend

Set Up Password Authentication file for your git server

We can get a password with MD5-based password algorithm, Apache variant via openssl passwd command.
You can add a username to the file using this command. We are using sammy as our username, but you can use whatever name you'd like:
sudo sh -c "echo -n 'sammy:' >> .gitpasswd" 
Next, add an encrypted password entry for the username by typing:
sudo sh -c "openssl passwd -apr1 >> .gitpasswd" 
You can repeat this process for additional usernames. You can see how the usernames and encrypted passwords are stored within the file by typing:
cat .gitpasswd 
Output
sammy:$apr1$wI1/T0nB$jEKuTJHkTOOWkopnXqC1d1
Or We can use The htpasswd utility, found in the apache2-utils package, serves this function well.
Let's add a new user kimmy via htpasswd, below is command line.
htpasswd -c .gitpasswd kimmy 
cat .gitpasswd 
sammy:$apr1$wI1/T0nB$jEKuTJHkTOOWkopnXqC1d1 
kimmy:$apr1$sBPFn6ek$L8Ta2LkiuXzi7bQZUqUlq0 

http/https nginx conf

cat /etc/nginx/sites-enabled/git.errong.win.conf
server {
    listen  80;
    listen [::]:80;
    server_name git.errong.win;
    auth_basic "Restricted";
    auth_basic_user_file /home/errong_leng/.gitpasswd;
    location ~ (/.*) {
        fastcgi_pass  unix:/var/run/fcgiwrap.socket;
        include       fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        fastcgi_param GIT_PROJECT_ROOT    /home/errong_leng/www/git;
        fastcgi_param REMOTE_USER         $remote_user;
        fastcgi_param PATH_INFO           $uri;
    }
}
cat /etc/nginx/sites-enabled/git.errong.win-ssl.conf
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name git.errong.win;
    ssl_certificate /etc/letsencrypt/git.errong.win/fullchain.cer;
    ssl_certificate_key /etc/letsencrypt/git.errong.win/git.errong.win.key;
    auth_basic "Restricted";
    auth_basic_user_file /home/errong_leng/.gitpasswd;
    location ~ (/.*) {
        fastcgi_pass  unix:/var/run/fcgiwrap.socket;
        include       fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        fastcgi_param GIT_PROJECT_ROOT    /home/errong_leng/www/git;
        fastcgi_param REMOTE_USER         $remote_user;
        fastcgi_param PATH_INFO           $uri;
    }
}
OK, nginx server config is done, just reload it.(sudo nginx -s reload)
Now it is time to set up git repository under the root(/home/errong_leng/www/git)

Set up git repository

$ cd www/git/ 
$ mkdir helloworld.git 
$ cd helloworld.git/ 
$ git --bare init 
Initialized empty Git repository in /home/errong_leng/www/git/helloworld.git/ 
$ cp hooks/post-update.sample hooks/post-update 
$ chmod a+x hooks/post-update 
$ chmod a+w . -R 
Now, We can git clone and push to the respository on remote machine via http/https protocol.

git clone helloworld.git

git clone https://git.errong.win/helloworld.git
Cloning into 'helloworld'...
Username for 'https://git.errong.win': lenger
Password for 'https://lenger@git.errong.win':
warning: You appear to have cloned an empty repository.
Checking connectivity... done.

git push helloworld.git

git push origin master
Username for 'https://git.errong.win': lenger
Password for 'https://lenger@git.errong.win':
Counting objects: 3, done.
Writing objects: 100% (3/3), 205 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://git.errong.win/helloworld.git
  • [new branch] master -> master

setup https server via An ACME Shell script on nginx

I will give every detail steps when I setup https server for https://git.errong.win.
You can refer as a guide.

acme.sh

acme.sh is probably the easiest & smartest shell script toautomatically issue & renew the free certificates from Let's Encrypt.
Install the shell script via guide

http server nginx conf

First, let's setup a http server first via nginx.
After you installed nginx, the simple way to setup a http server is to add a conf file under /etc/nginx/sites-enabled/ folder.
cat /etc/nginx/sites-enabled/git.errong.com.conf
server {
    listen       80;
    listen       [::]:80;
    server_name  git.errong.com;

    location / {
        root   html;
        index  index.html index.htm;
    }
}
Reload nginx via sudo nginx -s reload

issue free certificates

sudo acme.sh --issue --home /etc/letsencrypt --domain example.com --webroot /home/errong_leng/www/git --reloadcmd "nginx -s reload" --accountemail errong.leng@gmail.com
[Wed Jun 13 01:51:07 UTC 2018] Single domain='git.errong.win'
[Wed Jun 13 01:51:07 UTC 2018] Getting domain auth token for each domain
server {
[Wed Jun 13 01:51:07 UTC 2018] Getting webroot for domain='git.errong.win'
[Wed Jun 13 01:51:07 UTC 2018] Getting new-authz for domain='git.errong.win'
[Wed Jun 13 01:51:08 UTC 2018] The new-authz request is ok.
[Wed Jun 13 01:51:08 UTC 2018] Verifying:git.errong.win
[Wed Jun 13 01:51:11 UTC 2018] Success
[Wed Jun 13 01:51:11 UTC 2018] Verify finished, start to sign.
[Wed Jun 13 01:51:12 UTC 2018] Cert success.

[Wed Jun 13 01:51:12 UTC 2018] Your cert is in  /etc/letsencrypt/git.errong.win/git.errong.win.cer 
[Wed Jun 13 01:51:12 UTC 2018] Your cert key is in  /etc/letsencrypt/git.errong.win/git.errong.win.key 
[Wed Jun 13 01:51:12 UTC 2018] The intermediate CA cert is in  /etc/letsencrypt/git.errong.win/ca.cer 
[Wed Jun 13 01:51:12 UTC 2018] And the full chain certs is there:  /etc/letsencrypt/git.errong.win/fullchain.cer 
[Wed Jun 13 01:51:12 UTC 2018] Run reload cmd: nginx -s reload
[Wed Jun 13 01:51:12 UTC 2018] Reload success
Ok. We have ssl certificates now.
ssl_certificate /etc/letsencrypt/git.errong.win/fullchain.cer; 
ssl_certificate_key /etc/letsencrypt/git.errong.win/git.errong.win.key; 

https server nginx conf

cat /etc/nginx/sites-enabled/git.errong.win-ssl.conf
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name git.errong.win;
    location / {
        root   html;
        index  index.html index.htm;
    }
}
 
Now. everything is ready.
Just reload nginx server.
You will find http://git.errong.win and https://git.errong.win work welll now.
Remind :
please replace the server name "git.errong.win" to yours.

Samba add user for share

add user

sudo smbpasswd -a USER
sudo smbpasswd -e USER

set up share path

/etc/samba/smb.conf
[USER] 
       comment = USER home 
       path = /home/USER 
       writeable = yes 
       public=yes 
       browseable = yes 
       create mode = 776 
       force directory mode = 776 

restart samba service

sudo systemctl restart smbd.service
sudo systemctl restart nmbd.service

if still can't access, then check below config

/etc/samba/smb.conf
[global]    hosts allow = #add your IP here 
restart samba service again, it should work now.

Fix issue : grunt init failed with error Couldn't find match for "

Issue

PS D:\Ghost> grunt init
Running "update_submodules:pinned" (update_submodules) task

Running "subgrunt:init" (subgrunt) task
yarn install v1.7.0
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
warning Pattern ["glob@latest"] is trying to unpack in the same destination "C:\\Users\\lenger\\AppData\\Local\\Yarn\\Ca
che\\v1\\npm-glob-7.1.2-c19c9df9a028702d678612384a6552404c636d15" as pattern ["glob@^7.0.5","glob@^7.0.5","glob@^7.1.0",
"glob@^7.0.4","glob@^7.1.2","glob@^7.0.3","glob@^7.0.3","glob@^7.0.0","glob@7.1.2","glob@^7.0.3"]. This could result in
non-deterministic behavior, skipping.
error Couldn't find match for "8dc6b689903c9363ade94e2e4d21c94813bbbe98" in "refs/heads/master,refs/heads/node-4-compat,
refs/tags/v0.0.1,refs/tags/v0.0.2,refs/tags/v0.0.3,refs/tags/v0.1.0,refs/tags/v0.1.1" for "https://github.com/kevinansfi
eld/eslint-plugin-sort-imports-es6-autofix.git".
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
Warning: Failed installing node modules in "core/client". Use --force to continue.

Aborted due to warnings.

Solution

find -name "yarn.lock" | xargs rm -f 
then grunt init again
grunt init 

Transfer your ghost server between hosts

Assume you have a ghost server ran at a host machine.
Oneday the host machine maybe out of space or you find a new cheap host,
or you have a new host that have power memory and performance.
Then you may need to transfer your ghost server to another new host.

tar your ghost server to a *.tar.gz

//blog is my ghost server root directory
$cd www/blog
$tar -zcvf blog.tar.gz blog
copy the blog.tar.gz to the new host

preinstall on new host

nginx
sudo apt-get install nginx
node.js
https://nodejs.org/en/download/
extract and add node bin path to env
ghost
npm i -g ghost-cli 
acme.sh
https://github.com/Neilpang/acme.sh

configure on new host

tar cvf blog.tar.gz 

nginx conf

sudo ln -sf blog/system/files/blog.conf /etc/nginx/sites-available/blog.conf
sudo ln -sf /etc/nginx/sites-available/blog.conf /etc/nginx/sites-enabled/blog.conf

start http server

ghost start 
if you encounter systemd error, just follow the hint command(ghost linuxuser systemd) by ghost
and run ghost start again.
Before you start your ghost server, you should change the DNS record for your server. Since you changed host, the IP would changed.

enable https server via letsencrypt

sudo ln -sf blog/system/files/blog-ssl.conf /etc/nginx/sites-available/blog-ssl.conf
sudo ln -sf /etc/nginx/sites-available/blog-ssl.conf /etc/nginx/sites-enabled/blog-ssl.conf
acme.sh --issue --home /etc/letsencrypt --domain errong.win --webroot /home/errong_leng/www/blog/system/nginx-root --reloadcmd "nginx -s reload" --accountemail errong.leng@gmail.com

http nginx conf

server {
    listen 80;
    listen [::]:80;

    server_name errong.win;
    root /home/errong_leng/www/blog/system/nginx-root;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:6666;
        
    }

    location ~ /.well-known {
        allow all;
    }

    client_max_body_size 50m;
}

https nginx conf

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name errong.win;
    root /home/errong_leng/www/blog/system/nginx-root;

    ssl_certificate /etc/letsencrypt/errong.win/fullchain.cer;
    ssl_certificate_key /etc/letsencrypt/errong.win/errong.win.key;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:6666;
        
    }

    location ~ /.well-known {
        allow all;
    }

    client_max_body_size 50m;
}

setup shadowsocks server on ubuntu 16.04

install

sudo apt-get install software-properties-common -y
sudo add-apt-repository ppa:max-c-lv/shadowsocks-libev -y
sudo apt-get update
sudo apt install shadowsocks-libev

configure

sudo vim /etc/shadowsocks-libev/config.json
{
    "server":"10.128.0.2",
    "server_port":8911,
    "local_port":1008,
    "password":"jesusislove",
    "timeout":60,
    "method":"chacha20-ietf-poly1305"
}

start server

sudo systemctl start shadowsocks-libev

google compute engine

If the server are running on a google compute engine,
You need to setup a firewall rules for the server port(8911, written in config.json).
shadowsocks
Apply to all
IP ranges: 0.0.0.0/0
tcp:8911, udp:8911
Allow

client configuration

iOS
Android
Shadowsocks for Android / iOS also accepts BASE64 encoded URI format configs:
ss://BASE64-ENCODED-STRING-WITHOUT-PADDING#TAG 
Where the plain URI should be:
ss://method:password@hostname:port 
Quick Guide

他的力量若不够

利未记五7、11

7「他的力量若不够献一只羊羔,就要因所犯的罪,把两只斑鸠或是两只雏鸽带到耶和华面前为赎愆祭:一只作赎罪祭,一只作燔祭。

11「他的力量若不够献两只斑鸠或是两只雏鸽,就要因所犯的罪带供物来,就是细面伊法十分之一为赎罪祭;不可加上油,也不可加上乳香,因为是赎罪祭。


Photo by Brooke Lark / Unsplash

expressjs : implement http file download

Express helper, res.download

Express has a helper for this:
app.get('/xxx', function(req, res){ 
  var file = __dirname + '/xxx'; 
  res.download(file); // Set disposition and send it. 
}); 

http file download header

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
res.setHeader('Content-disposition', 'attachment; filename=xxx'); 

iptables, clean all rules

command

sudo iptables -P INPUT ACCEPT
sudo iptables -F

list all rules

sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy DROP)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination

node.js : file upload via express and multer

Form-based File Upload in HTML

https://tools.ietf.org/html/rfc1867

Express

Fast, unopinionated, minimalist web framework for node.

Multer

Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It is written on top of busboy for maximum efficiency.
NOTE: Multer will not process any form which is not multipart (multipart/form-data).

Client Codes(index.html)

<script type="text/javascript">
function upload(postUrl, fieldName, files)
{
  var formData = new FormData();
  formData.append(fieldName, files);

  var req = new XMLHttpRequest();
  req.open("POST", postUrl);
  req.onload = function(event) { console.log(event.target.responseText); };
  req.send(formData);
}
function onchange() {
  for (let i = 0; i < this.files.length; i++) {
    upload('/uploads', 'uploadfile', this.files[i]);
  }
}
window.onload = function () {
var  input = document.getElementById('file');
input.addEventListener('change', onchange);
}
</script>
<input type="file" id="file" name="file" multiple onchange="upload" />

Server Codes

var express = require('express')
var app = express()
var path = require('path')
var tmpdir = require('os').tmpdir
var upload = require('multer')({dest: tmpdir()})

app.get('/', function (req, res) {
  var indexhtml = 'index.html';
  var tmp = path.resolve(indexhtml);
  res.sendFile(tmp);
})

app.post('/uploads', upload.single('uploadfile'), function (req, res, next) {
  var rp = {'file':req.file, 'body' : req.body, 'ip':req.ip};
  res.send(rp);
})

app.listen(3000)

Run express Server

node index.js

Test

upload
As you can see in the console, the file was uploaded to server and saved with file name of "/tmp/e2f2d3ae260814fc4b589a13d5981aec".

Print all combination of that select n elements from 1,2,3,...,m.

"C" is for "combination". A combination is an un-ordered collection of distinct elements, usually of a prescribed size and taken from a given set.

   C(n, r) = n!/[r!(n-r)!] 
 
#include <stdio.h>
#include <sys/time.h>

int a[1001];

// selected n numbers stored in array a
// a[1], a[2], ... , a[n]
// a[i+1] > a[i]
// a[i] - i <= m - n + 1

void comos(int n, int m) {
  int i,j;
  if (n > m)
    return;
  for (i = 1; i <= n; i++) {
    a[i] = i;
  }
  int cur = n;
  do {
    if (a[cur]-cur <= m - n) {
      for (i = 1; i <= n; i++)
        printf("%d ", a[i]);
      printf("\n");
      a[cur]++;
      continue;
    } else {
      if (cur == 1) {
        break;
      }
      a[--cur]++;
      for (i = 1; i <= (n-cur); i++)
        a[cur+i] = a[cur] + i;
      if (a[cur] - cur < m - n + 1)
        cur=n;
    }
  } while(1);
}

int main() {
  int N, M;
  while (scanf("%d %d", &N, &M) != EOF) {
    if (N <= M) {
      struct timeval begin, end;
      gettimeofday(&begin, 0);
      comos(N, M);
      gettimeofday(&end, 0);
      printf("print comos(%d, %d) used %ld microseconds\n", N, M, end.tv_usec-begin.tv_usec);
    } else {
      printf("Invalid Input, %d <= %d is false\n", N, M);
    }
  }
  return 0;
}
 
1 5
1
2
3
4
5
print comos(1, 5) used 116 microseconds
2 5
1 2
1 3
1 4
1 5
2 3
2 4
2 5
3 4
3 5
4 5
print comos(2, 5) used 171 microseconds
3 5
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
print comos(3, 5) used 153 microseconds
4 5
1 2 3 4
1 2 3 5
1 2 4 5
1 3 4 5
2 3 4 5
print comos(4, 5) used 97 microseconds
5 5
1 2 3 4 5
print comos(5, 5) used 33 microseconds

all learning is understanding relationships

James Comer says that no significant learning can occur without a significant relationship.
George Washington Carver says all learning is understanding relationships.

"You were chosen to be in my class because I am the best teacher and you are the best students, they put us all together so we could show everybody else how to do it."

"Really?"

"Really. We have to show the other classes how to do it, so when we walk down the hall, people will notice us, so you can't make noise. You just have to strut."

"I am somebody. I was somebody when I came. I'll be a better somebody when I leave. I am powerful, and I am strong. I deserve the education that I get here. I have things to do, people to impress, and places to go."

"Yeah!"

You say it long enough, it starts to be a part of you.

fixed: embedded-redis: Unable to run on macOS Sonoma

Issue you might see below error while trying to run embedded-redis for your testing on your macOS after you upgrade to Sonoma. java.la...