Button to add files and folders to Calibre library with GUI or server

I thought I should share a button I made that adds book files and folders to the Calibre library whether the GUI or server is running. The Calibre database cannot be modified directly from the command line when the GUI is running without the server. This script will test if the server and GUI are running and will terminate the GUI and restart if the server is off. If the server is running, the option to allow unauthenticated local connections to make changes must be enabled for this to work.

The selected files are iterated through and all files matching the book extensions are added. If a selected folder contains at least one book, the books are added to the library and the folder is deleted to the recycle bin if supported.

I attempted to account for folders that contain the source code for a book by testing if the folder name contains the word 'code', if the folder contains more than 10 items and if there is a pdf that has the words 'list' and 'hardware' or 'software'. These folders are not deleted.

The current book formats tested for are pdf, epub, mobi, azw, azw3 & chm.

The port is set to the default 8080 but can be easily changed at the top of the script, along with the location of the Calibre executable. The maximum number of files tested for before deleted a directory can also be changed at the top of the script with max_num_files.

Any thoughts/suggestions welcome :slightly_smiling_face:

The button can be downloaded here or pasted into the toolbar:

<?xml version="1.0"?>
<button backcol="none" display="both" icon_size="large" label_pos="right" textcol="none">
	<label>Calibre+</label>
	<icon1>#newcommand</icon1>
	<function type="script">
		<instruction>@script JScript</instruction>
		<instruction>var calibre_location = &quot;C:\\Program Files\\Calibre2\\&quot;;</instruction>
		<instruction>var server_port = &quot;8080&quot;;</instruction>
		<instruction />
		<instruction>var max_num_files = 10;</instruction>
		<instruction />
		<instruction>function OnClick(clickData) {</instruction>
		<instruction />
		<instruction>    DOpus.ClearOutput();</instruction>
		<instruction />
		<instruction>    var num_books = 0;</instruction>
		<instruction>    //----------------------------</instruction>
		<instruction>    //Test if server is running</instruction>
		<instruction>    //----------------------------</instruction>
		<instruction>    var server_switch = false;</instruction>
		<instruction>    var objShell = new ActiveXObject(&quot;WScript.Shell&quot;);</instruction>
		<instruction>    var objExec = objShell.Exec(&quot;%comspec% /c netstat -a -n | find \&quot;LISTENING\&quot; | find \&quot;&quot; + server_port + &apos;&quot;&apos;);</instruction>
		<instruction>    var port_str = objExec.StdOut.ReadAll();</instruction>
		<instruction />
		<instruction>    if (port_str.indexOf(server_port) &gt;= 0) {</instruction>
		<instruction>      server_switch = true;</instruction>
		<instruction>      DOpus.Output(&quot;Server on&quot;);</instruction>
		<instruction>    }</instruction>
		<instruction />
		<instruction>    //------------------------------------------------</instruction>
		<instruction>    //Terminate gui if server isn&apos;t running</instruction>
		<instruction>    //------------------------------------------------</instruction>
		<instruction>    if (!server_switch) {</instruction>
		<instruction>      var gui_switch = false;</instruction>
		<instruction>      var wmi = GetObject(&quot;winmgmts:&quot;);</instruction>
		<instruction>      var proc = wmi.execquery(&quot;select * from Win32_process where Name=&apos;calibre.exe&apos;&quot;);</instruction>
		<instruction />
		<instruction>      var proc_itm</instruction>
		<instruction>      var enumProc = new Enumerator(proc);</instruction>
		<instruction>      for (; !enumProc.atEnd(); enumProc.moveNext()) {</instruction>
		<instruction>        proc_itm = enumProc.item();</instruction>
		<instruction>        proc_itm.terminate();</instruction>
		<instruction>        DOpus.delay(2000);</instruction>
		<instruction>        DOpus.Output(&quot;Calibre Terminated&quot;);</instruction>
		<instruction>        gui_switch = true;</instruction>
		<instruction>      }</instruction>
		<instruction>    }</instruction>
		<instruction>    //------------------------------------</instruction>
		<instruction>    //Process selected files and folders</instruction>
		<instruction>    //------------------------------------</instruction>
		<instruction>    var cmd = clickData.func.command;</instruction>
		<instruction>    cmd.deselect = false;</instruction>
		<instruction>    var selected_dirs = clickData.func.sourcetab.selected_dirs;</instruction>
		<instruction>    var selected_files = clickData.func.sourcetab.selected_files;</instruction>
		<instruction />
		<instruction>    //------------------------------</instruction>
		<instruction>    //Ensure files or folders selected</instruction>
		<instruction>    //------------------------------</instruction>
		<instruction>    if ((selected_dirs.count &lt; 1) &amp;&amp; (selected_files.count &lt; 1)) {</instruction>
		<instruction>      DOpus.Output(&quot;No directories or files selected&quot;);</instruction>
		<instruction>      return;</instruction>
		<instruction>    }</instruction>
		<instruction />
		<instruction>    var book_exts = [&quot;.pdf&quot;, &quot;.azw&quot;, &quot;.azw3&quot;, &quot;.epub&quot;, &quot;.mobi&quot;, &quot;.chm&quot;];</instruction>
		<instruction />
		<instruction>    var exec = DOpus.Create.Command();</instruction>
		<instruction />
		<instruction>    //-----------------------------------------</instruction>
		<instruction>    //Add selected books to calibre and delete</instruction>
		<instruction>    //-----------------------------------------</instruction>
		<instruction>    var book_files = 0</instruction>
		<instruction>    if (selected_files.count &gt; 0) {</instruction>
		<instruction>      var itm</instruction>
		<instruction>      for (var eItems = new Enumerator(selected_files); !eItems.atEnd(); eItems.moveNext()) {</instruction>
		<instruction>        itm = eItems.item();</instruction>
		<instruction>        DOpus.Output(itm.ext);</instruction>
		<instruction>        for (var i = 0; i &lt; book_exts.length; i++) {</instruction>
		<instruction>          if (book_exts[i] == itm.ext.toLowerCase()) {</instruction>
		<instruction>            var objExec = objShell.Exec(PathBuilder(itm.realpath, server_switch));</instruction>
		<instruction>            while (objExec.Status == 0) {</instruction>
		<instruction>              DOpus.Delay(100);</instruction>
		<instruction>            }</instruction>
		<instruction>            exec.RunCommand(&quot;Delete RECYCLE \&quot;&quot; + itm.realpath + &quot;\&quot; QUIET&quot;);</instruction>
		<instruction>            DOpus.Output(itm.name + &quot; added to calibre&quot;);</instruction>
		<instruction>          }</instruction>
		<instruction>        }</instruction>
		<instruction>      }</instruction>
		<instruction>    }</instruction>
		<instruction />
		<instruction>  //----------------------------------</instruction>
		<instruction>  //Add folders with books and delete</instruction>
		<instruction>  //----------------------------------</instruction>
		<instruction />
		<instruction>  if (selected_dirs.count &gt; 0){</instruction>
		<instruction>	//Loop through selected dirs</instruction>
		<instruction>    for (var dirs = new Enumerator(selected_dirs); !dirs.atEnd(); dirs.moveNext()){</instruction>
		<instruction>      var dir = dirs.item();</instruction>
		<instruction />
		<instruction>	  //Test if &apos;code&apos; is in the folder name</instruction>
		<instruction>      var code_in_name = false;</instruction>
		<instruction>      code_in_name = (dir.name.toLowerCase().indexOf(&apos;code&apos;) &gt; -1);</instruction>
		<instruction>	  if (code_in_name){</instruction>
		<instruction>		DOpus.Output(&quot;Folder name contains &apos;code&apos;&quot; + dir.name)</instruction>
		<instruction>	  }</instruction>
		<instruction />
		<instruction>      var book_found = false;</instruction>
		<instruction>      var files_test = false;</instruction>
		<instruction>	  var code_dir = false;</instruction>
		<instruction />
		<instruction>      var folderEnum = DOpus.FSUtil.ReadDir(dir.realpath, &apos;r&apos;);</instruction>
		<instruction />
		<instruction>	  //Test if number of files is greater than max_num_files</instruction>
		<instruction>	  var enumTest = DOpus.FSUtil.ReadDir(dir.realpath, &apos;r&apos;);</instruction>
		<instruction>	  var files_test = TooManyFiles(enumTest);</instruction>
		<instruction>	  if (files_test){</instruction>
		<instruction>		DOpus.Output(&quot;Folder with more than &quot; + max_num_files + &quot; found - &quot; + dir.name);</instruction>
		<instruction>	  }</instruction>
		<instruction />
		<instruction>	  //Loop through folder</instruction>
		<instruction>	  book_search:</instruction>
		<instruction>      while (!folderEnum.complete) {</instruction>
		<instruction />
		<instruction>		var folder_itm = folderEnum.Next();</instruction>
		<instruction />
		<instruction>		var file_name = folder_itm.name.toLowerCase();</instruction>
		<instruction>        for (var j = 0; j &lt; book_exts.length; j++){</instruction>
		<instruction>		//Ignore files with name containing &apos;list&apos; and &apos;hardware&apos; or &apos;software&apos;</instruction>
		<instruction>          if ((file_name.indexOf(&quot;list&quot;) &gt; -1) &amp;&amp; ((folder_name.indexOf(&quot;software&quot;) &gt; -1) || (folder_name.indexOf(&quot;hardware&quot;) &gt; -1))){</instruction>
		<instruction>            var code_dir = true;</instruction>
		<instruction>			DOpus.Output(&quot;Not adding Software Hardware List file&quot;);</instruction>
		<instruction>            continue book_search;</instruction>
		<instruction>          }</instruction>
		<instruction>          if (book_exts[j] == folder_itm.ext.toLowerCase()){</instruction>
		<instruction>            book_found = true;</instruction>
		<instruction>			DOpus.Output(&quot;Book found - &quot; + folder_itm.name);</instruction>
		<instruction>            var objExec2 = objShell.Exec(PathBuilder(folder_itm.realpath, server_switch));</instruction>
		<instruction>            while (objExec2.status == 0){</instruction>
		<instruction>              DOpus.Delay(100);</instruction>
		<instruction>            }</instruction>
		<instruction>          }</instruction>
		<instruction>        }</instruction>
		<instruction>      }</instruction>
		<instruction>      if ((book_found)&amp;&amp;(!code_dir)&amp;&amp;(!files_test)&amp;&amp;(!code_in_name)) {</instruction>
		<instruction>	  	DOpus.Output(dir.name + &quot; deleted&quot;);</instruction>
		<instruction>        exec.RunCommand(&quot;Delete RECYCLE \&quot;&quot; + dir.realpath + &quot;\&quot; QUIET&quot;);</instruction>
		<instruction>      }</instruction>
		<instruction>    }</instruction>
		<instruction>  }</instruction>
		<instruction>  //-----------------------------------------</instruction>
		<instruction>  //Restart Calibre process if already running</instruction>
		<instruction>  //-----------------------------------------</instruction>
		<instruction>  if (gui_switch){</instruction>
		<instruction>    objShell.Run(calibre_location + &quot;calibre.exe&quot;, 2, false);</instruction>
		<instruction>  }</instruction>
		<instruction>}</instruction>
		<instruction />
		<instruction>//------------------------------------------------</instruction>
		<instruction>function PathBuilder(s_path, s_switch) {</instruction>
		<instruction>  var calibre_path = calibre_location + &quot;calibredb add -d &quot; + &quot;\&quot;&quot; + s_path + &quot;\&quot;&quot;;</instruction>
		<instruction>  if (s_switch) {</instruction>
		<instruction>    var path_build = calibre_path + &quot; --with-library \&quot;http://127.0.0.1:&quot; + server_port + &quot;\&quot;&quot;;</instruction>
		<instruction>  }</instruction>
		<instruction>  if (!s_switch) {</instruction>
		<instruction>    var path_build = calibre_path;</instruction>
		<instruction>  }</instruction>
		<instruction>  return path_build;</instruction>
		<instruction>}</instruction>
		<instruction />
		<instruction />
		<instruction>//---------------------------------------------------</instruction>
		<instruction>function TooManyFiles(dir_enum){</instruction>
		<instruction>  var file_vector = dir_enum.Next(-1);</instruction>
		<instruction>  if (file_vector.count &gt; max_num_files){</instruction>
		<instruction>  	return true;</instruction>
		<instruction>  }</instruction>
		<instruction>  return false;</instruction>
		<instruction>}</instruction>
	</function>
</button>

For those interested, the source code:

var calibre_location = "C:\\Program Files\\Calibre2\\";
var server_port = "8080";

var max_num_files = 10;

function OnClick(clickData) {

    DOpus.ClearOutput();

    var num_books = 0;
    //----------------------------
    //Test if server is running
    //----------------------------
    var server_switch = false;
    var objShell = new ActiveXObject("WScript.Shell");
    var objExec = objShell.Exec("%comspec% /c netstat -a -n | find \"LISTENING\" | find \"" + server_port + '"');
    var port_str = objExec.StdOut.ReadAll();

    if (port_str.indexOf(server_port) >= 0) {
      server_switch = true;
      DOpus.Output("Server on");
    }

    //------------------------------------------------
    //Terminate gui if server isn't running
    //------------------------------------------------
    if (!server_switch) {
      var gui_switch = false;
      var wmi = GetObject("winmgmts:");
      var proc = wmi.execquery("select * from Win32_process where Name='calibre.exe'");

      var proc_itm
      var enumProc = new Enumerator(proc);
      for (; !enumProc.atEnd(); enumProc.moveNext()) {
        proc_itm = enumProc.item();
        proc_itm.terminate();
        DOpus.delay(2000);
        DOpus.Output("Calibre Terminated");
        gui_switch = true;
      }
    }
    //------------------------------------
    //Process selected files and folders
    //------------------------------------
    var cmd = clickData.func.command;
    cmd.deselect = false;
    var selected_dirs = clickData.func.sourcetab.selected_dirs;
    var selected_files = clickData.func.sourcetab.selected_files;

    //------------------------------
    //Ensure files or folders selected
    //------------------------------
    if ((selected_dirs.count < 1) && (selected_files.count < 1)) {
      DOpus.Output("No directories or files selected");
      return;
    }

    var book_exts = [".pdf", ".azw", ".azw3", ".epub", ".mobi", ".chm"];

    var exec = DOpus.Create.Command();

    //-----------------------------------------
    //Add selected books to calibre and delete
    //-----------------------------------------
    var book_files = 0
    if (selected_files.count > 0) {
      var itm
      for (var eItems = new Enumerator(selected_files); !eItems.atEnd(); eItems.moveNext()) {
        itm = eItems.item();
        DOpus.Output(itm.ext);
        for (var i = 0; i < book_exts.length; i++) {
          if (book_exts[i] == itm.ext.toLowerCase()) {
            var objExec = objShell.Exec(PathBuilder(itm.realpath, server_switch));
            while (objExec.Status == 0) {
              DOpus.Delay(100);
            }
            exec.RunCommand("Delete RECYCLE \"" + itm.realpath + "\" QUIET");
            DOpus.Output(itm.name + " added to calibre");
          }
        }
      }
    }

  //----------------------------------
  //Add folders with books and delete
  //----------------------------------

  if (selected_dirs.count > 0){
	//Loop through selected dirs
    for (var dirs = new Enumerator(selected_dirs); !dirs.atEnd(); dirs.moveNext()){
      var dir = dirs.item();

	  //Test if 'code' is in the folder name
      var code_in_name = false;
      code_in_name = (dir.name.toLowerCase().indexOf('code') > -1);
	  if (code_in_name){
		DOpus.Output("Folder name contains 'code'" + dir.name)
	  }

      var book_found = false;
      var files_test = false;
	  var code_dir = false;

      var folderEnum = DOpus.FSUtil.ReadDir(dir.realpath, 'r');

	  //Test if number of files is greater than max_num_files
	  var enumTest = DOpus.FSUtil.ReadDir(dir.realpath, 'r');
	  var files_test = TooManyFiles(enumTest);
	  if (files_test){
		DOpus.Output("Folder with more than " + max_num_files + " found - " + dir.name);
	  }

	  //Loop through folder
	  book_search:
      while (!folderEnum.complete) {

		var folder_itm = folderEnum.Next();

		var file_name = folder_itm.name.toLowerCase();
        for (var j = 0; j < book_exts.length; j++){
		//Ignore files with name containing 'list' and 'hardware' or 'software'
          if ((file_name.indexOf("list") > -1) && ((folder_name.indexOf("software") > -1) || (folder_name.indexOf("hardware") > -1))){
            var code_dir = true;
			DOpus.Output("Not adding Software Hardware List file");
            continue book_search;
          }
          if (book_exts[j] == folder_itm.ext.toLowerCase()){
            book_found = true;
			DOpus.Output("Book found - " + folder_itm.name);
            var objExec2 = objShell.Exec(PathBuilder(folder_itm.realpath, server_switch));
            while (objExec2.status == 0){
              DOpus.Delay(100);
            }
          }
        }
      }
      if ((book_found)&&(!code_dir)&&(!files_test)&&(!code_in_name)) {
	  	DOpus.Output(dir.name + " deleted");
        exec.RunCommand("Delete RECYCLE \"" + dir.realpath + "\" QUIET");
      }
    }
  }
  //-----------------------------------------
  //Restart Calibre process if already running
  //-----------------------------------------
  if (gui_switch){
    objShell.Run(calibre_location + "calibre.exe", 2, false);
  }
}

//------------------------------------------------
function PathBuilder(s_path, s_switch) {
  var calibre_path = calibre_location + "calibredb add -d " + "\"" + s_path + "\"";
  if (s_switch) {
    var path_build = calibre_path + " --with-library \"http://127.0.0.1:" + server_port + "\"";
  }
  if (!s_switch) {
    var path_build = calibre_path;
  }
  return path_build;
}


//---------------------------------------------------
function TooManyFiles(dir_enum){
  var file_vector = dir_enum.Next(-1);
  if (file_vector.count > max_num_files){
  	return true;
  }
  return false;
}

3 Likes

This might make me get my Calibre going and update my ebook database and storage. Hmm.

You may be interested in this post too.

1 Like

That's a great post, it's what inspired me to do this. I don't really do too much ebook conversion. The biggest problem I had was importing books when the gui is open without the server. There is no confirmation of successful import from the calibre command line so folders were being deleted without being imported. And what's the fun in doing things manually :wink: