<commit_before>const config = require("./config").root();
const path = require("path");
const fs = require("fs");
const child_process = require("child_process");
const rimraf = require("rimraf");

// Convert to milliseconds:
let keepTime = config.exportKeepTime * 60 * 1000;

function exportPath(deviceId) {
  if (deviceId.search(/^[a-z0-9\-]+$/i) == -1) {
    reject(new Error("Bad deviceId"));
  }
  return path.join(config.exportBase, "export---" + deviceId);
}

function removeExportPath(dir) {
  return new Promise((resolve, reject) => {
    rimraf(dir, (error) => {
      if (error && error.code != "ENOENT") {
        reject(error);
      } else {
        resolve();
      }
    });
  });
}

function launchWget(deviceId) {
  return new Promise((resolve, reject) => {
    let dir = exportPath(deviceId);
    let script = path.join(__dirname, "export-shots.sh");
    let start = Date.now();
    let subprocess = child_process.spawn(
      script,
      {
        env: {
          BASE_DIR: dir,
          AUTH: require("./dbschema").getTextKeys()[0],
          DEVICE_ID: deviceId,
          PORT: config.port,
          CONTENT_PORT: config.contentPort,
        }
      }
    );
    subprocess.on("close", function (code) {
      console.info("Exported to", dir, "in", (Date.now() - start) + "ms", "exit code:", code);
    });
    subprocess.on("error", function (e) {
      console.warn("Error launching wget:", e);
    });
    resolve();
    // FIXME: I don't think an exception here will reject
    // Also maybe this is all synchronous
  });
}

function getWgetStatus(deviceId) {
  return new Promise((resolve, reject) => {
    let dir = exportPath(deviceId);
    let result = {errors: []};
    let steps = 3;
    function done() {
      if (steps <= 0) {
        resolve(result);
      }
    }
    fs.readFile(path.join(dir, "wget-process.pid"), {encoding: "UTF-8"}, (error, data) => {
      if (error && error.code == "ENOENT") {
        // File doesn't exist
        result.running = false;
      } else if (error) {
        result.errors.push(["Error loading process PID:", error]);
      } else {
        result.running = true;
      }
      steps--;
      done();
    });
    fs.readFile(path.join(dir, "script-exit-code.txt"), {encoding: "UTF-8"}, (error, data) => {
      if (error && error.code == "ENOENT") {
        // Do nothing
      } else if (error) {
        result.errors.push(["Error loading exit code:", error]);
      } else {
        let code = parseInt(data, 10);
        if (code) {
          result.success = false;
        } else {
          result.success = true;
        }
      }
      steps--;
      done();
    });
    fs.stat(path.join(dir, "pageshot-export.zip"), (error, stat) => {
      if (error && error.code == "ENOENT") {
        result.zip = false;
      } else if (error) {
        result.errors.push(["Error checking zip file:", error]);
      } else {
        result.zip = {
          size: stat.size,
          mtime: stat.mtime.getTime(),
          keepTime: keepTime
        };
      }
      steps--;
      done();
    });
  });
}

exports.cleanExports = function () {
  let start = Date.now();
  fs.readdir(config.exportBase, (error, files) => {
    if (error && error.code == "ENOENT") {
      console.info("EXPORT: Could not clean exports, directory", config.exportBase, "does not exist");
      return;
    }
    if (error) {
      console.warn("EXPORT: Error listing", config.exportBase, ":", error);
      return;
    }
    let toCheck = files.filter((file) => file.startsWith("export--"));
    let count = toCheck.length;
    let deletedCount = 0;
    let errorCount = 0;
    function oneDone(deleted, errored) {
      count--;
      if (deleted) {
        deletedCount++;
      }
      if (errored) {
        errorCount++;
      }
      if (count <= 0) {
        console.info("EXPORT: finished cleanup run;", toCheck.length, "checked;", deletedCount, "deleted;", errorCount, "had errors; total time:", (Date.now() - start) + "ms");
      }
    }
    for (let dir of toCheck) {
      let fullDir = path.join(config.exportBase, dir);
      let file = path.join(fullDir, "pageshot-export.zip");
      fs.stat(file, (error, stat) => {
        if (error && error.code == "ENOENT") {
          fs.stat(fullDir, (error, stat) => {
            if (error && error.code == "ENOENT") {
              console.info("EXPORT: directory", fullDir, "disappeared during cleanup");
              oneDone(false, false);
              return;
            } else if (error) {
              console.warn("EXPORT: could not stat directory", fullDir, ":", error);
              oneDone(false, true);
              return;
            }
            if (stat.mtime.getTime() + keepTime < Date.now()) {
              rimraf(fullDir, (error) => {
                if (error) {
                  oneDone(false, true);
                  console.warn("EXPORT: failed to remove unfinished export", fullDir, ":", error);
                } else {
                  oneDone(true, false);
                  console.info("EXPORT: removed unfinished export", fullDir);
                }
              });
            }
          });
          return;
        } else if (error) {
          oneDone(false, true);
          console.warn("EXPORT: Error doing stat on", file, ":", error);
          return;
        }
        if (stat.mtime.getTime() + keepTime < Date.now()) {
          rimraf(fullDir, (error) => {
            if (error) {
              oneDone(false, true);
              console.warn("EXPORT: failed to remove outdated export", fullDir, ":", error);
            } else {
              oneDone(true, false);
              console.info("EXPORT: removed outdated export:", fullDir);
            }
          });
        } else {
          oneDone(false, false);
        }
      });
    }
  });
};

exports.setup = function (app) {

  app.get("/export", function (req, res) {
    if (! req.deviceId) {
      return require("./server").simpleResponse(res, "You must have the addon installed to export your shots", 403);
    }
    require("./views/export").render(req, res);
  });

  app.post("/export", function (req, res) {
    if (! req.deviceId) {
      return require("./server").simpleResponse(res, "You must have the addon installed to export your shots", 403);
    }
    launchWget(req.deviceId).then(() => {
      res.redirect("/export/status?started=" + encodeURIComponent(Date.now()));
    }).catch((e) => {
      require("./server").errorResponse(res, "Failed to launch export", e);
    });
  });

  app.get("/export/status", function (req, res) {
    if (! req.deviceId) {
      return require("./server").simpleResponse(res, "You must have the addon installed to export your shots", 403);
    }
    let started = parseInt(req.query.started, 10);
    getWgetStatus(req.deviceId).then((status) => {
      if ((! status.running) && ((! started) || (started + 10000 < Date.now()))) {
        res.redirect("/export");
        return;
      }
      if (started && status.zip) {
        res.redirect("/export/status");
        return;
      }
      req.wgetStatus = status;
      req.keepTime = keepTime;
      require("./views/export").renderStatus(req, res);
    }).catch((e) => {
      require("./server").errorResponse(res, "Failed to get status", e);
    });
  });

  app.get("/export/download/pageshot-export.zip", function (req, res) {
    if (! req.deviceId) {
      return require("./server").simpleResponse(res, "You must have the addon installed to download your shots", 403);
    }
    let dir = exportPath(req.deviceId);
    let fn = path.join(dir, "pageshot-export.zip");
    res.sendFile(fn);
  });

  app.post("/export/remove", function (req, res) {
    if (! req.deviceId) {
      return require("./server").simpleResponse(res, "You must have the addon installed", 403);
    }
    let dir = exportPath(req.deviceId);
    removeExportPath(dir).then(() => {
      res.redirect("/export?deleted");
    }).catch((e) => {
      require("./server").errorResponse(res, "Failed to remove directory", e);
    });
  });

};
<commit_msg>Get rid of uses of simpleResponse<commit_after>const config = require("./config").root();
const path = require("path");
const fs = require("fs");
const child_process = require("child_process");
const rimraf = require("rimraf");

// Convert to milliseconds:
let keepTime = config.exportKeepTime * 60 * 1000;

function exportPath(deviceId) {
  if (deviceId.search(/^[a-z0-9\-]+$/i) == -1) {
    reject(new Error("Bad deviceId"));
  }
  return path.join(config.exportBase, "export---" + deviceId);
}

function removeExportPath(dir) {
  return new Promise((resolve, reject) => {
    rimraf(dir, (error) => {
      if (error && error.code != "ENOENT") {
        reject(error);
      } else {
        resolve();
      }
    });
  });
}

function launchWget(deviceId) {
  return new Promise((resolve, reject) => {
    let dir = exportPath(deviceId);
    let script = path.join(__dirname, "export-shots.sh");
    let start = Date.now();
    let subprocess = child_process.spawn(
      script,
      {
        env: {
          BASE_DIR: dir,
          AUTH: require("./dbschema").getTextKeys()[0],
          DEVICE_ID: deviceId,
          PORT: config.port,
          CONTENT_PORT: config.contentPort,
        }
      }
    );
    subprocess.on("close", function (code) {
      console.info("Exported to", dir, "in", (Date.now() - start) + "ms", "exit code:", code);
    });
    subprocess.on("error", function (e) {
      console.warn("Error launching wget:", e);
    });
    resolve();
    // FIXME: I don't think an exception here will reject
    // Also maybe this is all synchronous
  });
}

function getWgetStatus(deviceId) {
  return new Promise((resolve, reject) => {
    let dir = exportPath(deviceId);
    let result = {errors: []};
    let steps = 3;
    function done() {
      if (steps <= 0) {
        resolve(result);
      }
    }
    fs.readFile(path.join(dir, "wget-process.pid"), {encoding: "UTF-8"}, (error, data) => {
      if (error && error.code == "ENOENT") {
        // File doesn't exist
        result.running = false;
      } else if (error) {
        result.errors.push(["Error loading process PID:", error]);
      } else {
        result.running = true;
      }
      steps--;
      done();
    });
    fs.readFile(path.join(dir, "script-exit-code.txt"), {encoding: "UTF-8"}, (error, data) => {
      if (error && error.code == "ENOENT") {
        // Do nothing
      } else if (error) {
        result.errors.push(["Error loading exit code:", error]);
      } else {
        let code = parseInt(data, 10);
        if (code) {
          result.success = false;
        } else {
          result.success = true;
        }
      }
      steps--;
      done();
    });
    fs.stat(path.join(dir, "pageshot-export.zip"), (error, stat) => {
      if (error && error.code == "ENOENT") {
        result.zip = false;
      } else if (error) {
        result.errors.push(["Error checking zip file:", error]);
      } else {
        result.zip = {
          size: stat.size,
          mtime: stat.mtime.getTime(),
          keepTime: keepTime
        };
      }
      steps--;
      done();
    });
  });
}

exports.cleanExports = function () {
  let start = Date.now();
  fs.readdir(config.exportBase, (error, files) => {
    if (error && error.code == "ENOENT") {
      console.info("EXPORT: Could not clean exports, directory", config.exportBase, "does not exist");
      return;
    }
    if (error) {
      console.warn("EXPORT: Error listing", config.exportBase, ":", error);
      return;
    }
    let toCheck = files.filter((file) => file.startsWith("export--"));
    let count = toCheck.length;
    let deletedCount = 0;
    let errorCount = 0;
    function oneDone(deleted, errored) {
      count--;
      if (deleted) {
        deletedCount++;
      }
      if (errored) {
        errorCount++;
      }
      if (count <= 0) {
        console.info("EXPORT: finished cleanup run;", toCheck.length, "checked;", deletedCount, "deleted;", errorCount, "had errors; total time:", (Date.now() - start) + "ms");
      }
    }
    for (let dir of toCheck) {
      let fullDir = path.join(config.exportBase, dir);
      let file = path.join(fullDir, "pageshot-export.zip");
      fs.stat(file, (error, stat) => {
        if (error && error.code == "ENOENT") {
          fs.stat(fullDir, (error, stat) => {
            if (error && error.code == "ENOENT") {
              console.info("EXPORT: directory", fullDir, "disappeared during cleanup");
              oneDone(false, false);
              return;
            } else if (error) {
              console.warn("EXPORT: could not stat directory", fullDir, ":", error);
              oneDone(false, true);
              return;
            }
            if (stat.mtime.getTime() + keepTime < Date.now()) {
              rimraf(fullDir, (error) => {
                if (error) {
                  oneDone(false, true);
                  console.warn("EXPORT: failed to remove unfinished export", fullDir, ":", error);
                } else {
                  oneDone(true, false);
                  console.info("EXPORT: removed unfinished export", fullDir);
                }
              });
            }
          });
          return;
        } else if (error) {
          oneDone(false, true);
          console.warn("EXPORT: Error doing stat on", file, ":", error);
          return;
        }
        if (stat.mtime.getTime() + keepTime < Date.now()) {
          rimraf(fullDir, (error) => {
            if (error) {
              oneDone(false, true);
              console.warn("EXPORT: failed to remove outdated export", fullDir, ":", error);
            } else {
              oneDone(true, false);
              console.info("EXPORT: removed outdated export:", fullDir);
            }
          });
        } else {
          oneDone(false, false);
        }
      });
    }
  });
};

exports.setup = function (app) {

  app.get("/export", function (req, res) {
    if (! req.deviceId) {
      res.set("Content-Type", "text/plain");
      res.status(403).send("You must have the addon installed to export your shots");
    }
    require("./views/export").render(req, res);
  });

  app.post("/export", function (req, res) {
    if (! req.deviceId) {
      res.set("Content-Type", "text/plain");
      res.status(403).send("You must have the addon installed to export your shots");
    }
    launchWget(req.deviceId).then(() => {
      res.redirect("/export/status?started=" + encodeURIComponent(Date.now()));
    }).catch((e) => {
      require("./server").errorResponse(res, "Failed to launch export", e);
    });
  });

  app.get("/export/status", function (req, res) {
    if (! req.deviceId) {
      res.set("Content-Type", "text/plain");
      res.status(403).send("You must have the addon installed to export your shots");
    }
    let started = parseInt(req.query.started, 10);
    getWgetStatus(req.deviceId).then((status) => {
      if ((! status.running) && ((! started) || (started + 10000 < Date.now()))) {
        res.redirect("/export");
        return;
      }
      if (started && status.zip) {
        res.redirect("/export/status");
        return;
      }
      req.wgetStatus = status;
      req.keepTime = keepTime;
      require("./views/export").renderStatus(req, res);
    }).catch((e) => {
      require("./server").errorResponse(res, "Failed to get status", e);
    });
  });

  app.get("/export/download/pageshot-export.zip", function (req, res) {
    if (! req.deviceId) {
      res.set("Content-Type", "text/plain");
      res.status(403).send("You must have the addon installed to download your shots");
    }
    let dir = exportPath(req.deviceId);
    let fn = path.join(dir, "pageshot-export.zip");
    res.sendFile(fn);
  });

  app.post("/export/remove", function (req, res) {
    if (! req.deviceId) {
      res.set("Content-Type", "text/plain");
      res.status(403).send("You must have the addon installed");
    }
    let dir = exportPath(req.deviceId);
    removeExportPath(dir).then(() => {
      res.redirect("/export?deleted");
    }).catch((e) => {
      require("./server").errorResponse(res, "Failed to remove directory", e);
    });
  });

};
<|endoftext|>


<commit_before>'use strict';

const error = require('./error');
const logger = require('./logger');
const Group = require('./permissions/group').Group;
const Collection = require('./collection').Collection;

const r = require('rethinkdb');

// These are exported for use by the CLI. They accept 'R' as a parameter because of
// https://github.com/rethinkdb/rethinkdb/issues/3263
const create_collection_reql = (R, internal_db, user_db, collection) => {
  const do_create = (table) =>
    R.db(user_db).tableCreate(table).do(() =>
      R.db(internal_db)
        .table('collections')
        .get(collection)
        .replace((old_row) =>
          R.branch(old_row.eq(null),
                   { id: collection, table },
                   old_row),
          { returnChanges: 'always' })('changes')(0)
        .do((res) =>
          R.branch(R.or(res.hasFields('error'),
                        res('new_val')('table').ne(table)),
                   R.db(user_db).tableDrop(table).do(() => res),
                   res)));

  return R.uuid().split('-')(-1)
           .do((id) => R.expr(collection).add('_').add(id)).do((table) =>
             R.db(internal_db).table('collections').get(collection).do((row) =>
               R.branch(row.eq(null),
                        do_create(table),
                        { old_val: row, new_val: row })));
};
const initialize_metadata_reql = (R, internal_db, user_db) => {
  return R.expr([ user_db, internal_db ])
          .forEach((db) => R.branch(R.dbList().contains(db), [], R.dbCreate(db)))
          .do(() =>
            R.expr([ 'collections', 'users_auth', 'users', 'groups' ])
             .forEach((table) => R.branch(R.db(internal_db).tableList().contains(table),
                                          [], R.db(internal_db).tableCreate(table))));
};

class Metadata {
  constructor(project_name,
              conn,
              clients,
              auto_create_collection,
              auto_create_index) {
    this._db = project_name;
    this._internal_db = `${this._db}_internal`;
    this._conn = conn;
    this._clients = clients;
    this._auto_create_collection = auto_create_collection;
    this._auto_create_index = auto_create_index;
    this._closed = false;
    this._ready = false;
    this._collections = new Map();
    this._groups = new Map();
    this._collection_feed = null;
    this._group_feed = null;
    this._index_feed = null;

    const make_feeds = () => {
      logger.debug('running metadata sync');
      const groups_ready =
        r.db(this._internal_db)
          .table('groups')
          .changes({ squash: true,
                     includeInitial: true,
                     includeStates: true,
                     includeTypes: true })
          .run(this._conn).then((res) => {
            if (this._closed) {
              res.close();
              throw new Error('This metadata instance has been closed.');
            }
            return new Promise((resolve, reject) => {
              this._group_feed = res;
              this._group_feed.eachAsync((change) => {
                if (change.type === 'state') {
                  if (change.state === 'ready') {
                    logger.info('Groups metadata synced.');
                    resolve();
                  }
                } else if (change.type === 'initial' ||
                           change.type === 'add' ||
                           change.type === 'change') {
                  const group = new Group(change.new_val);
                  this._groups.set(group.name, group);
                  this._clients.forEach((c) => c.group_changed(group.name));
                } else if (change.type === 'uninitial' ||
                           change.type === 'remove') {
                  const group = this._groups.delete(change.old_val.id);
                  if (group) {
                    this._clients.forEach((c) => c.group_changed(group.name));
                  }
                }
              }).catch(reject);
            });
          });
      const collections_ready =
        r.db(this._internal_db)
          .table('collections')
          .changes({ squash: true,
                     includeInitial: true,
                     includeStates: true,
                     includeTypes: true })
          .run(this._conn).then((res) => {
            if (this._closed) {
              res.close();
              throw new Error('This metadata instance has been closed.');
            }
            return new Promise((resolve, reject) => {
              this._collection_feed = res;
              this._collection_feed.eachAsync((change) => {
                if (change.type === 'state') {
                  if (change.state === 'ready') {
                    logger.info('Collections metadata synced.');
                    resolve();
                  }
                } else if (change.type === 'initial' ||
                           change.type === 'add' ||
                           change.type === 'change') {
                  // Ignore special collections
                  if (change.new_val.id !== 'users') {
                    const collection = new Collection(change.new_val, this._db, this._conn);
                    this._collections.set(collection.name, collection);
                  }
                } else if (change.type === 'uninitial' ||
                           change.type === 'remove') {
                  // Ignore special collections
                  if (change.old_val.id !== 'users') {
                    this._collections.delete(change.old_val.id);
                  }
                }
              }).catch(reject);
            });
          });
      const indexes_ready =
        r.db('rethinkdb')
          .table('table_config')
          .filter({ db: this._db })
          .pluck('name', 'indexes')
          .changes({ squash: true,
                     includeInitial: true,
                     includeStates: true,
                     includeTypes: true })
          .run(this._conn).then((res) => {
            if (this._closed) {
              res.close();
              throw new Error('This metadata instance has been closed.');
            }
            return new Promise((resolve, reject) => {
              this._index_feed = res;
              this._index_feed.eachAsync((change) => {
                if (change.type === 'state') {
                  if (change.state === 'ready') {
                    logger.info('Index metadata synced.');
                    resolve();
                  }
                } else if (change.type === 'initial' ||
                           change.type === 'add' ||
                           change.type === 'change') {
                  const table = change.new_val.name;
                  this._collections.forEach((c) => {
                    if (c.table === table) {
                      c.update_indexes(change.new_val.indexes, this._conn);
                    }
                  });
                }
              }).catch(reject);
            });
          })
      return Promise.all([ groups_ready, collections_ready, indexes_ready ]);
    };

    if (this._auto_create_collection) {
      this._ready_promise =
        initialize_metadata_reql(r, this._internal_db, this._db).run(this._conn).then(make_feeds);
    } else {
      this._ready_promise =
        r.expr([ this._db, this._internal_db ])
         .concatMap((db) => r.branch(r.dbList().contains(db), [], [db]))
         .run(this._conn).then((missing_dbs) => {
           logger.debug("checking for internal db/tables");
           if (missing_dbs.length > 0) {
             let err_msg;
             if (missing_dbs.length == 1) {
               err_msg = "The database " + missing_dbs[0] + " doesn't exist. ";
             } else {
               err_msg = "The databases " + missing_dbs[0] + " and " + missing_dbs[1] + " don't exist. ";
             }
             throw new Error(err_msg + "Run `hz set-schema` to initialize the database, "
                  + "then start the Horizon server.");
           } else {
             return make_feeds();
           }
         });
    }

    this._ready_promise = this._ready_promise.then(() => {
      logger.debug('adding admin user');
      // Ensure that the admin user and group exists
      return Promise.all([
        r.db(this._internal_db).table('users').get('admin')
          .replace((old_row) =>
            r.branch(old_row.eq(null),
                     { id: 'admin', groups: [ 'admin' ] },
                     old_row),
            { returnChanges: 'always' })('changes')(0)
          .do((res) =>
            r.branch(res('new_val').eq(null),
                     r.error(res('error')),
                     res('new_val'))).run(this._conn),
        r.db(this._internal_db).table('groups').get('admin')
          .replace((old_row) =>
            r.branch(old_row.eq(null),
                     { id: 'admin', rules: { 'carte_blanche': { 'template': 'any()' } } },
                     old_row),
            { returnChanges: 'always' })('changes')(0)
          .do((res) =>
            r.branch(res('new_val').eq(null),
                     r.error(res('error')),
                     res('new_val'))).run(this._conn)
      ]);
    }).then(() => {
      logger.debug('redirecting users table');
      // Redirect the 'users' table to the one in the internal db
      this._collections.set('users', new Collection({ id: 'users', table: 'users' },
                                                    this._internal_db, this._conn));
    }).then(() => {
      logger.debug('metadata sync complete');
      this._ready = true;
      return this;
    })

    this._ready_promise.catch(() => {
      this.close();
      throw e;
    });
  }

  close() {
    this._closed = true;
    this._ready = false;
    if (this._group_feed) {
      this._group_feed.close();
    }
    if (this._collection_feed) {
      this._collection_feed.close();
    }
    if (this._index_feed) {
      this._index_feed.close();
    }
  }

  is_ready() {
    return this._ready;
  }

  ready() {
    return this._ready_promise;
  }

  collection(name) {
    const res = this._collections.get(name);
    if (res === undefined) { throw new error.CollectionMissing(name); }
    if (res.promise) { throw new error.CollectionNotReady(res); }
    return res;
  }

  handle_error(err, done) {
    logger.debug(`Handling error: ${err.message}`);
    try {
      if (this._auto_create_collection) {
        if (err instanceof error.CollectionMissing) {
          logger.warn(`Auto-creating collection (dev mode): ${err.name}`);
          return this.create_collection(err.name, done);
        } else if (err instanceof error.CollectionNotReady) {
          return err.collection.on_ready(done);
        }
      }
      if (this._auto_create_index) {
        if (err instanceof error.IndexMissing) {
          logger.warn(`Auto-creating index on collection "${err.collection.name}" ` +
                      `(dev mode): ${JSON.stringify(err.fields)}`);
          return err.collection.create_index(err.fields, this._conn, done);
        } else if (err instanceof error.IndexNotReady) {
          return err.index.on_ready(done);
        }
      }
      done(err);
    } catch (new_err) {
      logger.debug(`Error when handling error: ${new_err.message}`);
      done(new_err);
    }
  }

  create_collection(name, done) {
    error.check(this._collections.get(name) === undefined,
                `Collection "${name}" already exists.`);

    create_collection_reql(r, this._internal_db, this._db, name)
      .run(this._conn)
      .then((res) => {
        error.check(!res.error, `Collection creation failed (dev mode): "${name}", ${res.error}`);
        logger.warn(`Collection created (dev mode): "${name}"`);
        this._collections.set(name, new Collection(res.new_val, this._db, this._conn));
        done();
      }).catch(done);
  }

  get_user_feed(id, done) {
    r.db(this._internal_db)
      .table('users')
      .get(id)
      .changes({ includeInitial: true, squash: true })
      .run(this._conn, done);
  }

  get_group(group_name) {
    return this._groups.get(group_name);
  }

  connection() {
    return this._conn;
  }
}

module.exports = { Metadata, create_collection_reql, initialize_metadata_reql };
<commit_msg>adding missing semicolon<commit_after>'use strict';

const error = require('./error');
const logger = require('./logger');
const Group = require('./permissions/group').Group;
const Collection = require('./collection').Collection;

const r = require('rethinkdb');

// These are exported for use by the CLI. They accept 'R' as a parameter because of
// https://github.com/rethinkdb/rethinkdb/issues/3263
const create_collection_reql = (R, internal_db, user_db, collection) => {
  const do_create = (table) =>
    R.db(user_db).tableCreate(table).do(() =>
      R.db(internal_db)
        .table('collections')
        .get(collection)
        .replace((old_row) =>
          R.branch(old_row.eq(null),
                   { id: collection, table },
                   old_row),
          { returnChanges: 'always' })('changes')(0)
        .do((res) =>
          R.branch(R.or(res.hasFields('error'),
                        res('new_val')('table').ne(table)),
                   R.db(user_db).tableDrop(table).do(() => res),
                   res)));

  return R.uuid().split('-')(-1)
           .do((id) => R.expr(collection).add('_').add(id)).do((table) =>
             R.db(internal_db).table('collections').get(collection).do((row) =>
               R.branch(row.eq(null),
                        do_create(table),
                        { old_val: row, new_val: row })));
};
const initialize_metadata_reql = (R, internal_db, user_db) => {
  return R.expr([ user_db, internal_db ])
          .forEach((db) => R.branch(R.dbList().contains(db), [], R.dbCreate(db)))
          .do(() =>
            R.expr([ 'collections', 'users_auth', 'users', 'groups' ])
             .forEach((table) => R.branch(R.db(internal_db).tableList().contains(table),
                                          [], R.db(internal_db).tableCreate(table))));
};

class Metadata {
  constructor(project_name,
              conn,
              clients,
              auto_create_collection,
              auto_create_index) {
    this._db = project_name;
    this._internal_db = `${this._db}_internal`;
    this._conn = conn;
    this._clients = clients;
    this._auto_create_collection = auto_create_collection;
    this._auto_create_index = auto_create_index;
    this._closed = false;
    this._ready = false;
    this._collections = new Map();
    this._groups = new Map();
    this._collection_feed = null;
    this._group_feed = null;
    this._index_feed = null;

    const make_feeds = () => {
      logger.debug('running metadata sync');
      const groups_ready =
        r.db(this._internal_db)
          .table('groups')
          .changes({ squash: true,
                     includeInitial: true,
                     includeStates: true,
                     includeTypes: true })
          .run(this._conn).then((res) => {
            if (this._closed) {
              res.close();
              throw new Error('This metadata instance has been closed.');
            }
            return new Promise((resolve, reject) => {
              this._group_feed = res;
              this._group_feed.eachAsync((change) => {
                if (change.type === 'state') {
                  if (change.state === 'ready') {
                    logger.info('Groups metadata synced.');
                    resolve();
                  }
                } else if (change.type === 'initial' ||
                           change.type === 'add' ||
                           change.type === 'change') {
                  const group = new Group(change.new_val);
                  this._groups.set(group.name, group);
                  this._clients.forEach((c) => c.group_changed(group.name));
                } else if (change.type === 'uninitial' ||
                           change.type === 'remove') {
                  const group = this._groups.delete(change.old_val.id);
                  if (group) {
                    this._clients.forEach((c) => c.group_changed(group.name));
                  }
                }
              }).catch(reject);
            });
          });
      const collections_ready =
        r.db(this._internal_db)
          .table('collections')
          .changes({ squash: true,
                     includeInitial: true,
                     includeStates: true,
                     includeTypes: true })
          .run(this._conn).then((res) => {
            if (this._closed) {
              res.close();
              throw new Error('This metadata instance has been closed.');
            }
            return new Promise((resolve, reject) => {
              this._collection_feed = res;
              this._collection_feed.eachAsync((change) => {
                if (change.type === 'state') {
                  if (change.state === 'ready') {
                    logger.info('Collections metadata synced.');
                    resolve();
                  }
                } else if (change.type === 'initial' ||
                           change.type === 'add' ||
                           change.type === 'change') {
                  // Ignore special collections
                  if (change.new_val.id !== 'users') {
                    const collection = new Collection(change.new_val, this._db, this._conn);
                    this._collections.set(collection.name, collection);
                  }
                } else if (change.type === 'uninitial' ||
                           change.type === 'remove') {
                  // Ignore special collections
                  if (change.old_val.id !== 'users') {
                    this._collections.delete(change.old_val.id);
                  }
                }
              }).catch(reject);
            });
          });
      const indexes_ready =
        r.db('rethinkdb')
          .table('table_config')
          .filter({ db: this._db })
          .pluck('name', 'indexes')
          .changes({ squash: true,
                     includeInitial: true,
                     includeStates: true,
                     includeTypes: true })
          .run(this._conn).then((res) => {
            if (this._closed) {
              res.close();
              throw new Error('This metadata instance has been closed.');
            }
            return new Promise((resolve, reject) => {
              this._index_feed = res;
              this._index_feed.eachAsync((change) => {
                if (change.type === 'state') {
                  if (change.state === 'ready') {
                    logger.info('Index metadata synced.');
                    resolve();
                  }
                } else if (change.type === 'initial' ||
                           change.type === 'add' ||
                           change.type === 'change') {
                  const table = change.new_val.name;
                  this._collections.forEach((c) => {
                    if (c.table === table) {
                      c.update_indexes(change.new_val.indexes, this._conn);
                    }
                  });
                }
              }).catch(reject);
            });
          })
      return Promise.all([ groups_ready, collections_ready, indexes_ready ]);
    };

    if (this._auto_create_collection) {
      this._ready_promise =
        initialize_metadata_reql(r, this._internal_db, this._db).run(this._conn).then(make_feeds);
    } else {
      this._ready_promise =
        r.expr([ this._db, this._internal_db ])
         .concatMap((db) => r.branch(r.dbList().contains(db), [], [db]))
         .run(this._conn).then((missing_dbs) => {
           logger.debug("checking for internal db/tables");
           if (missing_dbs.length > 0) {
             let err_msg;
             if (missing_dbs.length == 1) {
               err_msg = "The database " + missing_dbs[0] + " doesn't exist. ";
             } else {
               err_msg = "The databases " + missing_dbs[0] + " and " + missing_dbs[1] + " don't exist. ";
             }
             throw new Error(err_msg + "Run `hz set-schema` to initialize the database, "
                  + "then start the Horizon server.");
           } else {
             return make_feeds();
           }
         });
    }

    this._ready_promise = this._ready_promise.then(() => {
      logger.debug('adding admin user');
      // Ensure that the admin user and group exists
      return Promise.all([
        r.db(this._internal_db).table('users').get('admin')
          .replace((old_row) =>
            r.branch(old_row.eq(null),
                     { id: 'admin', groups: [ 'admin' ] },
                     old_row),
            { returnChanges: 'always' })('changes')(0)
          .do((res) =>
            r.branch(res('new_val').eq(null),
                     r.error(res('error')),
                     res('new_val'))).run(this._conn),
        r.db(this._internal_db).table('groups').get('admin')
          .replace((old_row) =>
            r.branch(old_row.eq(null),
                     { id: 'admin', rules: { 'carte_blanche': { 'template': 'any()' } } },
                     old_row),
            { returnChanges: 'always' })('changes')(0)
          .do((res) =>
            r.branch(res('new_val').eq(null),
                     r.error(res('error')),
                     res('new_val'))).run(this._conn)
      ]);
    }).then(() => {
      logger.debug('redirecting users table');
      // Redirect the 'users' table to the one in the internal db
      this._collections.set('users', new Collection({ id: 'users', table: 'users' },
                                                    this._internal_db, this._conn));
    }).then(() => {
      logger.debug('metadata sync complete');
      this._ready = true;
      return this;
    });

    this._ready_promise.catch(() => {
      this.close();
      throw e;
    });
  }

  close() {
    this._closed = true;
    this._ready = false;
    if (this._group_feed) {
      this._group_feed.close();
    }
    if (this._collection_feed) {
      this._collection_feed.close();
    }
    if (this._index_feed) {
      this._index_feed.close();
    }
  }

  is_ready() {
    return this._ready;
  }

  ready() {
    return this._ready_promise;
  }

  collection(name) {
    const res = this._collections.get(name);
    if (res === undefined) { throw new error.CollectionMissing(name); }
    if (res.promise) { throw new error.CollectionNotReady(res); }
    return res;
  }

  handle_error(err, done) {
    logger.debug(`Handling error: ${err.message}`);
    try {
      if (this._auto_create_collection) {
        if (err instanceof error.CollectionMissing) {
          logger.warn(`Auto-creating collection (dev mode): ${err.name}`);
          return this.create_collection(err.name, done);
        } else if (err instanceof error.CollectionNotReady) {
          return err.collection.on_ready(done);
        }
      }
      if (this._auto_create_index) {
        if (err instanceof error.IndexMissing) {
          logger.warn(`Auto-creating index on collection "${err.collection.name}" ` +
                      `(dev mode): ${JSON.stringify(err.fields)}`);
          return err.collection.create_index(err.fields, this._conn, done);
        } else if (err instanceof error.IndexNotReady) {
          return err.index.on_ready(done);
        }
      }
      done(err);
    } catch (new_err) {
      logger.debug(`Error when handling error: ${new_err.message}`);
      done(new_err);
    }
  }

  create_collection(name, done) {
    error.check(this._collections.get(name) === undefined,
                `Collection "${name}" already exists.`);

    create_collection_reql(r, this._internal_db, this._db, name)
      .run(this._conn)
      .then((res) => {
        error.check(!res.error, `Collection creation failed (dev mode): "${name}", ${res.error}`);
        logger.warn(`Collection created (dev mode): "${name}"`);
        this._collections.set(name, new Collection(res.new_val, this._db, this._conn));
        done();
      }).catch(done);
  }

  get_user_feed(id, done) {
    r.db(this._internal_db)
      .table('users')
      .get(id)
      .changes({ includeInitial: true, squash: true })
      .run(this._conn, done);
  }

  get_group(group_name) {
    return this._groups.get(group_name);
  }

  connection() {
    return this._conn;
  }
}

module.exports = { Metadata, create_collection_reql, initialize_metadata_reql };
<|endoftext|>


<commit_before>'use strict';

// send stdout to stderr, but in the end send the data to stdout
const log = console.log;
console.log = console.error;

const storageTools = require('./lib/storage-tools');

// normal logging goes to stderr

storageTools.dump()
  .then((data) => {
    console.error('storage-dump done');
    log(JSON.stringify(data, null, 2));
    process.exit();
  })
  .catch((e) => console.error('error dumping: ', e && e.stack || e));
<commit_msg>:art: async await for storage dump<commit_after>'use strict';

// send stdout to stderr, but in the end send the data to stdout
const log = console.log;
console.log = console.error;

const storageTools = require('./lib/storage-tools');

// normal logging goes to stderr
const storageDump = () => {
  try {
    const thing = await storageTools.dump();
    console.log('storage-dump done');
    log(JSON.stringify(thing, null, 2));
    process.exit();
  } catch (e) {
    console.error('error dumping: ', e && e.stack || e);
    throw e;
  }
}

storageDump();<|endoftext|>


<commit_before>/**
 * @file post.js
 * @author mengke01
 * @date
 * @description
 *
 * post 表
 *
 * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
 */

define(
    function(require) {
        var table = require('./table');
        var struct = require('./struct');
        var string = require('../util/string');
        var postName = require('../enum/postName');

        var Posthead = table.create(
            'posthead',
            [
                ['italicAngle', struct.Fixed],
                ['postoints', struct.Uint16], // 不知道干嘛用，文档里面没有
                ['underlinePosition', struct.Int16],
                ['underlineThickness', struct.Int16],
                ['isFixedPitch', struct.Uint32],
                ['minMemType42', struct.Uint32],
                ['maxMemType42', struct.Uint32],
                ['minMemType1', struct.Uint32],
                ['maxMemType1', struct.Uint32]
            ]
        );

        var post = table.create(
            'post',
            [
            ],
            {
                read: function(reader, ttf) {
                    var tbl = null;
                    var format = reader.readFixed(this.offset);

                    // format2
                    if(format == 2) {

                        // 读取表头
                        tbl = new Posthead(reader.offset).read(reader, ttf);
                        tbl.format = format;

                        var numberOfGlyphs = ttf.maxp.numGlyphs;

                        var glyphNameIndex = [];
                        for(var i = 0; i < numberOfGlyphs; ++i) {
                            glyphNameIndex.push(reader.readUint16());
                        }

                        var pascalStringOffset = reader.offset;
                        var pascalStringLength = ttf.tables.post.length - (pascalStringOffset - this.offset);
                        var pascalStringBytes = reader.readBytes(reader.offset, pascalStringLength);

                        tbl.glyphNameIndex = glyphNameIndex;
                        tbl.names = string.readPascalString(pascalStringBytes);
                    }
                    else {
                        tbl = {
                            format: format
                        };
                    }

                    return tbl;
                },
                write: function(writer, ttf) {

                    var numberOfGlyphs = ttf.glyf.length;
                    var post = ttf.post || {};

                    // write header
                    writer.writeFixed(2); // format
                    writer.writeFixed(post.italicAngle || 0); // italicAngle
                    writer.writeUint16(post.postoints || 0); // postoints
                    writer.writeInt16(post.underlinePosition || 0); // underlinePosition
                    writer.writeInt16(post.underlineThickness || 0); // underlineThickness
                    writer.writeUint32(post.isFixedPitch || 0); // isFixedPitch
                    writer.writeUint32(post.minMemType42 || 0); // minMemType42
                    writer.writeUint32(post.maxMemType42 || 0); // maxMemType42
                    writer.writeUint32(post.minMemType1 || 0); // minMemType1
                    writer.writeUint32(post.maxMemType1 || numberOfGlyphs); // maxMemType1

                    // write glyphNameIndex
                    var nameIndexs = ttf.support.post.nameIndexs;
                    for (var i = 0, l = nameIndexs.length; i < l; i ++) {
                        writer.writeUint16(nameIndexs[i]);
                    }

                    // write names
                    ttf.support.post.glyphNames.forEach(function(name) {
                        writer.writeBytes(name);
                    });
                },
                size: function(ttf) {
                    var numberOfGlyphs = ttf.glyf.length;
                    var glyphNames = [];
                    var nameIndexs = [];
                    var size = 34 + numberOfGlyphs * 2; // header + nameIndex
                    var nameIndex = 0;

                    // 获取 name的大小
                    for(var i = 0; i < numberOfGlyphs; i++) {
                        var glyf = ttf.glyf[i];

                        // 这里需要注意，"" 有可能是"\3" length不为0，但是是空字符串
                        if (glyf.name && glyf.name.charCodeAt(0) > 31) {

                            if (glyf.name == '.notdef') {
                                nameIndexs.push(0);
                            }
                            else if (glyf.name == '.null') {
                                nameIndexs.push(1);
                            }
                            else if (glyf.name == 'nonmarkingreturn') {
                                nameIndexs.push(2);
                            }
                            // 这里需要注意
                            // unicode如果小于258，并且glyph名字与默认的名字相等，
                            // 则不需要放入name tbl
                            else if (glyf.unicode
                                && glyf.unicode[0] - 29 < 258
                                && postName[glyf.unicode[0] - 29] == glyf.name
                            )
                            {
                                nameIndexs.push(glyf.unicode[0] - 29);
                            }
                            else {
                                nameIndexs.push(258 + nameIndex++);
                                var bytes = string.getPascalStringBytes(ttf.glyf[i].name); //pascal string bytes
                                glyphNames.push(bytes);
                                size += bytes.length;
                            }

                        }
                        // 如果代码点有名字，则按默认的名字
                        else if (glyf.unicode && glyf.unicode[0] - 29 < 258) {
                            nameIndexs.push(glyf.unicode[0] - 29);
                        }
                        // 否则命名为 .notdef
                        else {
                            nameIndexs.push(0);
                        }
                    }

                    ttf.post = ttf.post || {};
                    ttf.post.maxMemType1 = numberOfGlyphs;

                    ttf.support.post = {
                        nameIndexs: nameIndexs,
                        glyphNames: glyphNames
                    };

                    return size;
                }
            }
        );

        return post;
    }
);<commit_msg>修复post表<commit_after>/**
 * @file post.js
 * @author mengke01
 * @date
 * @description
 *
 * post 表
 *
 * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
 */

define(
    function(require) {
        var table = require('./table');
        var struct = require('./struct');
        var string = require('../util/string');
        var postName = require('../enum/postName');

        var Posthead = table.create(
            'posthead',
            [
                ['italicAngle', struct.Fixed],
                ['underlinePosition', struct.Int16],
                ['underlineThickness', struct.Int16],
                ['postoints', struct.Int16], // 不知道干嘛用，文档里面没有
                ['isFixedPitch', struct.Uint32],
                ['minMemType42', struct.Uint32],
                ['maxMemType42', struct.Uint32],
                ['minMemType1', struct.Uint32],
                ['maxMemType1', struct.Uint32]
            ]
        );

        var post = table.create(
            'post',
            [
            ],
            {
                read: function(reader, ttf) {
                    var tbl = null;
                    var format = reader.readFixed(this.offset);

                    // format2
                    if(format == 2) {

                        // 读取表头
                        tbl = new Posthead(reader.offset).read(reader, ttf);
                        tbl.format = format;

                        var numberOfGlyphs = ttf.maxp.numGlyphs;

                        var glyphNameIndex = [];
                        for(var i = 0; i < numberOfGlyphs; ++i) {
                            glyphNameIndex.push(reader.readUint16());
                        }

                        var pascalStringOffset = reader.offset;
                        var pascalStringLength = ttf.tables.post.length - (pascalStringOffset - this.offset);
                        var pascalStringBytes = reader.readBytes(reader.offset, pascalStringLength);

                        tbl.glyphNameIndex = glyphNameIndex;
                        tbl.names = string.readPascalString(pascalStringBytes);
                    }
                    else {
                        tbl = {
                            format: format
                        };
                    }

                    return tbl;
                },
                write: function(writer, ttf) {

                    var numberOfGlyphs = ttf.glyf.length;
                    var post = ttf.post || {};

                    // write header
                    writer.writeFixed(2); // format
                    writer.writeFixed(post.italicAngle || 0); // italicAngle
                    writer.writeInt16(post.underlinePosition || 0); // underlinePosition
                    writer.writeInt16(post.underlineThickness || 0); // underlineThickness
                    writer.writeUint16(post.postoints || 0); // postoints
                    writer.writeUint32(post.isFixedPitch || 0); // isFixedPitch
                    writer.writeUint32(post.minMemType42 || 0); // minMemType42
                    writer.writeUint32(post.maxMemType42 || 0); // maxMemType42
                    writer.writeUint32(post.minMemType1 || 0); // minMemType1
                    writer.writeUint32(post.maxMemType1 || numberOfGlyphs); // maxMemType1

                    // write glyphNameIndex
                    var nameIndexs = ttf.support.post.nameIndexs;
                    for (var i = 0, l = nameIndexs.length; i < l; i ++) {
                        writer.writeUint16(nameIndexs[i]);
                    }

                    // write names
                    ttf.support.post.glyphNames.forEach(function(name) {
                        writer.writeBytes(name);
                    });
                },
                size: function(ttf) {
                    var numberOfGlyphs = ttf.glyf.length;
                    var glyphNames = [];
                    var nameIndexs = [];
                    var size = 34 + numberOfGlyphs * 2; // header + nameIndex
                    var nameIndex = 0;

                    // 获取 name的大小
                    for(var i = 0; i < numberOfGlyphs; i++) {
                        var glyf = ttf.glyf[i];

                        // 这里需要注意，"" 有可能是"\3" length不为0，但是是空字符串
                        if (glyf.name && glyf.name.charCodeAt(0) > 31) {

                            if (glyf.name == '.notdef') {
                                nameIndexs.push(0);
                            }
                            else if (glyf.name == '.null') {
                                nameIndexs.push(1);
                            }
                            else if (glyf.name == 'nonmarkingreturn') {
                                nameIndexs.push(2);
                            }
                            // 这里需要注意
                            // unicode如果小于258，并且glyph名字与默认的名字相等，
                            // 则不需要放入name tbl
                            else if (glyf.unicode
                                && glyf.unicode[0] - 29 < 258
                                && postName[glyf.unicode[0] - 29] == glyf.name
                            )
                            {
                                nameIndexs.push(glyf.unicode[0] - 29);
                            }
                            else {
                                nameIndexs.push(258 + nameIndex++);
                                var bytes = string.getPascalStringBytes(ttf.glyf[i].name); //pascal string bytes
                                glyphNames.push(bytes);
                                size += bytes.length;
                            }

                        }
                        // 如果代码点有名字，则按默认的名字
                        else if (glyf.unicode && glyf.unicode[0] - 29 < 258) {
                            nameIndexs.push(glyf.unicode[0] - 29);
                        }
                        // 否则命名为 .notdef
                        else {
                            nameIndexs.push(0);
                        }
                    }

                    ttf.post = ttf.post || {};
                    ttf.post.maxMemType1 = numberOfGlyphs;

                    ttf.support.post = {
                        nameIndexs: nameIndexs,
                        glyphNames: glyphNames
                    };

                    return size;
                }
            }
        );

        return post;
    }
);<|endoftext|>


<commit_before>/**
 * Setup component.
 *
 * Site Kit by Google, Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import Header from 'GoogleComponents/header';
import Button from 'GoogleComponents/button';
import ResetButton from 'GoogleComponents/reset-button';
import Layout from 'GoogleComponents/layout/layout';
import Optin from 'GoogleComponents/optin';
import { sendAnalyticsTrackingEvent } from 'GoogleUtil';
import { getSiteKitAdminURL } from 'SiteKitCore/util';
import { Component, Fragment } from 'GoogleUtil/react-features';
import { delay } from 'lodash';

/**
 * WordPress dependencies
 */
import { __, sprintf } from '@wordpress/i18n';
import { getQueryArg } from '@wordpress/url';

class SetupUsingProxy extends Component {
	constructor( props ) {
		super( props );

		const { proxySetupURL, siteURL } = googlesitekit.admin;
		const { isSiteKitConnected } = googlesitekit.setup;
		const { canSetup } = googlesitekit.permissions;

		this.state = {
			canSetup,
			isSiteKitConnected,
			completeSetup: false,
			proxySetupURL,
			context: getQueryArg( location.href, 'googlesitekit_context' ),
			siteHostname: ( new URL( siteURL ) ).hostname,
		};
	}

	isSetupFinished() {
		const {
			isSiteKitConnected,
			completeSetup,
		} = this.state;

		return isSiteKitConnected && completeSetup;
	}

	render() {
		if ( this.isSetupFinished() ) {
			const redirectURL = getSiteKitAdminURL(
				'googlesitekit-dashboard',
				{
					notification: 'authentication_success',
				},
			);

			delay( () => {
				window.location.replace( redirectURL );
			}, 500, 'later' );
		}

		const { context, proxySetupURL, siteHostname } = this.state;
		const isRevoked = 'revoked' === context;

		return (
			<Fragment>

								<Layout>

														{ isRevoked ? (
															<Fragment>

																	{ sprintf(
																		/* translators: %s is the site's hostname. (e.g. example.com) */
																		__( 'You revoked access to Site Kit for %s', 'google-site-kit' ),
																		siteHostname
																	) }

																	{ __( 'Site Kit will no longer have access to your account. If you’d like to reconnect Site Kit, click "Start Setup" below to generate new credentials.', 'google-site-kit' ) }

															</Fragment>
														) : (
															<Fragment>

																	{ __( 'The Site Kit plugin is active but requires setup', 'google-site-kit' ) }

																	{ __( 'Site Kit Service will guide you through 3 simple setup steps.', 'google-site-kit' ) }

															</Fragment>
														) }
														<Button
															className="googlesitekit-start-setup"
															href={ proxySetupURL }
															onClick={ () => {
																sendAnalyticsTrackingEvent( 'plugin_setup', 'proxy_start_setup_landing_page' );
															} }
														>
															{ __( 'Start setup', 'google-site-kit' ) }
														</Button>
														<ResetButton />
														<Optin />

								</Layout>

			</Fragment>
		);
	}
}

export default SetupUsingProxy;
<commit_msg>fix: Bad import in proxy setup page<commit_after>/**
 * Setup component.
 *
 * Site Kit by Google, Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import Header from 'GoogleComponents/header';
import Button from 'GoogleComponents/button';
import ResetButton from 'GoogleComponents/reset-button';
import Layout from 'GoogleComponents/layout/layout';
import Optin from 'GoogleComponents/optin';
import { sendAnalyticsTrackingEvent } from 'GoogleUtil';
import { getSiteKitAdminURL } from 'SiteKitCore/util';
import { delay } from 'lodash';

/**
 * WordPress dependencies
 */
import { Component, Fragment } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { getQueryArg } from '@wordpress/url';

class SetupUsingProxy extends Component {
	constructor( props ) {
		super( props );

		const { proxySetupURL, siteURL } = googlesitekit.admin;
		const { isSiteKitConnected } = googlesitekit.setup;
		const { canSetup } = googlesitekit.permissions;

		this.state = {
			canSetup,
			isSiteKitConnected,
			completeSetup: false,
			proxySetupURL,
			context: getQueryArg( location.href, 'googlesitekit_context' ),
			siteHostname: ( new URL( siteURL ) ).hostname,
		};
	}

	isSetupFinished() {
		const {
			isSiteKitConnected,
			completeSetup,
		} = this.state;

		return isSiteKitConnected && completeSetup;
	}

	render() {
		if ( this.isSetupFinished() ) {
			const redirectURL = getSiteKitAdminURL(
				'googlesitekit-dashboard',
				{
					notification: 'authentication_success',
				},
			);

			delay( () => {
				window.location.replace( redirectURL );
			}, 500, 'later' );
		}

		const { context, proxySetupURL, siteHostname } = this.state;
		const isRevoked = 'revoked' === context;

		return (
			<Fragment>

								<Layout>

														{ isRevoked ? (
															<Fragment>

																	{ sprintf(
																		/* translators: %s is the site's hostname. (e.g. example.com) */
																		__( 'You revoked access to Site Kit for %s', 'google-site-kit' ),
																		siteHostname
																	) }

																	{ __( 'Site Kit will no longer have access to your account. If you’d like to reconnect Site Kit, click "Start Setup" below to generate new credentials.', 'google-site-kit' ) }

															</Fragment>
														) : (
															<Fragment>

																	{ __( 'The Site Kit plugin is active but requires setup', 'google-site-kit' ) }

																	{ __( 'Site Kit Service will guide you through 3 simple setup steps.', 'google-site-kit' ) }

															</Fragment>
														) }
														<Button
															className="googlesitekit-start-setup"
															href={ proxySetupURL }
															onClick={ () => {
																sendAnalyticsTrackingEvent( 'plugin_setup', 'proxy_start_setup_landing_page' );
															} }
														>
															{ __( 'Start setup', 'google-site-kit' ) }
														</Button>
														<ResetButton />
														<Optin />

								</Layout>

			</Fragment>
		);
	}
}

export default SetupUsingProxy;
<|endoftext|>


<commit_before>import { Controller } from "@hotwired/stimulus"

import '../src/datatables-bs5'

export default class extends Controller {
  static values = {
    simple: Boolean,
    url: String
  }

  initialize() {
    var myTable = $.fn.dataTable;
    $.extend( true, myTable.Buttons.defaults, {
      "dom": { "button": { "className": 'btn btn-outline-secondary btn-sm' } }
    })
  }

  connect() {
    let dtOptions = {}
    this.compileOptions(dtOptions)
    if (!this.simpleValue) {
      this.setInputFields()
    }
    const table = $(this.element.querySelector('table'))

    // prepare options, optional add remote processing (not yet implemented)
    let dtable = $(table).DataTable(dtOptions)

    // process search input
    dtable.columns().eq(0).each((colIdx) => {
      $('input[name=idx'+colIdx+']').on( 'keyup change', function() {
	dtable.column(colIdx).search(this.value).draw()
      })
    })
  } // connect

  // search fields for each column
  setInputFields() {
    this.element.querySelectorAll("table tfoot th:not([class='nosearch'])")
        .forEach((th, idx) => {
          th.insertAdjacentHTML('afterbegin', this.searchField(idx))
        })
  }

  // single search input field
  searchField(idx) {
    return `<input type="text" placeholder="search" name="idx${idx}" />`
  }

  // datatables options
  compileOptions(options) {
    // common options
    options.pagingType = "full_numbers"
    options.stateSave = false
    options.lengthMenu = [ [10, 25, 100, 250, 1000], [10, 25, 100, 250, 1000] ]
    options.columnDefs = [ { "targets": "nosort", "orderable": false },
                           { "targets": "notvisible", "visible": false },
                           { "targets": "actions", "className": "actions" } ]
    // with or without buttons
    if (this.simpleValue) {
      this.simpleOptions(options)
    } else {
      this.buttonOptions(options)
    }

    // remote fetch via ajax or plain html data
    if (this.hasUrlValue) {
      this.remoteOptions(options)
    }
  }

  simpleOptions(options) {
    options.dom =  "<'row'<'col-sm-12'tr>>" +
                   "<'row'<'col pt-2'l><'col'i><'col'p>>"
    options.pagingType = "numbers"
  }

  buttonOptions(options) {
    options.dom = "<'row'<'col'l><'col'B><'col'f>>" +
                    "<'row'<'col-sm-12'tr>>" +
                    "<'row'<'col'i><'col'p>>"
    options.buttons = [
                        { "extend": 'excel',
	                  "exportOptions": { "search": ':applied' } },
                        { "extend": 'pdf',
	                  "orientation": 'landscape',
	                  "pageSize": 'A4',
	                  "exportOptions": { "columns": ':visible',
	                                     "search": ':applied' } },
                        { "extend": 'print'},
                        { "extend": 'colvis', "columns": ':gt(0)' } ]
  }

  remoteOptions(options) {
    options.searchDelay = 400
    options.processing = true
    options.serverSide = true
    options.ajax = { "url": this.urlValue, "type": "POST" }
  }
} // Controller
<commit_msg>hide buttons on smaller displays<commit_after>import { Controller } from "@hotwired/stimulus"

import '../src/datatables-bs5'

export default class extends Controller {
  static values = {
    simple: Boolean,
    url: String
  }

  initialize() {
    var myTable = $.fn.dataTable;
    $.extend( true, myTable.Buttons.defaults, {
      "dom": { "button": { "className": 'btn btn-outline-secondary btn-sm' } }
    })
  }

  connect() {
    let dtOptions = {}
    this.compileOptions(dtOptions)
    if (!this.simpleValue) {
      this.setInputFields()
    }
    const table = $(this.element.querySelector('table'))

    // prepare options, optional add remote processing (not yet implemented)
    let dtable = $(table).DataTable(dtOptions)

    // process search input
    dtable.columns().eq(0).each((colIdx) => {
      $('input[name=idx'+colIdx+']').on( 'keyup change', function() {
	dtable.column(colIdx).search(this.value).draw()
      })
    })
  } // connect

  // search fields for each column
  setInputFields() {
    this.element.querySelectorAll("table tfoot th:not([class='nosearch'])")
        .forEach((th, idx) => {
          th.insertAdjacentHTML('afterbegin', this.searchField(idx))
        })
  }

  // single search input field
  searchField(idx) {
    return `<input type="text" placeholder="search" name="idx${idx}" />`
  }

  // datatables options
  compileOptions(options) {
    // common options
    options.pagingType = "full_numbers"
    options.stateSave = false
    options.lengthMenu = [ [10, 25, 100, 250, 1000], [10, 25, 100, 250, 1000] ]
    options.columnDefs = [ { "targets": "nosort", "orderable": false },
                           { "targets": "notvisible", "visible": false },
                           { "targets": "actions", "className": "actions" } ]
    // with or without buttons
    if (this.simpleValue) {
      this.simpleOptions(options)
    } else {
      this.buttonOptions(options)
    }

    // remote fetch via ajax or plain html data
    if (this.hasUrlValue) {
      this.remoteOptions(options)
    }
  }

  simpleOptions(options) {
    options.dom =  "<'row'<'col-sm-12'tr>>" +
                   "<'row'<'col pt-2'l><'col'i><'col'p>>"
    options.pagingType = "numbers"
  }

  buttonOptions(options) {
    options.dom = "<'row'<'col'l><'col d-none d-lg-block'B><'col'f>>" +
                    "<'row'<'col-sm-12'tr>>" +
                    "<'row'<'col'i><'col'p>>"
    options.buttons = [
                        { "extend": 'excel',
	                  "exportOptions": { "search": ':applied' } },
                        { "extend": 'pdf',
	                  "orientation": 'landscape',
	                  "pageSize": 'A4',
	                  "exportOptions": { "columns": ':visible',
	                                     "search": ':applied' } },
                        { "extend": 'print'},
                        { "extend": 'colvis', "columns": ':gt(0)' } ]
  }

  remoteOptions(options) {
    options.searchDelay = 400
    options.processing = true
    options.serverSide = true
    options.ajax = { "url": this.urlValue, "type": "POST" }
  }
} // Controller
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react'
import { Link } from 'react-router'

import './index.css'

const Breadcrumb = ({fragments}) => {
  const fragmentNodes = fragments.map(fragment => {
    let {label, linkTo} = fragment

    if (linkTo) return <Link to={linkTo} className="Breadcrumb-fragment Breadcrumb-fragment--link" key={label}>{label}</Link>
    return {label}
  });

  return
{fragmentNodes}

}

Breadcrumb.propTypes = {
  fragments: PropTypes.arrayOf(PropTypes.shape({
    label: React.PropTypes.string.isRequired,
    linkTo: React.PropTypes.string
  })),
}

export default Breadcrumb
<commit_msg>Removed Breadcrumb<commit_after><|endoftext|>


<commit_before>define(
function(require)
{
  var _             = require('underscore');
  var _S            = require('underscore.string');
  var JsonBaseModel = require('models/json_base');

  return JsonBaseModel.extend(
  {
    idAttribute: 'media_id',

    amfphp_url_templates:
    {
      read:   "media.getMedia",
      update: "media.updateMedia",
      create: "media.createMediaFromRawUpload",
      delete: "media.deleteMedia"
    },

    amfphp_url_attributes: function()
    {
      var attribute_list =
      [
        "game_id",
        "media_id",
        "name",
        "autoplay",
        "file_name",
        "data",
        "raw_upload_id"
      ];

      if(this.id === "0") { attribute_list = _.without(attribute_list, "game_id"); }

      // FIXME temporary fix for optional attribute, might need to remove the fixed attribute logic and make it a white list (with non nulls) that gets sent? (make sure nothing ever needs to be nulled out)
      if(!this.get("data")) { attribute_list = _.without(attribute_list, "data"); }

      return attribute_list;
    },

    defaults:
    {
      "name": "Default",
      "file_name": "",
      "autoplay": false,
    },

    is_video: function() { return this.get("file_name").match(/\.(avi|mp4|mkv|mpeg|mpg|mov|m4v|3gp)/); },
    is_audio: function() { return this.get("file_name").match(/\.(wav|mp3|alac|flac|caf|m4a)/); },

    // TODO show video in place!
    thumbnail: function()
    {
           if(this.id === "0") return "images/default128.png";
      else if(this.is_video()) return "images/video128.png";
      else if(this.is_audio()) return "images/audio128.png";
      else                     return this.get("thumb_url");
    },

    thumbnail_for: function(object)
    {
      if(this.id === "0")
      {
        var icon_name = "default";

        if(object)
        {
          // NOTE using this instead of instance of to remove possible require.js cycle reference
          if(object.idAttribute === "dialog_id")   { icon_name = "conversation" }
          if(object.idAttribute === "item_id")     { icon_name = "item"         }
          if(object.idAttribute === "plaque_id")   { icon_name = "plaque"       }
          if(object.idAttribute === "web_page_id") { icon_name = "webpage"      }
          if(object.idAttribute === "game_id")     { icon_name = "aris"         }
          if(object.idAttribute === "note_id")     { icon_name = "notebook"     }

          if(object.idAttribute === "trigger_id")
            // NOTE this only works in scene triggers until storage is fixed.
            return object.game_object().icon_thumbnail();

          if(object.idAttribute === "tab_id")
          {
            if(object.get("type") === "QUESTS")    { icon_name = "quests"    }
            if(object.get("type") === "MAP")       { icon_name = "map"       }
            if(object.get("type") === "INVENTORY") { icon_name = "inventory" }
            if(object.get("type") === "SCANNER")   { icon_name = "scanner"   }
            if(object.get("type") === "DECODER")   { icon_name = "scanner"   }
            if(object.get("type") === "PLAYER")    { icon_name = "player"    }
            if(object.get("type") === "NOTEBOOK")  { icon_name = "notebook"  }

            if(object.game_object())
              return object.game_object().icon_thumbnail();
          }

          if(object.idAttribute === "factory_id")
          {
            if(object.game_object())
              return object.game_object().icon_thumbnail();
          }
        }

        return "images/"+icon_name+"_icon_120.png"
      }
      else
        return this.thumbnail();
    },

    name_for: function(object)
    {
      if(object && object.idAttribute === "trigger_id" && this.id === "0")
      {
        var name = object.game_object().get('name') || 'Parent'
        return _.str.truncate(name, 7) + "'s Icon"
      }
      else
        return this.get("name");
    },

    content: function()
    {
      return this.get("url");
    }
  });
});

<commit_msg>Make raw_upload_id optional in model<commit_after>define(
function(require)
{
  var _             = require('underscore');
  var _S            = require('underscore.string');
  var JsonBaseModel = require('models/json_base');

  return JsonBaseModel.extend(
  {
    idAttribute: 'media_id',

    amfphp_url_templates:
    {
      read:   "media.getMedia",
      update: "media.updateMedia",
      create: "media.createMediaFromRawUpload",
      delete: "media.deleteMedia"
    },

    amfphp_url_attributes: function()
    {
      var attribute_list =
      [
        "game_id",
        "media_id",
        "name",
        "autoplay",
        "file_name",
        "data",
        "raw_upload_id"
      ];

      if(this.id === "0") { attribute_list = _.without(attribute_list, "game_id"); }

      // FIXME temporary fix for optional attribute, might need to remove the fixed attribute logic and make it a white list (with non nulls) that gets sent? (make sure nothing ever needs to be nulled out)
      if(!this.get("data")) { attribute_list = _.without(attribute_list, "data"); }
      if(!this.get("raw_upload_id")) { attribute_list = _.without(attribute_list, "raw_upload_id"); }

      return attribute_list;
    },

    defaults:
    {
      "name": "Default",
      "file_name": "",
      "autoplay": false,
    },

    is_video: function() { return this.get("file_name").match(/\.(avi|mp4|mkv|mpeg|mpg|mov|m4v|3gp)/); },
    is_audio: function() { return this.get("file_name").match(/\.(wav|mp3|alac|flac|caf|m4a)/); },

    // TODO show video in place!
    thumbnail: function()
    {
           if(this.id === "0") return "images/default128.png";
      else if(this.is_video()) return "images/video128.png";
      else if(this.is_audio()) return "images/audio128.png";
      else                     return this.get("thumb_url");
    },

    thumbnail_for: function(object)
    {
      if(this.id === "0")
      {
        var icon_name = "default";

        if(object)
        {
          // NOTE using this instead of instance of to remove possible require.js cycle reference
          if(object.idAttribute === "dialog_id")   { icon_name = "conversation" }
          if(object.idAttribute === "item_id")     { icon_name = "item"         }
          if(object.idAttribute === "plaque_id")   { icon_name = "plaque"       }
          if(object.idAttribute === "web_page_id") { icon_name = "webpage"      }
          if(object.idAttribute === "game_id")     { icon_name = "aris"         }
          if(object.idAttribute === "note_id")     { icon_name = "notebook"     }

          if(object.idAttribute === "trigger_id")
            // NOTE this only works in scene triggers until storage is fixed.
            return object.game_object().icon_thumbnail();

          if(object.idAttribute === "tab_id")
          {
            if(object.get("type") === "QUESTS")    { icon_name = "quests"    }
            if(object.get("type") === "MAP")       { icon_name = "map"       }
            if(object.get("type") === "INVENTORY") { icon_name = "inventory" }
            if(object.get("type") === "SCANNER")   { icon_name = "scanner"   }
            if(object.get("type") === "DECODER")   { icon_name = "scanner"   }
            if(object.get("type") === "PLAYER")    { icon_name = "player"    }
            if(object.get("type") === "NOTEBOOK")  { icon_name = "notebook"  }

            if(object.game_object())
              return object.game_object().icon_thumbnail();
          }

          if(object.idAttribute === "factory_id")
          {
            if(object.game_object())
              return object.game_object().icon_thumbnail();
          }
        }

        return "images/"+icon_name+"_icon_120.png"
      }
      else
        return this.thumbnail();
    },

    name_for: function(object)
    {
      if(object && object.idAttribute === "trigger_id" && this.id === "0")
      {
        var name = object.game_object().get('name') || 'Parent'
        return _.str.truncate(name, 7) + "'s Icon"
      }
      else
        return this.get("name");
    },

    content: function()
    {
      return this.get("url");
    }
  });
});

<|endoftext|>


<commit_before>

/**
 * Model class to represent all the notes a user has.
 * Representation Invariants
 *   - Immutable.
 *   - All notes contained in this model should have unique identifiers
 *     with respect to each other.
 * @param {Array.<networkStickies.Note>} notes the notes for the user.
 * @constructor
 * @const
 */
networkStickies.NoteSet = function(notes) {
  // defensive copy
  /** @const */ var notesCopy = _(notes).map(function(note) {
    return note;
  });

  /**
   * Edit a note.
   * @param {number} identifier the ID of the note.
   * @param {string} newBody the new body of the note.
   * @return {networkStickies.NoteSet} the updated model.
   * @const
   */
  this.editNoteWithId = function(identifier, newBody) {
    /** @const */ var newNotes = _(notesCopy).map(function(note) {
      if (note.identifier() !== identifier) {
        return note;
      } else {
        return note.updateBody(newBody);
      }
    });
    return new networkStickies.NoteSet(newNotes);
  };

  /**
   * Delete a note.
   * @param {number} identifier the ID of the note.
   * @return {networkStickies.NoteSet} the updated model.
   * @const
   */
  this.deleteNoteWithId = function(identifier) {
    /** @const */ var newNotes = _(notesCopy).reject(function(note) {
      return note.identifier() === identifier;
    });
    return new networkStickies.NoteSet(newNotes);
  };
};
<commit_msg>Added a find method to noteSet<commit_after>

/**
 * Model class to represent all the notes a user has.
 * Representation Invariants
 *   - Immutable.
 *   - All notes contained in this model should have unique identifiers
 *     with respect to each other.
 * @param {Array.<networkStickies.Note>} notes the notes for the user.
 * @constructor
 * @const
 */
networkStickies.NoteSet = function(notes) {
  // defensive copy
  /** @const */ var notesCopy = _(notes).map(function(note) {
    return note;
  });

  /**
   * Retrieve a note.
   * @param {number} identifier the ID of the note.
   * @return {networkStickies.utils.Option} a Some containing the Note,
   *     or None if no note exists.
   */
  this.findNoteById = function(identifier) {
    /** @const */ var result = _(notesCopy).detect(function(note) {
      return note.identifier() === identifier;
    });
    if (result) {
      return new networkStickies.utils.Some(result);
    } else {
      return networkStickies.utils.None.instance;
    }
  };

  /**
   * Edit a note.
   * @param {number} identifier the ID of the note.
   * @param {string} newBody the new body of the note.
   * @return {networkStickies.NoteSet} the updated model.
   * @const
   */
  this.editNoteWithId = function(identifier, newBody) {
    /** @const */ var newNotes = _(notesCopy).map(function(note) {
      if (note.identifier() !== identifier) {
        return note;
      } else {
        return note.updateBody(newBody);
      }
    });
    return new networkStickies.NoteSet(newNotes);
  };

  /**
   * Delete a note.
   * @param {number} identifier the ID of the note.
   * @return {networkStickies.NoteSet} the updated model.
   * @const
   */
  this.deleteNoteWithId = function(identifier) {
    /** @const */ var newNotes = _(notesCopy).reject(function(note) {
      return note.identifier() === identifier;
    });
    return new networkStickies.NoteSet(newNotes);
  };
};
<|endoftext|>