Compare commits

...

No commits in common. "main" and "master" have entirely different histories.
main ... master

15 changed files with 1171 additions and 143 deletions

132
.gitignore vendored
View File

@ -1,132 +0,0 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

View File

@ -1,9 +0,0 @@
MIT License
Copyright (c) 2024 x0x7
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.

View File

@ -1,2 +0,0 @@
# todo4

27
Readme.md Normal file
View File

@ -0,0 +1,27 @@
# Todo4: Condorset Task Management
## Synopsis
Todo4 is a personal task management software that runs in the terminal via a custom NodeJS repl. It utilizes [Condorset Ranking](https://en.wikipedia.org/wiki/Condorcet_method) to track the priority and timeliness of tasks and order dependencies. It can whittle the largests lists into a selection of the most imporant tasks so long as you use good judgement when ranking tasks.
## File structure
todo.js contains the core logic of managing tasks and their relative ranking. data.js and data2.js manages state persistence. nice.js is a wrapper to todo.js that provides easy to use functions for the node REPL environment including many shorthands. easystart.js takes the functions from nice.js and places them into a REPL with its own context and makes it easy to launch specific lists as "users".
## The most important included functions
These are the functions I use most from nice.js
- pull() aka p(). Pull some sample tasks off the top of the stack. Pulling tasks list them and provides the task id and assigns a temporary short id.
- rank() rank with full ids.
- r() rank with short ids.
- pullmirror() aka pm(). gives you a sample of past tasks to see if you want to do them again or re-add them to the main stack
- add(). Opens up a prompt to add tasks to the top of the stack
- addfromfile(). Let's you bulk add from a file
- remove(). Removes a task from the stack.
- done(). Removes and marks done using a full id.
- d(). Mark done the task you ranked top in your last ranking or uses a short id.
- search(). Find tasks matching each pattern you provide.
- pullexposed(). Pull but only consider tasks that have ranking data
- edit(). Edit the text of a task
- lookback(). Look at what tasks you have gotten done either that day or some number of days back.

92
data.js Normal file
View File

@ -0,0 +1,92 @@
var exports = module.exports;
//var {Database} = require('sqlite3');
//var sqldb = new Database(_data+'/data.db');
var {Client} = require('cassandra-driver');
const cass = new Client({
cloud: {secureConnectBundle:'secure-connect-todo.zip'},
credentials: {
username:'',
password:''
},
});
async function startcass() {
await cass.connect();
cass.execute('use todo');
}
startcass();
(()=>{
var rankCache = new Map();
var rankReadTimeouts = new Map();
var rankWriteTimeouts = new Map();
var rankReadBounce = 1000*60*5;
var rankWriteBounce = 1000*15;
function rawgetrank(user,cb) {
cass.execute('Select json from rank where user=?',[user]).then(result=>{
if(!result || !result.rows || !result.rows.length) return cb(null,{});
cb(null,JSON.parse(result.rows[0].json));
}).catch(cb);
}
function updaterankreadtimeout(user) {
if(rankReadTimeouts.has(user)) clearTimeout(rankReadTimeouts.get(user));
rankReadTimeouts.set(user,setTimeout(()=>rankCache.delete(user),rankReadBounce));
}
function getrank(user,cb) {
updaterankreadtimeout(user);
if(rankCache.has(user)) return cb(null,rankCache.get(user));
rawgetrank(user,(err,obj)=>{
if(err) return cb(err);
rankCache.set(user,obj);
return cb(null,obj);
});
}
function rawwriterank(user,obj) {
cass.execute('Insert into rank (user,json) values (?,?)',[user,JSON.stringify(obj)]);
}
function writerank(user,obj) {
if(rankWriteTimeouts.has(user)) clearTimeout(rankWriteTimeouts.get(user));
rankWriteTimeouts.set(user,setTimeout(()=>rawwriterank(user,obj),rankWriteBounce));
}
exports.getrank=getrank;
exports.writerank=writerank;
})();
(()=>{
function rawgettitle(user,id,cb) {
cass.execute('Select title from titles where user=? and id=?',[user,id]).then(result=>{
if(!result || !result.rows || !result.rows.length) return cb('title not found');
cb(null,result.rows[0]);
}).catch(cb);
}
function rawwritetitle(user,id,title) {
cass.execute('Insert into titles (user,id,title) values (?,?,?)',[user.id,title]);
}
exports.gettitle=rawgettitle;
exports.writetitle=rawwritetitle;
})();
(()=>{
function rawgetday(user,day,cb) {
cass.execute('Select json from day where user=? and day=?',[user,day]).then(result=>{
if(!result || !result.rows || !result.rows.length) return cb(null,[]);
cb(null,JSON.parse(result.rows[0].json));
}).catch(cb);
}
function rawwriteday(user,day,list) {
cass.execute('Insert into day (user,day,json) values (?,?,?)',[user,day,JSON.stringify(list)]);
}
exports.getday=rawgetday;
exports.writeday=rawwriteday;
})();
(()=>{
function rawaddlog(user,title,id) {
cass.execute('Insert into addjournal (user,time,title,id) values (?,?,?,?)',[user,Date.now(),title,id]);
}
exports.addlogwrite=rawaddlog;
})();

93
data2.js Normal file
View File

@ -0,0 +1,93 @@
var readwritecache2 = require('../readwritecache2');
var fs=require('fs');
var _data = __dirname+'/data';
var sqlitedb = require('./sqlitedb');
var sqldb = sqlitedb;
//var {Client} = require('cassandra-driver');
//const cass = new Client({
// cloud: {secureConnectBundle:'secure-connect-todo.zip'},
// credentials: {
// username:'jvonmitchell@gmail.com',
// password:'6e9hDX8$$'
// },
//});
//
//async function startcass() {
// await cass.connect();
// cass.execute('use todo');
//}
//startcass();
module.exports.ranks=readwritecache2({
default:{},
writefreq:1000*30,
readfreq:1000*60*5,
delayclearonread:true,
delaywriteonwrite:true,
get:(key,cb)=>{
console.log('Reading ranks for',key);
sqlitedb.get("SELECT json FROM rank WHERE user=?",key,(err,json)=>{
if(err) return cb(err);
if(!json) return cb();
try {
cb(null,JSON.parse(json.json));
}
catch(e) {
cb(e);
}
});
},
write:(key,obj,notindb,cb)=>{
//console.log('Writing ranks for',key);
if(notindb) return sqlitedb.run('INSERT INTO rank (user,json) VALUES (?,?)',key,JSON.stringify(obj),cb);
sqlitedb.run('UPDATE rank SET json=? WHERE user=?',JSON.stringify(obj),key,cb);
}
});
module.exports.journals=readwritecache2({
default:{},
get:(key,cb)=>{
var user = key.split('.')[0];
var date=key.split('.day.')[1].replace('.json','');
//var date=key.toString();
sqldb.get('Select json from day where user=? and day=?',user,date,(err,result)=>{
if(err) return cb(err);
if(result) return cb(null,JSON.parse(result.json));
//cass.execute('Select json from day where user=? and day=?',[user,date]).then(result=>{
//if(result && result.rows && result.rows.length) {
// //console.log({result});
// return cb(null,JSON.parse(result.rows[0].json));
//}
fs.readFile(_data+'/'+key+'.json',(err,buff)=>{
if(err) return cb();
try {
cb(null,JSON.parse(buff));
}
catch(e) {
console.log({key,buff:buff.toString(),err:e,cb});
cb(e);
}
});
//});
});
},
write:(key,obj,notindb,cb)=>{
var user = key.split('.')[0];
var day = key.split('.day.')[1];
var stringified=JSON.stringify(obj);
//cass.execute('Insert into day (user,day,json) values (?,?,?)',[user,day,stringified]);
fs.writeFile(_data+'/'+key+'.json',stringified,cb);
if(notindb) {
sqldb.run('INSERT into day (user,day,json) values (?,?,?)',user,day,stringified);
}
else {
sqldb.run('UPDATE day SET json=? WHERE user=? AND day=?',stringified,user,day);
}
},
writefreq:1000*30,
readfreq:1000*60*5,
delayclearonread:true,
delaywriteonwrite:true
});

9
easystart.js Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env node
var repl = require('repl');
var user = process.argv[2]||'me';
var todo=require('./nice')(user);
var context=repl.start(user+'> ').context;
context.todo=todo;
Object.keys(todo).forEach(k=>context[k]=todo[k]);

330
nice.js Normal file
View File

@ -0,0 +1,330 @@
module.exports=exports;
var todo=require('./todo');
var {pullexposedtasks,randomtasks,edittitle,searchtasks,pullmirrorsuper,pullmirrortask,getjournal,decayranks,completetask,pulltasks,addtask,removetask,ranktasks,removetasks,addmany}=todo;
Object.keys(todo).forEach(k=>exports[k]=todo[k]);
var prompt=require('prompt-sync')();
var fs=require('fs');
var async=require('async');
function noop() {}
function exports(user) {
var retr={};
retr.exports=exports;
retr.random=random;
retr.rand=random;
function random(count=5) {
randomtasks(user,count,(err,res)=>{
console.log();
res.forEach(i=>console.log(i.id,i.title));
});
}
retr.add=add;
function add() {
if(arguments.length==0) {
var list=[];
function ask() {
var line=prompt(':');
if(line) {
list.push(line);
ask();
}
else { //Add list
addmany(user,list,console.log);
}
//if(line) addtask(user,line,ask);
}
return ask();
}
Array.prototype.forEach.bind(arguments)(item=>{
if(typeof(item)==='string') addtask(user,item,noop);
else { //Asuming list
item.forEach(item=>addtask(user,item,noop));
}
});
}
retr.remove=remove;
function remove() {
Array.prototype.forEach.bind(arguments)(item=>{
if(typeof(item)==='number') {
if(item<=4) {
var verify = prompt('Verify: Y/n');
if(verify[0]=='n'||verify[0]=='N') return;
}
removetask(user,item,noop);
}
else { //Asuming list
item.forEach(item=>removetask(user,item,noop));
}
});
}
function printtasks(tasks,stream) {
pastpull=tasks;
try {
tasks.sort(()=>Math.random()-0.5);
stream.write('\n');
//console.log({tasks});
tasks.forEach((t,i)=>{
//console.log(t.id+':',i,t.title);
stream.write(t.id+': '+i+' '+t.title+'\n');
});
}
catch(e) {
console.log({user,tasks});
console.log(e);
}
}
retr.pullexposed = pullexposed;
function pullexposed(count=4,stream=process.stdout) {
pullexposedtasks(user,count,(err,tasks)=>{
if(err) return console.log(err,tasks);
printtasks(tasks,stream);
});
}
var pastpull=[];
retr.pull=pull;
retr.p = pull;
function pull(count=4,stream=process.stdout) {
pulltasks(user,count,(err,tasks)=>{
if(err) return console.log(err,tasks);
printtasks(tasks,stream);
});
}
retr.r=r;
var pulllog=fs.createWriteStream(__dirname+'/data/logs/pull.log',{flags:'a'});
//function r() {
// if(arguments.length===1 && typeof(arguments[0])==='string') {
// return retr.done(arguments[0]);
// }
// var expanded=[];
// Array.prototype.forEach.bind(arguments)(item=>{
// if(typeof(item)==='number') expanded.push(item);
// else { //Asuming list
// expanded=expanded.concat(item);
// }
// });
// var toptitle = pastpull[expanded[0]].title;
// expanded=expanded.map(i=>pastpull[i].id);
// //console.log(expanded)
// ranktasks(user,expanded,noop);
// pulllog.write(toptitle+'\n');
// pastrank=expanded;
//}
function r() {
if (arguments.length === 1 && typeof(arguments[0]) === 'string') {
return retr.done(arguments[0]);
}
var expanded = [];
Array.prototype.forEach.call(arguments, item => {
if (typeof(item) === 'number') {
expanded.push([item]); // Wrap single numbers in an array
} else if (Array.isArray(item)) {
expanded.push(item); // Use array as is
}
});
var toptitle = pastpull[expanded[0][0]].title; // Use the first element of the first array to get the title
expanded = expanded.map(group => group.map(i => pastpull[i].id)); // Replace temp ids with longhand ids
ranktasks(user, expanded, noop);
pulllog.write(toptitle + '\n');
pastrank = expanded.flat();
}
retr.addfromfile=addfromfile;
function addfromfile(path='insert') {
fs.readFile(path,(err,buff)=>{
if(err) return console.log(err);
addmany(user,buff.toString().split('\n').filter(i=>i.length),console.log);
});
}
retr.done=done;
function done(taskid) {
if(taskid<=4) {
var verify = prompt('Verify: Y/n');
if(verify[0]=='n'||verify[0]=='N') return;
}
completetask(user,taskid,err=>{
if(err) return console.log(err);
decayranks(user,1-5/300,noop);
//if(typeof(taskid)=='number') setTimeout(pm,100);
});
}
retr.d=d;
function d() {
done(pastrank[0]);
}
var pastrank=[];
retr.rank=rank;
//function rank() {
// var expanded=[];
// Array.prototype.forEach.bind(arguments)(item=>{
// if(typeof(item)==='number') expanded.push(item);
// else { //Asuming list
// expanded=expanded.concat(item);
// }
// });
// pastrank=expanded;
// ranktasks(user,expanded,noop);
//}
function rank() {
var expanded = [];
if (arguments.length === 1 && Array.isArray(arguments[0])) {
expanded = arguments[0]; // Treat single array argument as the complete taskGroups
} else {
// Treat each argument as an element to be passed to ranktasks
Array.prototype.forEach.call(arguments, item => {
if (typeof(item) === 'number') {
expanded.push([item]); // Wrap single numbers in an array
} else {
expanded.push(item); // Already an array, use as is
}
});
}
pastrank = expanded.flat();
ranktasks(user, expanded, noop);
}
retr.pullmirror=pullmirror;
retr.pm=pullmirror;
retr.pullmirrormonth=pullmirrormonth;
retr.pullmirrorweek=pullmirrorweek;
retr.pullmirrorday=pullmirrorday;
function pullmirror() {
//async.parallel([pullmirrormonth,pullmirrorweek,pullmirrorday],(err,res)=>{
async.map([doublepullmirrormonth,pullmirrormonth,doublepullmirrorweek,pullmirrorweek,doublepullmirrorday,pullmirrorday],(func,cb)=>{
func((err,res)=>{
if(err == 'Journal missing - skiplimit reached') return cb();
cb(err,res);
});
},(err,res)=>{
if(err) return console.log(err);
res=res.filter(r=>r);
res.forEach(item=>{
if(!item) return console.log('missing');
console.log((new Date(item.time)).toLocaleString(),item.title,item.id||'');
});
});
}
function pullmirrorday(cb) {
pullmirrortask(user,cb);
}
function doublepullmirrorday(cb) {
getjournal(user,0,(err,journal)=>{
if(err) return console.log(err);
if(!journal.list) return cb();
pullmirrorsuper(user,1,journal.list.length*2,5,cb);
});
}
retr.lookback=lookback;
function lookback(days) {
getjournal(user,days,(err,journal)=>{
if(err) return console.log(err);
if(!journal.list) return console.log('Journal missing');
journal.list.forEach(item=>{
console.log((new Date(item.time)).toLocaleString(),item.title,item.id||'');
});
});
}
retr.pullmirrorweek=pullmirrorweek;
function pullmirrorweek(cb) {
var day=(new Date()).getDay()||7;
var pending=day;
var lookcount=0;
for(var i=0;i<day;++i) {
getjournal(user,i,(err,journal)=>{
--pending;
if(journal && journal.list) lookcount+=journal.list.length;
if(!pending) {
pullmirrorsuper(user,day,lookcount,5,cb);
}
});
}
}
retr.doublepullmirrorweek=doublepullmirrorweek;
function doublepullmirrorweek(cb) {
var day=(new Date()).getDay()||7;
var pending=day;
var lookcount=0;
for(var i=0;i<day;++i) {
getjournal(user,i,(err,journal)=>{
--pending;
if(journal && journal.list) lookcount+=journal.list.length;
if(!pending) {
pullmirrorsuper(user,day,lookcount*2,5,cb);
}
});
}
}
retr.pullmirrormonth=pullmirrormonth;
function pullmirrormonth(cb) {
var day=(new Date()).getDate()||30;
var pending=day;
var lookcount=0;
for(var i=0;i<day;++i) {
getjournal(user,i,(err,journal)=>{
--pending;
if(journal && journal.list) lookcount+=journal.list.length;
if(!pending) {
pullmirrorsuper(user,day,lookcount,15,cb);
}
});
}
}
retr.doublepullmirrormonth=doublepullmirrormonth;
function doublepullmirrormonth(cb) {
var day=(new Date()).getDate()||30;
var pending=day;
var lookcount=0;
for(var i=0;i<day;++i) {
getjournal(user,i,(err,journal)=>{
--pending;
if(journal && journal.list) lookcount+=journal.list.length;
if(!pending) {
pullmirrorsuper(user,day,lookcount*2,15,cb);
}
});
}
}
function lookup(ref) {
if(!ref) return;
if(typeof(ref)=='number') return lookupid(ref);
else return lookupname(ref);
}
retr.search=search;
function search(titlefrag) {
var args=[];
for(var i=0;arguments[i];++i) {
args=args.concat(arguments[i]);
}
searchtasks(user,args,console.log);
}
retr.searchres=searchres;
function searchres(titlefrag) {
var args=[];
for(var i=0;arguments[i];++i) {
args=args.concat(arguments[i]);
}
return new Promise((resolve,reject)=>{
searchtasks(user,args,(err,results)=>{
if(err) return reject(err);
resolve(results);
});
});
}
retr.edit = edit;
function edit(id,title) {
edittitle(user,id,title);
}
/*
http.createServer((req,res)=>{
if(req.url=='/pull') {
res.writeHead(200,{'Content-type':'text/plain'});
pulltasks(user,5,(err,tasks)=>{
tasks.forEach((t,i)=>{
//console.log(t.id+':',i,t.title);
res.write(t.id+': '+i+' '+t.title+'\n');
});
res.end();
});
}
}).listen(7000);
*/
return retr;
}

16
package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "todo2",
"version": "1.0.0",
"description": "",
"main": ".eslintrc.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"prompt-sync": "^4.2.0",
"sqlite3": "^5.1.4"
}
}

77
server.js Executable file
View File

@ -0,0 +1,77 @@
#!/usr/bin/env node
var async = require('async');
var UrlResolver = require('urlresolver');
var p = new UrlResolver();
var user = 'me';
exports.p = p;
var todo=require('./todo');
var {pulltasks,addtask,removetask,ranktasks,removetasks}=todo;
Object.keys(todo).forEach(k=>exports[k]=todo[k]);
exports.de=de;
function de(res,err,code) {
res.writeHead(code||400,{"Content-Type":'text/plain'});
res.end(err.toString());
}
exports.simpcb=simpcb;
function simpcb(res) {
return (err,data)=>{
if(err) return de(res,err);
if(data) return res.end(data.toString());
res.end('success');
};
}
p.add('/pull',(req,res)=>{
pulltasks(user,5,(err,tasks)=>{
if(err) return cb(err);
res.writeHead(200,{"Content-Type":'text/plain'});
res.end(tasks.map((i,ind)=>`${i.id}: ${ind} ${i.title}`).join('\n'));
});
});
p.add('/api/pull',(req,res)=>{
//Replace with req.user
pulltasks(user,5,(err,tasks)=>{
if(err) return cb(err);
res.writeHead(200,{"Content-Type":'application/json'});
res.end(JSON.stringify(tasks));
});
});
p.add('/add/task/',(req,res)=>{
addtask(req.user,req.post.string,simpcb(res));
});
p.add('/remove/task',(req,res)=>{
var taskid=parseInt(req.post.string);
if(isNaN(taskid)) return de(res,"Not a number");
removetask(req.user,taskid,simpcb(res));
});
p.add('/rank/tasks',(req,res)=>{
var tasks=req.post.string.split(',').map(i=>parseInt(i)).filter(i=>!isNaN(i));
ranktasks(req.user,tasks,simpcb(res));
});
p.add('/rank/and/remove/tasks',(req,res)=>{
var [rank,remove]=req.post.string.split('|').map(list=>list.split(',').map(i=>parseInt(i)).filter(i=>!isNaN(i)));
async.parallel([
cb=>ranktasks(req.user,rank,cb),
cb=>removetasks(req.user,remove,cb)
],simpcb(res));
});
function exports(req,res) {
p.get(req.url)(req,res);
}
require('http').createServer(exports).listen(8092);

34
setupdb.js Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env node
/*
sqlitedb.get("SELECT json FROM rank WHERE user=?",key,(err,json)=>{
if(notindb) return sqlitedb.run('INSERT INTO rank (user,json) VALUES (?,?)',key,JSON.stringify(obj),cb);
sqlitedb.run('UPDATE rank SET json=? WHERE user=?',JSON.stringify(obj),key,cb);
*/
var db = require('./sqlitedb');
db.run('CREATE TABLE rank (user text primary key,json text)',console.log);
/*
todo.js: async.map(ids,(id,cb)=>sqldb.get('Select id,title from titles where user=? and id=?',user,id,cb),cb);
todo.js: sqldb.run('Insert into titles values (?,?,?)',user,i,tasktext,noop);
todo.js: sqldb.run('Insert into titles values (?,?,?)',user,i,tasktext,noop);
todo.js: sqldb.get('Select title from titles where user=? and id=?',user,taskid,(err,result)=>{
todo.js: sqldb.run('Delete from titles where user=? and id=?',user,taskid,err=>{
todo.js: sqldb.get('Select title from titles where user=? and id=?',user,taskid,(err,titleobj)=>{
todo.js: sqldb.all(`Select id,title from titles where user=? and ${titlefrags}`,user,cb);
todo.js: sqldb.run('Update titles Set title=? Where user=? and id=?',title,user,id,cb);
todo.js: async.map(selectedids,(id,cb)=>sqldb.get('Select id,title from titles where user=? and id=?',user,id,cb),cb);
*/
db.run('CREATE TABLE titles (user text,id integer,title text)');
db.run('CREATE INDEX titles_idx ON titles(user,id)');
/*
data2.js: sqldb.get('Select json from day where user=? and day=?',user,date,(err,result)=>{
data2.js: sqldb.run('INSERT into day (user,day,json) values (?,?,?)',user,day,stringified);
data2.js: sqldb.run('UPDATE day SET json=? WHERE user=? AND day=?',stringified,user,day);
*/
CREATE TABLE day (user text not null,day text not null,json text not null);
CREATE UNIQUE INDEX day_idx ON day(user,day);

4
sqlitedb.js Normal file
View File

@ -0,0 +1,4 @@
var sqlite3 = require('sqlite3');
module.exports = new sqlite3.Database(__dirname+'/data/data.db');

8
test.data.js Normal file
View File

@ -0,0 +1,8 @@
function test1() {
var a = require('./data');
setTimeout(()=>{
a.getrank('test',console.log);
},1000);
}
test1();

6
test.js Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env node
var a = require('./server');
a.pulltasks('test',5,console.log);

475
todo.js Normal file
View File

@ -0,0 +1,475 @@
var exports=module.exports;
var _data = __dirname+'/data';
exports._data=_data;
var fs=require('fs');
var {shuffle} = require('lodash');
var async = require('async');
var df = require('../df');
var {ranks,journals} = require('./data2');
df((err,kb)=>{
if(err) return console.log('df err',err);
if(kb<1000) process.exit(1);
});
var db = require('../readwritecache')(dbwrite,dbwrite,dbread,{},1000,1);
//var {Database} = require('sqlite3');
var sqldb = require('./sqlitedb');
//var {Client} = require('cassandra-driver');
//const cass = new Client({
// cloud: {secureConnectBundle:'secure-connect-todo.zip'},
// credentials: {
// username:'jvonmitchell@gmail.com',
// password:'6e9hDX8$$'
// },
//});
//async function startcass() {
// await cass.connect();
// cass.execute('use todo');
//}
//startcass();
exports.db=db;
exports.dbwrite=dbwrite;
function dbwrite(key,obj,cb) {
//console.log("Writing",key);
var stringified=JSON.stringify(obj);
var user = key.split('.')[0];
if(key.endsWith('.rank')) {
sqldb.run('Update rank set json=? where user=?',stringified,user,noop);
cass.execute('Insert into rank (user,json) values (?,?)',[user,stringified]);
}
if(key.indexOf('.day.')!=-1) {
var day = key.split('.day.')[1];
cass.execute('Insert into day (user,day,json) values (?,?,?)',[user,day,stringified]);
}
fs.writeFile(_data+'/'+key+'.json',stringified,cb);
}
var rankCache = new Map();
var rankCacheTimeouts = new Map();
exports.dbread=dbread;
function dbread(key,cb) {
var user = key.split('.')[0];
if(key.endsWith('.rank')) {
if(rankCache.has(user)) {
clearTimeout(rankCacheTimeouts.get(user));
rankCacheTimeouts.set(user,setTimeout(()=>rankCache.delete(user)));
return cb(null,rankCache.get(user));
}
return sqldb.get('Select json from rank where user=?',user,(err,json)=>{
if(err) return cb(err);
//if(!json) return cb('User not found in rank');
if(!json) return cb();
json=JSON.parse(json.json);
rankCache.set(user,json);
rankCacheTimeouts.set(user,setTimeout(()=>rankCache.delete(user)));
cb(null,json);
});
}
if(key.indexOf('.day.')!=-1) {
//var indexOf = key.indexOf('.day.');
//console.log({key,indexOf});
var date=key.split('.day.')[1].replace('.json','');
return cass.execute('Select json from day where user=? and day=?',[user,date]).then(result=>{
if(result && result.rows && result.rows.length) {
//console.log({result});
return cb(null,JSON.parse(result.rows[0].json));
}
fs.readFile(_data+'/'+key+'.json',(err,buff)=>{
if(err) return cb();
try {
cb(null,JSON.parse(buff));
}
catch(e) {
console.log({key,buff:buff.toString(),err:e,cb});
cb(e);
}
});
});
}
fs.readFile(_data+'/'+key+'.json',(err,buff)=>{
if(err) return cb();
try {
cb(null,JSON.parse(buff));
}
catch(e) {
console.log({key,buff:buff.toString(),err:e,cb});
cb(e);
}
});
}
exports.minindexes=minindexes;
function minindexes(list) {
if(!list.length) return [];
var minvalue=list.reduce((a,b)=>a<b?a:b);
var retr=[];
for(var i=0;i<list.length;++i) {
if(list[i]==minvalue) retr.push(i);
}
//console.log({minvalue,idexes:retr});
return retr;
}
exports.removetasks=removetasks;
function removetasks(user,tasks,cb) {
async.map(tasks,(task,cb)=>removetask(user,cb),cb);
}
function noop () {}
exports.addtask=addtask;
function addtask(user,tasktext,cb) {
//var rankkey=user+'.rank';
var addhistkey=user+'.addhist';
//async.map([rankkey,addhistkey],db.get,(err,objs)=>{
async.parallel([
cb=>ranks.get(user,cb),
cb=>db.get(addhistkey,cb)
],(err,objs)=>{
if(err) return cb(err);
var [rankobj,addhistobj]=objs;
var i=0;
while(rankobj[i]) ++i;
sqldb.run('Insert into titles values (?,?,?)',user,i,tasktext,noop);
rankobj[i]={};
if(!addhistobj.hist) addhistobj.hist=[];
addhistobj.hist.push(tasktext);
//console.log({rankkey,rankobj,i,tasktext});
//db.markWrite(rankkey);
ranks.markWrite(user);
db.markWrite(addhistkey);
cb(null,i);
});
}
exports.addmany=addmany;
function addmany(user,tasktexts,cb) {
//var rankkey=user+'.rank';
//db.get(rankkey,(err,rankobj)=>{
ranks.get(user,(err,rankobj)=>{
if(err) return cb(err);
var retr=[];
var i=0;
tasktexts.forEach(tasktext=>{
if(!tasktext) return;
while(rankobj[i]) ++i;
retr.push(i);
//sqldb.run('BEGIN TRANSACTION',noop);
sqldb.run('Insert into titles values (?,?,?)',user,i,tasktext,noop);
//sqldb.run('COMMIT',noop);
rankobj[i]={};
});
//db.markWrite(rankkey);
ranks.markWrite(user);
cb(null,retr);
});
}
exports.removetask=removetask;
function removetask(user,taskid,cb) {
//var rankkey=user+'.rank';
//db.get(rankkey,(err,rankobj)=>{
ranks.get(user,(err,rankobj)=>{
if(err) return cb(err);
sqldb.get('Select title from titles where user=? and id=?',user,taskid,(err,result)=>{
if(err) return cb(err);
if(result) console.log('Removed:',result.title);
sqldb.run('Delete from titles where user=? and id=?',user,taskid,err=>{
if(err) return cb(err);
console.log('removing',taskid);
delete rankobj[taskid];
Object.values(rankobj).forEach(peerobj=>{
delete peerobj[taskid];
});
//db.markWrite(rankkey);
ranks.markWrite(user);
cb();
});
});
});
}
exports.completetask=completetask;
function completetask(user,taskid,cb) {
var now=Date.now();
var journalkey=user+'.day.'+(new Date()).toLocaleDateString().replace(/\//g,'.');
//db.get(journalkey,(err,journalobj)=>{
journals.get(journalkey,(err,journalobj)=>{
if(err) return cb(err);
var title;
journalobj.list=journalobj.list||[];
if(typeof(taskid)==='string') {
title=taskid;
journalobj.list.push({time:now,title});
journals.markWrite(journalkey);
//db.markWrite(journalkey);
}
else {
sqldb.get('Select title from titles where user=? and id=?',user,taskid,(err,titleobj)=>{
if(err) return cb(err);
title=titleobj.title;
journalobj.list.push({time:now,title,id:taskid});
removetask(user,taskid,cb);
journals.markWrite(journalkey);
//db.markWrite(journalkey);
});
}
});
}
exports.undercount=undercount;
function undercount(id,obj,blockset) {
if(blockset.has(id)) return 1000;
var count=0;
var keys=Object.keys(obj);
if(!keys.length) return 0;
for(var i=0;i<keys.length;++i) {
if(blockset.has(keys[i])) continue;
if(obj[keys[i]]<0) count-=obj[keys[i]];
}
//console.log({id,count});
return count;
}
exports.decayranks=decayranks;
const DECAY=5/300;
function decayranks(user,decay,cb) {
//var key=user+'.rank';
decay=decay||DECAY;
//db.get(key,(err,obj)=>{
ranks.get(user,(err,obj)=>{
if(err) return cb(err);
Object.values(obj).forEach(subobj=>{
Object.keys(subobj).forEach(subkey=>{
if(subkey=='block') return;
subobj[subkey]*=decay;
});
});
//db.markWrite(key);
ranks.markWrite(user);
cb();
});
}
exports.getjournal=getjournal;
function getjournal(user,days,cb) {
days=days||0;
days=Math.abs(days);
var journalkey=user+'.day.'+(new Date(Date.now()-1000*60*60*24*days)).toLocaleDateString().replace(/\//g,'.');
//db.get(journalkey,cb);
journals.get(journalkey,cb);
}
//exports.ranktasks=ranktasks;
//function ranktasks(user,tasklist,cb) {
// //var key=user+'.rank';
// //db.get(key,(err,obj)=>{
// ranks.get(user,(err,obj)=>{
// if(err) return cb(err);
// var i;
// for(i=0;i<tasklist.length;++i) {
// for(var j=i+1;j<tasklist.length;++j) {
// obj[tasklist[i]][tasklist[j]]=1;
// obj[tasklist[j]][tasklist[i]]=-1;
// }
// }
// for(i=1;i<tasklist.length;++i) {
// obj[tasklist[i]].block=i; //We don't want to see it for a bit, add a block that will count down
// //console.log("Block",tasklist[i],i);
// }
// //db.markWrite(key);
// ranks.markWrite(user);
// cb();
// });
//}
exports.ranktasks=ranktasks;
function ranktasks(user,taskGroups,cb) {
ranks.get(user,(err,obj)=>{
if(err) return cb(err);
var i;
for(i=0;i<taskGroups.length;++i) {
for(var j=i+1;j<taskGroups.length;++j) {
for(var taskA of [].concat(taskGroups[i])) {
for(var taskB of [].concat(taskGroups[j])) {
obj[taskA][taskB]=1;
obj[taskB][taskA]=-1;
}
}
}
}
for (let i = 1; i < taskGroups.length; ++i) {
[].concat(taskGroups[i]).forEach(task => obj[task].block = i);
}
ranks.markWrite(user);
cb();
});
}
exports.searchtasks=searchtasks;
function searchtasks(user,titlefrags,cb) {
//This is not safe
if(typeof(titlefrags)=='string') titlefrags = [titlefrags];
titlefrags = titlefrags.map(i=>i.toLowerCase());
titlefrags = titlefrags.map(i=>`title like '%${i}%'`);
titlefrags = titlefrags.join(' and ');
sqldb.all(`Select id,title from titles where user=? and ${titlefrags}`,user,cb);
}
exports.edittitle=edittitle;
function edittitle(user,id,title,cb) {
sqldb.run('Update titles Set title=? Where user=? and id=?',title,user,id,cb);
}
exports.pullmirrorsuper=pullmirrorsuper;
function pullmirrorsuper(user,daysback,donecount,skiplimit,cb) {
getjournal(user,daysback,(err,backjournal)=>{
if(err) return cb(err);
if(!backjournal.list) {
if(skiplimit) pullmirrorsuper(user,daysback+1,donecount,skiplimit-1,cb);
else return cb("Journal missing - skiplimit reached");
}
else if(backjournal.list.length<donecount) pullmirrorsuper(user,daysback+1,donecount-=backjournal.list.length,skiplimit,cb);
else cb(null,backjournal.list[backjournal.list.length-donecount]);
});
}
exports.pullmirrortask=pullmirrortask;
function pullmirrortask(user,cb) {
var skiplimit=5;
getjournal(user,0,(err,journal0)=>{
if(err) return cb(err);
if(!journal0.list) return cb("Journal missing");
var donecount=journal0.list.length;
var daysback=1;
function getone() {
getjournal(user,daysback,(err,backjournal)=>{
if(err) return cb(err);
if(!backjournal.list) {
if(skiplimit) {
--skiplimit;
++daysback;
//console.log('One journal missing');
return getone();
}
return cb("Journal missing");
}
if(backjournal.list.length<donecount) {
donecount-=backjournal.list.length;
++daysback;
return getone();
}
cb(null,backjournal.list[backjournal.list.length-donecount]);
});
}
getone(1);
});
}
function filter(list,filterSet,maxcount) {
maxcount = maxcount || Infinity;
return list.filter(i=>!filterSet.has(i)).slice(0,maxcount);
}
exports.randomtasks=randomtasks;
function randomtasks(user,count,cb) {
ranks.get(user,(err,rank)=>{
var allids = Object.keys(rank);
var selectedids;
if(allids<count*count) {
selectedids=allids.sort(()=>Math.random()-0.5).slice(0,5);
}
else {
selectedids=new Set();
while(selectedids.size<count) {
selectedids.add(allids[Math.floor(allids.length*Math.random())]);
}
selectedids=Array.from(selectedids);
}
async.map(selectedids,(id,cb)=>sqldb.get('Select id,title from titles where user=? and id=?',user,id,cb),cb);
});
}
function getRanksButOnlyExposed(user,cb) {
ranks.get(user,(err,rank)=>{
if(err) return cb(err);
var out={};
var ids=Object.keys(rank);
var validids = ids.filter(id=>Object.keys(rank[id]).length>0);
validids.forEach(id=>out[id]=rank[id]);
cb(null,out);
});
}
exports.pulltaskids=pulltaskids;
function pulltaskids(user,count,cb,getter=ranks.get) {
//var key=user+'.rank';
var pulledids=[];
var pulledidsSet=new Set();
var filterSet=new Set();
//db.get(key,(err,rank)=>{
getter(user,(err,rank)=>{
if(err) return cb(err);
var rankkeys=Object.keys(rank);
var topcohort;
//console.log({rank,rankkeys});
//For blocking items with temp block.
rankkeys.forEach(i=>{ //If it currently has a block don't display it
//console.log('Block level',i,rank[i].block);
if(rank[i].block>0) {
//console.log('Blocking',i,rank[i].block);
rank[i].block=Math.floor(rank[i].block-1);
//console.log('Blocking',i,rank[i].block);
if(rank[i].block<=0) {
delete rank[i].block;
//console.log('Delete rank',i);
}
filterSet.add(i);
}
});
//db.markWrite(key);
ranks.markWrite(user);
while(pulledids.length<count) {
topcohort=minindexes(rankkeys.map(id=>undercount(id,rank[id],pulledidsSet))).map(i=>rankkeys[i]);
//console.log({rankkeys,topcohort});
if(topcohort.length==0) return cb(null,pulledids); //No more items to add. Return.
topcohort.forEach(id=>pulledidsSet.add(id));
topcohort=filter(shuffle(topcohort),filterSet,count-pulledids.length);
pulledids=pulledids.concat(topcohort);
if(pulledids.length==count) return cb(null,pulledids);
}
});
}
exports.pulltasks=pulltasks;
function pulltasks(user,count,cb) {
pulltaskids(user,count,(err,ids)=>{
if(err) return cb(err);
console.log({ids});
async.map(ids,(id,cb)=>sqldb.get('Select id,title from titles where user=? and id=?',user,id,cb),cb);
});
}
module.exports.pullexposedtasks=pullexposedtasks;
function pullexposedtasks(user,count,cb) {
pulltaskids(user,count,(err,ids)=>{
if(err) return cb(err);
console.log({ids});
async.map(ids,(id,cb)=>sqldb.get('Select id,title from titles where user=? and id=?',user,id,cb),cb);
},getRanksButOnlyExposed);
};