<commit_before>var EventEmitter = require('events').EventEmitter;
var http = require('http');
var https = require('https');
var assert = require('assert');
var WebSocketServer = require('ws').Server;
var fs = require('fs');
var yazl = require('yazl');
var util = require('util');
var path = require('path');
var Pend = require('pend');
var express = require('express');
var osenv = require('osenv');
var plugins = [
  require('./plugins/lastfm'),
];
var Player = require('./player');
var PlayerServer = require('./player_server');
var MpdProtocol = require('./mpd_protocol');
var MpdApiServer = require('./mpd_api_server');
var WebSocketApiClient = require('./web_socket_api_client');
var leveldown = require('leveldown');
var net = require('net');
var safePath = require('./safe_path');
var MultipartForm = require('multiparty').Form;
var createGzipStatic = require('connect-static');
var serveStatic = require('serve-static');
var Cookies = require('cookies');
var log = require('./log');

module.exports = GrooveBasin;

var defaultConfig = {
  host: '0.0.0.0',
  port: 16242,
  dbPath: "groovebasin.db",
  musicDirectory: path.join(osenv.home(), "music"),
  lastFmApiKey: "<KEY>",
  lastFmApiSecret: "<KEY>",
  mpdHost: '0.0.0.0',
  mpdPort: 6600,
  acoustidAppKey: '<KEY>',
  encodeQueueDuration: 8,
  sslKey: 'certs/self-signed-key.pem',
  sslCert: 'certs/self-signed-cert.pem',
};

util.inherits(GrooveBasin, EventEmitter);
function GrooveBasin() {
  EventEmitter.call(this);

  this.app = express();
}

GrooveBasin.prototype.initConfigVar = function(name, defaultValue) {
  this.configVars.push(name);
  this[name] = defaultValue;
};

GrooveBasin.prototype.loadConfig = function(cb) {
  var self = this;
  var pathToConfig = "config.json";
  fs.readFile(pathToConfig, {encoding: 'utf8'}, function(err, contents) {
    var anythingAdded = false;
    var config;
    if (err) {
      if (err.code === 'ENOENT') {
        anythingAdded = true;
        self.config = defaultConfig;
        log.warn("No " + pathToConfig + " found; writing default.");
      } else {
        return cb(err);
      }
    } else {
      try {
        self.config = JSON.parse(contents);
      } catch (err) {
        cb(new Error("Unable to parse " + pathToConfig + ": " + err.message));
        return;
      }
    }
    // this ensures that even old files get new config values when we add them
    for (var key in defaultConfig) {
      if (self.config[key] === undefined) {
        anythingAdded = true;
        self.config[key] = defaultConfig[key];
      }
    }
    if (anythingAdded) {
      fs.writeFile(pathToConfig, JSON.stringify(self.config, null, 4), cb);
    } else {
      cb();
    }
  });
};

GrooveBasin.prototype.start = function() {
  var self = this;

  if (process.argv.indexOf('--verbose') > 0) {
    log.level = log.levels.Debug;
  } else {
    log.level = log.levels.Warn;
  }

  var pend = new Pend();
  pend.go(function(cb) {
    self.loadConfig(cb);
  });
  pend.go(function(cb) {
    var options = {
      dir: path.join(__dirname, "../public"),
      aliases: [],
    };
    createGzipStatic(options, function(err, middleware) {
      if (err) return cb(err);
      self.app.use(middleware);
      cb();
    });
  });
  pend.go(function(cb) {
    createGzipStatic({dir: path.join(__dirname, "../src/public")}, function(err, middleware) {
      if (err) return cb(err);
      self.app.use(middleware);
      cb();
    });
  });
  pend.wait(function(err) {
    if (err) {
      log.error(err.stack);
      return;
    }
    pend.go(function(cb) {
      self.db = leveldown(self.config.dbPath);
      self.db.open(cb);
    });
    pend.wait(makePlayer);
  });
  function makePlayer(err) {
    if (err) {
      log.error(err.stack);
      return;
    }
    self.httpHost = self.config.host;
    self.httpPort = self.config.port;
    if (process.argv.indexOf('--delete-all-users') > 0) {
      // this will call process.exit when done
      PlayerServer.deleteAllUsers(self.db);
      return;
    }


    self.player = new Player(self.db, self.config.musicDirectory, self.config.encodeQueueDuration);
    self.player.initialize(function(err) {
      if (err) {
        log.error("unable to initialize player:", err.stack);
        return;
      }
      log.debug("Player initialization complete.");

      var pend = new Pend();
      plugins.forEach(function(PluginClass) {
        var plugin = new PluginClass(self);
        if (plugin.initialize) pend.go(plugin.initialize.bind(plugin));
      });
      pend.wait(function(err) {
        if (err) {
          log.error("Error initializing plugin:", err.stack);
          return;
        }
        self.startServer();
      });
    });
  }

};

GrooveBasin.prototype.initializeDownload = function() {
  var self = this;
  var musicDir = self.config.musicDirectory;
  var serve = serveStatic(musicDir);
  self.app.use('/library', serveStaticMiddleware);
  self.app.get('/library/', self.hasPermRead, function(req, resp) {
    downloadPath("", "library.zip", req, resp);
  });
  self.app.get(/^\/library\/(.*)\/$/, self.hasPermRead, function(req, resp){
    var reqDir = req.params[0];
    var zipName = safePath(reqDir.replace(/\//g, " - ")) + ".zip";
    downloadPath(reqDir, zipName, req, resp);
  });
  self.app.get('/download/keys', self.hasPermRead, function(req, resp) {
    var reqKeys = Object.keys(req.query);
    var files = [];
    var commonArtistName, commonAlbumName;
    reqKeys.forEach(function(key) {
      var dbFile = self.player.libraryIndex.trackTable[key];
      if (!dbFile) return;
      files.push(path.join(musicDir, dbFile.file));
      if (commonAlbumName === undefined) commonAlbumName = dbFile.albumName || "";
      else if (commonAlbumName !== dbFile.albumName) commonAlbumName = null;
      if (commonArtistName === undefined) commonArtistName = dbFile.artistName || "";
      else if (commonArtistName !== dbFile.artistName) commonArtistName = null;
    });
    var reqZipName = commonAlbumName || commonArtistName || "songs";
    var zipName = safePath(reqZipName) + ".zip";
    sendZipOfFiles(zipName, files, req, resp);
  });

  function downloadPath(dirName, zipName, req, resp) {
    var files = [];
    var dirEntry = self.player.dirs[dirName];
    if (!dirEntry) {
      resp.statusCode = 404;
      resp.end("Not found");
      return;
    }
    sendZipOfFiles(zipName, files, req, resp);

    function addOneDir(dirEntry) {
      var baseName, relPath;
      for (baseName in dirEntry.entries) {
        relPath = path.join(dirEntry.dirName, baseName);
        var dbTrack = self.player.dbFilesByPath[relPath];
        if (dbTrack) files.push(dbTrack.file);
      }
      for (baseName in dirEntry.dirEntries) {
        relPath = path.join(dirEntry.dirName, baseName);
        var childEntry = self.player.dirs[relPath];
        if (childEntry) addOneDir(childEntry);
      }
    }
  }

  function sendZipOfFiles(zipName, files, req, resp) {
    req.on('close', cleanupEverything);

    resp.setHeader("Content-Type", "application/zip");
    resp.setHeader("Content-Disposition", "attachment; filename=" + zipName);

    var zipfile = new yazl.ZipFile();
    zipfile.on('error', function(err) {
      log.error("Error while sending zip of files:", err.stack);
      cleanupEverything();
    });

    files.forEach(function(file) {
      zipfile.addFile(file, path.relative(self.config.musicDirectory, file), {compress: false});
    });
    zipfile.end(function(finalSize) {
      resp.setHeader("Content-Length", finalSize.toString());
      zipfile.outputStream.pipe(resp);
    });

    function cleanupEverything() {
      // TODO: clean shutdown?
      // zipfile.destroy();<commit_msg>deleted commented out code<commit_after>var EventEmitter = require('events').EventEmitter;
var http = require('http');
var https = require('https');
var assert = require('assert');
var WebSocketServer = require('ws').Server;
var fs = require('fs');
var yazl = require('yazl');
var util = require('util');
var path = require('path');
var Pend = require('pend');
var express = require('express');
var osenv = require('osenv');
var plugins = [
  require('./plugins/lastfm'),
];
var Player = require('./player');
var PlayerServer = require('./player_server');
var MpdProtocol = require('./mpd_protocol');
var MpdApiServer = require('./mpd_api_server');
var WebSocketApiClient = require('./web_socket_api_client');
var leveldown = require('leveldown');
var net = require('net');
var safePath = require('./safe_path');
var MultipartForm = require('multiparty').Form;
var createGzipStatic = require('connect-static');
var serveStatic = require('serve-static');
var Cookies = require('cookies');
var log = require('./log');

module.exports = GrooveBasin;

var defaultConfig = {
  host: '0.0.0.0',
  port: 16242,
  dbPath: "groovebasin.db",
  musicDirectory: path.join(osenv.home(), "music"),
  lastFmApiKey: "<KEY>",
  lastFmApiSecret: "<KEY>",
  mpdHost: '0.0.0.0',
  mpdPort: 6600,
  acoustidAppKey: '<KEY>',
  encodeQueueDuration: 8,
  sslKey: 'certs/self-signed-key.pem',
  sslCert: 'certs/self-signed-cert.pem',
};

util.inherits(GrooveBasin, EventEmitter);
function GrooveBasin() {
  EventEmitter.call(this);

  this.app = express();
}

GrooveBasin.prototype.initConfigVar = function(name, defaultValue) {
  this.configVars.push(name);
  this[name] = defaultValue;
};

GrooveBasin.prototype.loadConfig = function(cb) {
  var self = this;
  var pathToConfig = "config.json";
  fs.readFile(pathToConfig, {encoding: 'utf8'}, function(err, contents) {
    var anythingAdded = false;
    var config;
    if (err) {
      if (err.code === 'ENOENT') {
        anythingAdded = true;
        self.config = defaultConfig;
        log.warn("No " + pathToConfig + " found; writing default.");
      } else {
        return cb(err);
      }
    } else {
      try {
        self.config = JSON.parse(contents);
      } catch (err) {
        cb(new Error("Unable to parse " + pathToConfig + ": " + err.message));
        return;
      }
    }
    // this ensures that even old files get new config values when we add them
    for (var key in defaultConfig) {
      if (self.config[key] === undefined) {
        anythingAdded = true;
        self.config[key] = defaultConfig[key];
      }
    }
    if (anythingAdded) {
      fs.writeFile(pathToConfig, JSON.stringify(self.config, null, 4), cb);
    } else {
      cb();
    }
  });
};

GrooveBasin.prototype.start = function() {
  var self = this;

  if (process.argv.indexOf('--verbose') > 0) {
    log.level = log.levels.Debug;
  } else {
    log.level = log.levels.Warn;
  }

  var pend = new Pend();
  pend.go(function(cb) {
    self.loadConfig(cb);
  });
  pend.go(function(cb) {
    var options = {
      dir: path.join(__dirname, "../public"),
      aliases: [],
    };
    createGzipStatic(options, function(err, middleware) {
      if (err) return cb(err);
      self.app.use(middleware);
      cb();
    });
  });
  pend.go(function(cb) {
    createGzipStatic({dir: path.join(__dirname, "../src/public")}, function(err, middleware) {
      if (err) return cb(err);
      self.app.use(middleware);
      cb();
    });
  });
  pend.wait(function(err) {
    if (err) {
      log.error(err.stack);
      return;
    }
    pend.go(function(cb) {
      self.db = leveldown(self.config.dbPath);
      self.db.open(cb);
    });
    pend.wait(makePlayer);
  });
  function makePlayer(err) {
    if (err) {
      log.error(err.stack);
      return;
    }
    self.httpHost = self.config.host;
    self.httpPort = self.config.port;
    if (process.argv.indexOf('--delete-all-users') > 0) {
      // this will call process.exit when done
      PlayerServer.deleteAllUsers(self.db);
      return;
    }


    self.player = new Player(self.db, self.config.musicDirectory, self.config.encodeQueueDuration);
    self.player.initialize(function(err) {
      if (err) {
        log.error("unable to initialize player:", err.stack);
        return;
      }
      log.debug("Player initialization complete.");

      var pend = new Pend();
      plugins.forEach(function(PluginClass) {
        var plugin = new PluginClass(self);
        if (plugin.initialize) pend.go(plugin.initialize.bind(plugin));
      });
      pend.wait(function(err) {
        if (err) {
          log.error("Error initializing plugin:", err.stack);
          return;
        }
        self.startServer();
      });
    });
  }

};

GrooveBasin.prototype.initializeDownload = function() {
  var self = this;
  var musicDir = self.config.musicDirectory;
  var serve = serveStatic(musicDir);
  self.app.use('/library', serveStaticMiddleware);
  self.app.get('/library/', self.hasPermRead, function(req, resp) {
    downloadPath("", "library.zip", req, resp);
  });
  self.app.get(/^\/library\/(.*)\/$/, self.hasPermRead, function(req, resp){
    var reqDir = req.params[0];
    var zipName = safePath(reqDir.replace(/\//g, " - ")) + ".zip";
    downloadPath(reqDir, zipName, req, resp);
  });
  self.app.get('/download/keys', self.hasPermRead, function(req, resp) {
    var reqKeys = Object.keys(req.query);
    var files = [];
    var commonArtistName, commonAlbumName;
    reqKeys.forEach(function(key) {
      var dbFile = self.player.libraryIndex.trackTable[key];
      if (!dbFile) return;
      files.push(path.join(musicDir, dbFile.file));
      if (commonAlbumName === undefined) commonAlbumName = dbFile.albumName || "";
      else if (commonAlbumName !== dbFile.albumName) commonAlbumName = null;
      if (commonArtistName === undefined) commonArtistName = dbFile.artistName || "";
      else if (commonArtistName !== dbFile.artistName) commonArtistName = null;
    });
    var reqZipName = commonAlbumName || commonArtistName || "songs";
    var zipName = safePath(reqZipName) + ".zip";
    sendZipOfFiles(zipName, files, req, resp);
  });

  function downloadPath(dirName, zipName, req, resp) {
    var files = [];
    var dirEntry = self.player.dirs[dirName];
    if (!dirEntry) {
      resp.statusCode = 404;
      resp.end("Not found");
      return;
    }
    sendZipOfFiles(zipName, files, req, resp);

    function addOneDir(dirEntry) {
      var baseName, relPath;
      for (baseName in dirEntry.entries) {
        relPath = path.join(dirEntry.dirName, baseName);
        var dbTrack = self.player.dbFilesByPath[relPath];
        if (dbTrack) files.push(dbTrack.file);
      }
      for (baseName in dirEntry.dirEntries) {
        relPath = path.join(dirEntry.dirName, baseName);
        var childEntry = self.player.dirs[relPath];
        if (childEntry) addOneDir(childEntry);
      }
    }
  }

  function sendZipOfFiles(zipName, files, req, resp) {
    req.on('close', cleanupEverything);

    resp.setHeader("Content-Type", "application/zip");
    resp.setHeader("Content-Disposition", "attachment; filename=" + zipName);

    var zipfile = new yazl.ZipFile();
    zipfile.on('error', function(err) {
      log.error("Error while sending zip of files:", err.stack);
      cleanupEverything();
    });

    files.forEach(function(file) {
      zipfile.addFile(file, path.relative(self.config.musicDirectory, file), {compress: false});
    });
    zipfile.end(function(finalSize) {
      resp.setHeader("Content-Length", finalSize.toString());
      zipfile.outputStream.pipe(resp);
    });

    function cleanupEverything() {
{'fraction_non_alphanumeric': 0.15790846264180466, 'fraction_numerical': 0.0033902725257530316, 'mean_word_length': 3.1311607864260704, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5479512', 'n_tokens_mistral': 5021, 'n_tokens_neox': 4894, 'n_words': 1010}