Browse Source

Init repo

Dhairya Malhotra 3 weeks ago
commit
00b5751554
4 changed files with 751 additions and 0 deletions
  1. 0 0
      FILES/DBFILE.TXT
  2. 30 0
      Makefile
  3. 15 0
      extract_title.py
  4. 706 0
      src/doclib.cpp

+ 0 - 0
FILES/DBFILE.TXT


+ 30 - 0
Makefile

@@ -0,0 +1,30 @@
+
+CXX=g++
+RM = rm -f
+MKDIRS = mkdir -p
+
+BINDIR = ./bin
+SRCDIR = ./src
+OBJDIR = ./obj
+INCDIR = ./include
+
+CXXFLAGS=-std=c++11 -I${OPENSSL_DIR}/include
+LDFLAGS= -lncurses -L${OPENSSL_DIR}/lib -lssl -lcrypto
+
+TARGET_BIN = \
+       $(BINDIR)/doclib
+
+all : $(TARGET_BIN)
+
+$(BINDIR)/%: $(OBJDIR)/%.o
+	-@$(MKDIRS) $(dir $@)
+	$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
+
+$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
+	-@$(MKDIRS) $(dir $@)
+	$(CXX) $(CXXFLAGS) -I$(INCDIR) -c $^ -o $@
+
+clean:
+	$(RM) -r $(BINDIR)/* $(OBJDIR)/*
+	$(RM) *~ */*~
+

+ 15 - 0
extract_title.py

@@ -0,0 +1,15 @@
+#! /usr/bin/python3
+
+import os
+import sys
+from pdfrw import PdfReader
+
+if (len(sys.argv) > 1 and os.path.isfile(sys.argv[1])):
+  fname = sys.argv[1]
+else:
+  exit()
+
+title = PdfReader(fname).Info.Title
+if (title != None):
+  print(title.strip('()'))
+

+ 706 - 0
src/doclib.cpp

@@ -0,0 +1,706 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <curses.h>
+#include <ctype.h>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <vector>
+#include <memory>
+#include <cassert>
+#include <algorithm>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <openssl/md5.h>
+
+std::string url_encode(const std::string &value) {
+  std::ostringstream escaped;
+  escaped.fill('0');
+  escaped << std::hex;
+
+  for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
+    std::string::value_type c = (*i);
+
+    // Keep alphanumeric and other accepted characters intact
+    if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
+      escaped << c;
+      continue;
+    }
+
+    // Any other characters are percent-encoded
+    escaped << std::uppercase;
+    escaped << '%' << std::setw(2) << int((unsigned char) c);
+    escaped << std::nouppercase;
+  }
+
+  return escaped.str();
+}
+
+std::string exec(const char* cmd) {
+    char buffer[128];
+    std::string result = "";
+    std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
+    if (!pipe) throw std::runtime_error("popen() failed!");
+    while (!feof(pipe.get())) {
+        if (fgets(buffer, 128, pipe.get()) != NULL)
+            result += buffer;
+    }
+    return result;
+}
+
+enum MYFIELD_IDX{
+  MYFIELD_TITLE    ,
+  MYFIELD_YEAR     ,
+  MYFIELD_AUTHORS  ,
+  MYFIELD_JOURNAL  ,
+  MYFIELD_DOI      ,
+  MYFIELD_NOTES    ,
+  MYFIELD_PATH     ,
+  MYFIELD_COUNT    ,
+  MYFIELD_BLOCKSIZE,
+};
+static const char* MYFIELD_STR[]={
+  "TITLE"    ,
+  "YEAR"     ,
+  "AUTHORS"  ,
+  "JOURNAL"  ,
+  "DOI"      ,
+  "NOTES"    ,
+  "PATH"     ,
+  "COUNT"    ,
+  "BLOCKSIZE"
+};
+
+int file_md5(const char *fname, unsigned char result[MD5_DIGEST_LENGTH]){
+  int file_descript = open(fname, O_RDONLY);
+  if(file_descript < 0) return -1;
+
+  struct stat statbuf;
+  if(fstat(file_descript, &statbuf) < 0) return -1;
+  unsigned long file_size = statbuf.st_size;
+
+  char* file_buffer = (char*)mmap(0, file_size, PROT_READ, MAP_SHARED, file_descript, 0);
+
+  MD5((unsigned char*) file_buffer, file_size, result);
+
+  munmap(file_buffer, file_size);
+
+  return 0;
+}
+
+bool addpaper(WINDOW* win, std::string* data){
+  int ch='a';
+  int max_x,max_y;
+  getmaxyx(win, max_y, max_x);
+  clear();
+
+  mvprintw(1, 0, "ADD PAPER"); wclrtoeol(win);
+
+  std::string field_cap[MYFIELD_COUNT];
+  for(long i=0;i<MYFIELD_COUNT;i++){
+    field_cap[i]=MYFIELD_STR[i];
+    field_cap[i].append(":");
+  }
+
+  std::vector<int> field_coord(2*MYFIELD_COUNT);
+  std::string field_str[MYFIELD_COUNT];
+  for(size_t i=0;i<MYFIELD_COUNT;i++) {
+    field_coord[2*i+0]=5+4*i;
+    field_coord[2*i+1]=field_str[i].size();
+  }
+
+  int curr_field=0;
+  while(1) {
+
+    clear();
+    for(size_t i=0;i<MYFIELD_COUNT;i++) {
+      mvprintw(field_coord[2*i+0]-1, 0, field_cap[i].c_str() ); wclrtoeol(win);
+      mvprintw(field_coord[2*i+0]  , 0, field_str[i].c_str() ); wclrtoeol(win);
+    }
+    move(field_coord[curr_field*2+0], field_coord[curr_field*2+1]);
+
+    ch=getch();
+    if(ch==10){ // Enter
+      break;
+    }else if(ch==27){ // Esc
+      for(size_t i=0;i<MYFIELD_COUNT;i++) field_str[i].clear();
+      break;
+    }else if(ch==259){ // Up
+      curr_field=(curr_field+MYFIELD_COUNT-1)%MYFIELD_COUNT;
+    }else if(ch==258){ // Down
+      curr_field=(curr_field+MYFIELD_COUNT+1)%MYFIELD_COUNT;
+    }else if(ch==263 || ch==330 || ch==127){ // Delete
+      if(field_str[curr_field].size() && field_coord[curr_field*2+1]){
+        field_str[curr_field].erase(field_coord[curr_field*2+1]-1,1);
+        field_coord[curr_field*2+1]--;
+      }
+    }else if(ch==260){ // Left
+      if(field_coord[curr_field*2+1]>0) field_coord[curr_field*2+1]--;
+    }else if(ch==261){ // Right
+      if(field_coord[curr_field*2+1]<field_str[curr_field].size()) field_coord[curr_field*2+1]++;
+    }else{ // default
+      addch(ch);
+      char cstr[10];
+      sprintf(cstr,"%c",ch);
+      field_str[curr_field].insert(field_coord[curr_field*2+1],cstr);
+      field_coord[curr_field*2+1]++;
+    }
+  }
+
+  bool add=true;
+  for(size_t i=0;i<MYFIELD_COUNT;i++) data[i]=field_str[i];
+  for(size_t i=0;i<MYFIELD_COUNT;i++) if(!data[i].size()) add=false;
+
+  clear();
+  return add;
+}
+
+void displaypaper(WINDOW* win, const std::vector<std::string>& db, int curr_field){
+  if(db.size()/MYFIELD_BLOCKSIZE<=curr_field) return;
+
+  int ch='a';
+  int max_x,max_y;
+  getmaxyx(win, max_y, max_x);
+  clear();
+
+  const std::string& path=db[curr_field*MYFIELD_BLOCKSIZE+MYFIELD_PATH];
+  std::string       title=db[curr_field*MYFIELD_BLOCKSIZE+MYFIELD_TITLE];
+  std::string        year=db[curr_field*MYFIELD_BLOCKSIZE+MYFIELD_YEAR];
+  std::string        doi =db[curr_field*MYFIELD_BLOCKSIZE+MYFIELD_DOI];
+
+  for(long i=0;i<MYFIELD_COUNT;i++){ // Display details
+    mvprintw(3*i, 0, MYFIELD_STR[i]);
+    mvprintw(3*i, 9, ":");
+    mvprintw(3*i, 12, db[curr_field*MYFIELD_BLOCKSIZE+i].c_str()); wclrtoeol(win);
+  }
+
+  if(!doi.size()){ // Display bibtex
+    std::string cmd("curl -H \"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36\" -Ls \"http://search.crossref.org/dois?q=");
+    cmd.append("\"");
+    cmd.append(url_encode(title));
+    cmd.append("\"");
+    cmd.append("&year=");
+    cmd.append(year);
+    cmd.append("&rows=1&sort=score\" 2> /dev/null | grep \"\\\"doi\\\"\" | rev | cut -c 3- | rev | cut -c 13- ");
+    doi=exec(cmd.c_str());
+    if (doi.size()) doi.resize(doi.size()-1);
+  }
+
+  if(doi.size()){ // Display bibtex
+    std::string bibtex;
+
+    std::string cmd("curl -H \"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36\" -LH \"Accept: text/bibliography; style=bibtex\" http://dx.doi.org/");
+    cmd.append(url_encode(doi));
+    cmd.append(" 2> /dev/null");
+    bibtex=exec(cmd.c_str());
+
+    for(long i=2;i<bibtex.size();i++){
+      if(bibtex[i-2]==-30 && bibtex[i-1]==-128 && bibtex[i]==-103){
+        bibtex[i-2]=32;
+        bibtex[i-1]=32;
+        bibtex[i-0]='\'';
+      }
+      if(bibtex[i-2]==-30 && bibtex[i-1]==-128 && bibtex[i]==-109){
+        bibtex[i-2]=32;
+        bibtex[i-1]=45;
+        bibtex[i-0]=32;
+      }
+      if(bibtex[i-2]=='}' && bibtex[i-1]==',' && bibtex[i]==' '){
+        bibtex[i]=10;
+      }
+    }
+
+    //bibtex=" @article{Rahimian_2015, title={Boundary integral method for the flow of vesicles with viscosity contrast in three dimensions}, volume={298}, ISSN={0021-9991}, url={http://dx.doi.org/10.1016/J.JCP.2015.06.017}, DOI={10.1016/j.jcp.2015.06.017}, journal={Journal of Computational Physics}, publisher={Elsevier BV}, author={Rahimian, Abtin and Veerapaneni, Shravan K. and Zorin, Denis and Biros, George}, year={2015}, month={Oct}, pages={766@~S786}}";
+    mvprintw(3*MYFIELD_COUNT+0, 0, "BIBTEX   :"); wclrtoeol(win);
+    mvprintw(3*MYFIELD_COUNT+1, 0, bibtex.c_str()); wclrtoeol(win);
+  }
+
+  ch=getch();
+  if(ch==10){ // Open file
+    std::string cmd("open.sh ");
+    cmd.append(db[curr_field*MYFIELD_BLOCKSIZE+MYFIELD_PATH]);
+    exec(cmd.c_str());
+  }
+  clear();
+}
+
+void searchpaper(WINDOW* win, const std::vector<std::string>& db_){
+  std::vector<std::string> db;
+  std::string srch_str;
+
+  int ch='a';
+  int max_x,max_y;
+  getmaxyx(win, max_y, max_x);
+  clear();
+
+  MYFIELD_IDX display_fields[]={MYFIELD_TITLE,
+                                MYFIELD_YEAR,
+                                MYFIELD_AUTHORS,
+                                MYFIELD_JOURNAL};
+
+  double field_wid[MYFIELD_COUNT];
+  for(long i=0;i<MYFIELD_COUNT;i++) field_wid[i]=0;
+  field_wid[MYFIELD_TITLE  ]=0.50;
+  field_wid[MYFIELD_YEAR   ]=0.05;
+  field_wid[MYFIELD_AUTHORS]=0.30;
+  field_wid[MYFIELD_JOURNAL]=0.15;
+
+  double field_dsp[MYFIELD_COUNT]; field_dsp[0]=0;
+  for(size_t i=1;i<MYFIELD_COUNT;i++) field_dsp[i]=field_dsp[i-1]+field_wid[i-1];
+
+  int curr_field=0;
+  int start_row=0;
+  bool update_db=true;
+  while(1) {
+    if(update_db){
+      db.clear();
+      for(size_t i=0;i<db_.size()/MYFIELD_BLOCKSIZE;i++){
+        bool found=(srch_str.size()==0);
+        for(long field=0; field<MYFIELD_COUNT; field++){
+          std::string str=db_[i*MYFIELD_BLOCKSIZE+field];
+          for(long j=0;j<str.size();j++) str[j]=std::toupper(str[j]);
+          if(found) break;
+          else found=(str.find(srch_str) != std::string::npos);
+        }
+        if(found) for(size_t field=0;field<MYFIELD_BLOCKSIZE;field++){
+          db.push_back(db_[i*MYFIELD_BLOCKSIZE+field]);
+        }
+      }
+      curr_field=0;
+      start_row=0;
+      update_db=false;
+      clear();
+    }
+
+    // Draw
+    mvprintw(1, 0, "SEARCH PAPER"); wclrtoeol(win);
+    size_t rows=db.size()/MYFIELD_BLOCKSIZE;
+    if(curr_field-start_row>=max_y-3) start_row++;
+    if(curr_field<start_row) start_row--;
+    for(size_t i=start_row;i<start_row+max_y-3;i++) {
+      if(i>=rows) break;
+      if(i==curr_field) attron(A_BOLD);// | A_UNDERLINE);//COLOR_PAIR(1));
+      for(const MYFIELD_IDX& field : display_fields){
+        std::ostringstream fmt;
+        fmt<<"| %0."<<(int)(field_wid[field]*max_x-3)<<"s";
+
+        int offset=(int)(field_dsp[field]*max_x);
+        mvprintw(3+i-start_row, offset, fmt.str().c_str(), db[i*MYFIELD_BLOCKSIZE+field].c_str() );
+        wclrtoeol(win);
+      }
+      if(i==curr_field) attroff(A_BOLD);// | A_UNDERLINE);//COLOR_PAIR(1));
+    }
+    mvprintw(2, 0, "%s", srch_str.c_str()); wclrtoeol(win);
+    move(2, srch_str.size());
+
+    ch=getch();
+    if(ch==10 || ch==261){ // Enter
+      if(db.size()>0) displaypaper(win, db, curr_field);
+    }else if(ch==27){ // Esc
+      break;
+    }else if(ch==259){ // Up
+      if(curr_field>0) curr_field--;
+    }else if(ch==258){ // Down
+      if(curr_field+1<rows) curr_field++;
+    }else if(ch==263 || ch==330 || ch==127){ // Delete
+      if(srch_str.size()){
+        delch();
+        srch_str.erase(srch_str.end()-1);
+        update_db=true;
+      }
+    }else{
+      addch(ch);
+      char cstr[10];
+      sprintf(cstr,"%c",ch);
+      srch_str.append(cstr);
+      std::transform(srch_str.begin(), srch_str.end(), srch_str.begin(), ::toupper);
+      update_db=true;
+    }
+  }
+  clear();
+}
+
+int main(){
+  auto addpaper_new = [](WINDOW* win, std::string* data) {
+    int max_x,max_y;
+    getmaxyx(win, max_y, max_x);
+    clear();
+
+    auto select_paper = [&](std::string fname) {
+      clear();
+      mvprintw(1, 0, "SEARCHING FOR PAPERS ... "); wclrtoeol(win);
+      exec(std::string("open.sh " + fname).c_str());
+      refresh();
+
+      auto extract_search_fields = [](std::vector<std::string>& vec, std::string title, std::string field, long crop0, long crop1) {
+        std::string cmd("curl -H \"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36\" -Ls \"http://search.crossref.org/dois?q=\"" + url_encode(title) + "\"");
+        std::string lst = exec((cmd + "&rows=9&sort=score\" 2> /dev/null | grep \"\\\"" + field + "\\\":\" | rev | cut -c " + std::to_string(2 + crop1) + "- | rev | cut -c " + std::to_string(field.size() + 10 + crop0) + "- ").c_str());
+        auto tokenize = [](std::vector<std::string>& vec, std::string lst) {
+          for (long i = 0; i < lst.size(); i++) {
+            if (vec.size() == 0) vec.push_back("");
+            if ((lst[i] == 13 || lst[i] == 10) && vec.back().size() != 0) {
+              vec.push_back("");
+            } else {
+              vec.back().append(std::string() + lst[i]);
+            }
+          }
+          if (vec.size() && vec.back().size() == 0) vec.resize(vec.size()-1);
+        };
+        tokenize(vec, lst);
+      };
+      auto get_bib_fields = [](std::string& title_, std::string& year_, std::string& author_, std::string& journal_, std::string& doi_, const std::string& doi) {
+        if(doi.size()){ // Display bibtex
+          std::string bibtex;
+          bibtex=exec(std::string("curl -LH \"Accept: text/bibliography; style=bibtex\" http://dx.doi.org/" + url_encode(doi) + " 2> /dev/null").c_str());
+
+          auto extract_field = [](std::string str, std::string field) {
+            long i;
+            std::string out;
+            for (i = 0; i < str.size() - field.size() - 2; i++) {
+              bool matched = true;
+              for (long j = 0; j < field.size(); j++) {
+                if (str[i+j] != field[j]) {
+                  matched = false;
+                  continue;
+                }
+                if (matched && str[i+field.size()+0] == '=' && str[i+field.size()+1] == '{') {
+                  i += field.size() + 2;
+                  for (; i < str.size(); i++) {
+                    if (str[i] == '}') break;
+                    out.push_back(str[i]);
+                  }
+                }
+              }
+            }
+            return out;
+          };
+          title_ = extract_field(bibtex, "title");
+          year_ = extract_field(bibtex, "year");
+          author_ = extract_field(bibtex, "author");
+          journal_ = extract_field(bibtex, "journal");
+          doi_ = extract_field(bibtex, "doi");
+        }
+      };
+      auto transform_authors = [](std::string in) {
+        auto tokenize = [](std::string in, std::string sep) {
+          std::vector<std::string> vec(1);
+          for (long i = 0; i < in.size(); i++) {
+            bool new_token = true;
+            for (long j = 0; j < sep.size(); j++) {
+              if (in[i + j] != sep[j]) {
+                new_token = false;
+                break;
+              }
+            }
+            if (!new_token) {
+              vec.back().push_back(in[i]);
+            } else {
+              vec.push_back(std::string());
+              i += sep.size()-1;
+            }
+          }
+          if (vec.size() && vec.back().size() == 0) vec.resize(vec.size() - 1);
+          for (auto x:vec) std::cout<<x<<'\n';
+          return vec;
+        };
+        std::string out;
+        std::vector<std::string> author_vec = tokenize(in, " and ");
+        for (long i = 0; i < author_vec.size(); i++) {
+          std::vector<std::string> name_vec = tokenize(author_vec[i], ", ");
+          for (long j = name_vec.size()-1; j >= 0; j--) out += name_vec[j] + " ";
+          if (out.size()) out.resize(out.size() - 1);
+          out += ", ";
+        };
+        if (out.size()) out.resize(out.size() - 2);
+        return out;
+      };
+      std::string title = exec(("./extract_title.py " + fname).c_str());
+      std::vector<std::string> doi_vec, year_vec, title_vec;
+      extract_search_fields(doi_vec, title, "doi", 0, 1);
+      extract_search_fields(year_vec, title, "year", -1, -1);
+      extract_search_fields(title_vec, title, "title", 0, 1);
+      //assert(year_vec.size() == doi_vec.size());
+      //assert(title_vec.size() == doi_vec.size());
+
+      clear();
+      std::string cmd("curl -H \"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36\" -Ls \"http://search.crossref.org/dois?q=\"" + url_encode(title) + "\"" + "&rows=9&sort=score\" 2> /dev/null");
+      mvprintw(1, 0, "ADD PAPER"); wclrtoeol(win);
+      mvprintw(2, 0, "%s: %s", fname.c_str(), cmd.c_str()); wclrtoeol(win);
+      for (long i = 0; i < doi_vec.size(); i++) {
+        mvprintw(4+i, 0, "%s", (std::to_string(i+1) + ") " + year_vec[i] + " - " + title_vec[i]).c_str()); wclrtoeol(win);
+      }
+
+      while (1) {
+        int ch=getch();
+        long option = (long)ch - 48;
+        if (ch==27) {
+          return false;
+        } else if (0 < option && option <= 9) {
+          std::string bib_title, bib_year, bib_author, bib_journal, bib_doi;
+          get_bib_fields(bib_title, bib_year, bib_author, bib_journal, bib_doi, doi_vec[option-1]);
+
+          std::string field_str[MYFIELD_COUNT];
+          field_str[MYFIELD_TITLE  ] = title_vec[option-1];
+          field_str[MYFIELD_YEAR   ] = year_vec[option-1];
+          field_str[MYFIELD_AUTHORS] = transform_authors(bib_author);
+          field_str[MYFIELD_JOURNAL] = bib_journal;
+          field_str[MYFIELD_DOI    ] = doi_vec[option-1];
+          field_str[MYFIELD_NOTES  ] = "";
+          field_str[MYFIELD_PATH   ] = fname;
+          for(size_t i=0;i<MYFIELD_COUNT;i++) data[i]=field_str[i];
+          return true;
+        } else if (option == 0) {
+          return addpaper(win, data);
+        }
+      }
+    };
+
+    std::string srch_str;
+    bool update_db=true;
+    while(1) {
+      clear();
+      mvprintw(1, 0, "ADD PAPER"); wclrtoeol(win);
+      mvprintw(2, 0, "%s", srch_str.c_str()); wclrtoeol(win);
+      move(2, srch_str.size());
+      int ch=getch();
+      if(ch==10 || ch==261){ // Enter
+        auto ret = select_paper(srch_str);
+        clear();
+        if (ret) return true;
+      }else if(ch==27){ // Esc
+        break;
+      }else if(ch==259){ // Up
+      }else if(ch==258){ // Down
+      }else if(ch==263 || ch==330 || ch==127){ // Delete
+        if(srch_str.size()){
+          delch();
+          srch_str.erase(srch_str.end()-1);
+          update_db=true;
+        }
+      }else{
+        addch(ch);
+        char cstr[10];
+        sprintf(cstr,"%c",ch);
+        srch_str.append(cstr);
+        std::transform(srch_str.begin(), srch_str.end(), srch_str.begin(), ::toupper);
+        update_db=true;
+      }
+    }
+    clear();
+
+    return false;
+  };
+
+  initscr(); // Start curses mode
+  if(0) if(has_colors() == TRUE){
+    start_color(); // Start color
+    init_pair(1, COLOR_YELLOW, COLOR_BLACK);
+  }
+
+  WINDOW * mainwin=NULL;
+  int ch=0;
+  int max_x,max_y;
+  getmaxyx(mainwin, max_y, max_x);
+
+  // Initialize ncurses
+  if ( (mainwin = initscr()) == NULL ) {
+    fprintf(stderr, "Error initialising ncurses.\n");
+    exit(EXIT_FAILURE);
+  }
+
+  noecho();                  // Turn off key echoing
+  keypad(mainwin, TRUE);     // Enable the keypad for non-char keys
+
+  std::vector<std::string> db;
+  { // load db
+    std::string line;
+    std::ifstream dbfile("FILES/DBFILE.TXT");
+    if(dbfile.is_open()){
+      while(!dbfile.eof()){
+        getline(dbfile, line);
+        db.push_back(line);
+      }
+      dbfile.close();
+    }
+    if(db.size()) db.pop_back();
+    { // reverse
+      std::vector<std::string> db_rev=db;
+      for (int i = 0; i < db.size() / MYFIELD_BLOCKSIZE; i++) {
+        for (int j = 0; j < MYFIELD_BLOCKSIZE; j++) {
+          db[i * MYFIELD_BLOCKSIZE + j] = db_rev[(db.size() / MYFIELD_BLOCKSIZE - i - 1) * MYFIELD_BLOCKSIZE + j];
+        }
+      }
+    }
+  }
+
+  while ( ch!='q' && ch!=27 ) {
+    refresh();
+    mvprintw(1, 0, "MAIN MENU"); wclrtoeol(mainwin);
+
+    ch=getch();
+    if(ch==27){ // Esc
+      mvaddstr(0, 0, "# ESC"); wclrtoeol(mainwin);
+    }else if(ch=='q'){ // Quit
+      mvaddstr(0, 0, "# Quit"); wclrtoeol(mainwin);
+    }else if(ch=='s'){ // Search
+      mvaddstr(0, 0, "# Search"); wclrtoeol(mainwin);
+      searchpaper(mainwin,db);
+    }else if(ch=='a'){ // Add
+      mvaddstr(0, 0, "# Add"); wclrtoeol(mainwin);
+      std::string data_fields[MYFIELD_COUNT];
+      if(addpaper_new(mainwin,data_fields)){
+        unsigned char md5result[MD5_DIGEST_LENGTH];
+        //std::string file_ext=data_fields[MYFIELD_PATH].substr(data_fields[MYFIELD_PATH].size()-4,3);
+        std::string file_ext=data_fields[MYFIELD_PATH].substr(data_fields[MYFIELD_PATH].find_last_of("."));
+        std::transform(file_ext.begin(), file_ext.end(), file_ext.begin(), ::toupper);
+        bool md5=!file_md5(data_fields[MYFIELD_PATH].c_str(), md5result);
+        if(md5){
+          std::string ofname;
+          { // get MD5 fname
+            ofname.append("FILES/");
+            for(int i=0;i<MD5_DIGEST_LENGTH;i++){
+              char str[10];
+              sprintf(str, "%02x", md5result[i]);
+              std::transform(str, str+2, str, ::toupper);
+              ofname.append(str);
+            }
+            ofname.append(file_ext);//".PDF");
+          }
+          { // move file
+            std::rename(data_fields[MYFIELD_PATH].c_str(), ofname.c_str());
+          }
+          for(int i=0;i<MYFIELD_COUNT-1;i++){
+            std::transform(data_fields[i].begin(), data_fields[i].end(), data_fields[i].begin(), ::toupper);
+            db.push_back(data_fields[i]);
+          }
+          db.push_back(ofname);
+          {
+            std::string s="##########################################################################################################################################################################################################";
+            db.push_back(s);
+          }
+        }
+      }
+    }else if(ch=='t'){ // Test
+      {
+      //std::string data_fields[MYFIELD_COUNT];
+      //for(size_t i=59;i<60;i++){
+      //  {
+      //    std::stringstream ss;
+      //    ss<<i<<".pdf";
+      //    data_fields[MYFIELD_COUNT-1]=ss.str();
+      //  }
+      //  unsigned char md5result[MD5_DIGEST_LENGTH];
+      //  bool md5=!file_md5(data_fields[MYFIELD_COUNT-1].c_str(), md5result);
+      //  if(md5){
+      //    std::string ofname;
+      //    { // get MD5 fname
+      //      ofname.append("FILES/");
+      //      for(int i=0;i<MD5_DIGEST_LENGTH;i++){
+      //        char str[10];
+      //        sprintf(str, "%02x", md5result[i]);
+      //        std::transform(str, str+2, str, ::toupper);
+      //        ofname.append(str);
+      //      }
+      //      ofname.append(".PDF");
+      //    }
+      //    { // move file
+      //      std::rename(data_fields[MYFIELD_COUNT-1].c_str(), ofname.c_str());
+      //    }
+      //    for(int i=0;i<MYFIELD_COUNT-1;i++){
+      //      std::transform(data_fields[i].begin(), data_fields[i].end(), data_fields[i].begin(), ::toupper);
+      //      db.push_back(data_fields[i]);
+      //    }
+      //    db.push_back(ofname);
+      //    {
+      //      std::string s;
+      //      db.push_back(s);
+      //    }
+      //  }
+      //}
+      }
+    }else if(ch=='x'){ // Export (pdf only)
+      {
+      //for(size_t i=0;i<db.size()/MYFIELD_BLOCKSIZE;i++){
+      //  std::string ifname=db[i*MYFIELD_BLOCKSIZE+4];
+      //  if(ifname.end()[-3]=='P' && ifname.end()[-2]=='D' && ifname.end()[-1]=='F'){
+      //    std::string ofname="export/";
+      //    std::string i_str=std::to_string(i);
+      //    for(size_t j=0;j<4-i_str.size();j++) ofname.append("0");
+      //    ofname.append(i_str);
+      //    ofname.append("-");
+      //    ofname.append(db[i*MYFIELD_BLOCKSIZE+0]);
+      //    std::transform(ofname.begin(), ofname.end(), ofname.begin(), ::tolower);
+      //    for(size_t j=0;j<ofname.size();j++) if(ofname[j]==' ') ofname[j]='-';
+      //    ofname.append(".pdf");
+      //    std::ifstream src(ifname.c_str());
+      //    std::ofstream dst(ofname.c_str());
+      //    dst<<src.rdbuf();
+      //  }
+      //}
+      }
+    }else{ // Nothing
+      mvprintw(0, 0, "Unrecognized command: %d", ch); clrtoeol();
+    }
+  }
+
+  { // save db
+    { // reverse
+      std::vector<std::string> db_rev=db;
+      for (int i = 0; i < db.size() / MYFIELD_BLOCKSIZE; i++) {
+        for (int j = 0; j < MYFIELD_BLOCKSIZE; j++) {
+          db[i * MYFIELD_BLOCKSIZE + j] = db_rev[(db.size() / MYFIELD_BLOCKSIZE - i - 1) * MYFIELD_BLOCKSIZE + j];
+        }
+      }
+    }
+    std::string line;
+    std::ofstream dbfile("FILES/DBFILE.TXT");
+    if(dbfile.is_open()){
+      for(int i=0;i<db.size();i++) dbfile<<db[i]<<'\n';
+      dbfile.close();
+    }
+  }
+
+  // Clean up after ourselves
+  delwin(mainwin);
+  endwin();
+  refresh();
+
+  return EXIT_SUCCESS;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+//mvaddstr(0, 1, "");
+//mvprintw(0, 1, "");
+
+
+
+//#include <iostream>
+//#include <stdio.h>
+//#include <stdlib.h>
+//#include <curses.h>
+//#include <termios.h>
+//#include <unistd.h>
+//#include <termios.h>
+//#include <sys/ioctl.h>
+//#include <fcntl.h>
+
+// use system call to make terminal send all keystrokes directly to stdin
+//system ("/bin/stty raw");
+
+
+// use system call to set terminal behaviour to more normal behaviour
+//system ("/bin/stty cooked");
+