<commit_before>const { AbstractShot } = require("../shared/shot");
const renderOembedString = require("./views/oembed.js").renderString;
const db = require("./db");
const uuid = require("uuid");
const linker = require("./linker");
const config = require("./config").root();
let ClipRewrites;

class Shot extends AbstractShot {

  constructor(ownerId, backend, id, attrs) {
    super(backend, id, attrs);
    this.ownerId = ownerId;
  }

  oembedJson({maxheight, maxwidth}) {
    let body = renderOembedString({shot: this, maxheight, maxwidth, backend: this.backend});
    return {
      // Attributes we could set, but don't (yet):
      //author_name: "",
      //author_url: "",
      //cache_age: "",
      //thumbnail_url: "",
      //thumbnail_width: "",
      //thumbnail_height: "",
      type: "rich",
      version: "1.0",
      title: this.title,
      provider_name: "PageShot",
      provider_url: this.backend,
      html: body,
      // I don't really understand how these relate to maxheight/maxwidth, or how we should set them:
      height: 300,
      width: 400
    };
  }

  get contentUrl() {
    return "//" + config.contentOrigin + "/content/" + this.id;
  }

  insert() {
    return db.transaction((client) => {
      return db.queryWithClient(
        client, "SELECT id FROM data WHERE id = $1", [this.id]
      ).then((rows) => {
        if (rows.rowCount) {
          // duplicate key
          return false;
        }

        let clipRewrites = new ClipRewrites(this);
        clipRewrites.rewriteShotUrls();
        let oks = clipRewrites.commands();
        let json = this.asJson();
        let head = json.head;
        let body = json.body;
        json.head = null;
        json.body = null;
        oks.push({setHead: null});
        oks.push({setBody: null});
        return db.queryWithClient(
          client,
          `INSERT INTO data (id, deviceid, value, head, body, url)
           VALUES ($1, $2, $3, $4, $5, $6)`,
          [this.id, this.ownerId, JSON.stringify(json), head, body, json.url]
        ).then((rowCount) => {
          return clipRewrites.commit(client);
        }).then(() => {
          return oks;
        }).catch((err) => {
          if (err.code == '23505') {
            // This is a duplicate key error, means the insert failed
            clipRewrites.revertShotUrls();
            return false;
          }
          throw err;
        });
      });
    });
  }

  update() {
    let clipRewrites = new ClipRewrites(this);
    clipRewrites.rewriteShotUrls();
    let oks = clipRewrites.commands();
    let json = this.asJson();
    return db.transaction((client) => {
      let head = json.head;
      let body = json.body;
      json.head = null;
      json.body = null;
      if (head !== null) {
        oks.push({setHead: null});
      }
      if (body !== null) {
        oks.push({setBody: null});
      }
      let promise;
      if (head === null && body === null) {
        promise = db.queryWithClient(
          client,
          `UPDATE data SET value = $1
          WHERE id = $2 AND deviceid = $3`,
          [JSON.stringify(json), this.id, this.ownerId]
        );
      } else {
        promise = db.queryWithClient(
          client,
          `UPDATE data SET value = $1, head = $2, body = $3
          WHERE id = $4 AND deviceid = $5`,
          [JSON.stringify(json), head, body, this.id, this.ownerId]
        );
      }
      return promise.then((rowCount) => {
        if (! rowCount) {
          throw new Error("No row updated");
        }
        return clipRewrites.commit(client);
      }).then(() => {
        return oks;
      });
    });
  }

}

Shot.getRawBytesForClip = function (uid) {
  return db.select(
    "SELECT image, contenttype FROM images WHERE id = $1", [uid]
  ).then((rows) => {
    if (! rows.length) {
      return null;
    } else {
      return {data: rows[0].image, contentType: rows[0].contenttype};
    }
  });
};

exports.Shot = Shot;

class ServerClip extends AbstractShot.prototype.Clip {
  imageBinary() {
    if (! (this.image && this.image.url)) {
      throw new Error("Not an image clip");
    }
    let url = this.image.url;
    let match = (/^data:([^;]*);base64,/).exec(url);
    if (! match) {
      throw new Error("Bad clip URL");
    }
    let imageData = url.substr(match[0].length);
    imageData = new Buffer(imageData, 'base64');
    return {
      contentType: match[1],
      data: imageData
    };
  }

  setUrlFromBinary(binaryData) {
    let url = "data:" + binaryData.contentType + ";base64,";
    url += binaryData.data.toString("base64");
    this.image.url = url;
  }
}

Shot.prototype.Clip = ServerClip;

Shot.get = function (backend, id) {
  return Shot.getRawValue(id).then((rawValue) => {
    if (! rawValue) {
      return null;
    }
    let json = JSON.parse(rawValue.value);
    let shot = new Shot(rawValue.userid, backend, id, json);
    shot.urlIfDeleted = rawValue.url;
    shot.expireTime = rawValue.expireTime;
    shot.deleted = rawValue.deleted;
    return shot;
  });
};

Shot.getFullShot = function (backend, id) {
  if (! id) {
    throw new Error("Empty id: " + id);
  }
  return db.select(
    `SELECT value, deviceid, head, body FROM data
    WHERE data.id = $1`,
    [id]
  ).then((rows) => {
    if (! rows.length) {
      return null;
    }
    let row = rows[0];
    let json = JSON.parse(row.value);
    let shot = new Shot(row.userid, backend, id, json);
    shot.head = row.head;
    shot.body = row.body;
    return shot;
  });
};

Shot.getRawValue = function (id) {
  if (! id) {
    throw new Error("Empty id: " + id);
  }
  return db.select(
    `SELECT value, deviceid, url, expire_time, deleted FROM data WHERE id = $1`,
    [id]
  ).then((rows) => {
    if (! rows.length) {
      return null;
    }
    let row = rows[0];
    return {
      userid: row.deviceid,
      value: row.value,
      url: row.url,
      expireTime: row.expire_time,
      deleted: row.deleted
    };
  });
};

Shot.getShotsForDevice = function (backend, deviceId) {
  if (! deviceId) {
    throw new Error("Empty deviceId: " + deviceId);
  }
  return db.select(
    `SELECT DISTINCT devices.id
     FROM devices, devices AS devices2
     WHERE devices.id = $1
           OR (devices.accountid = devices2.accountid
               AND devices2.id = $1)
    `,
    [deviceId]
  ).then((rows) => {
    let ids = [];
    let idNums = [];
    for (let i=0; i<rows.length; i++) {
      ids.push(rows[i].id);
      idNums.push("$" + (i+1));
    }
    return db.select(
      `SELECT data.id, data.value, data.deviceid
       FROM data
       WHERE data.deviceid IN (${idNums.join(", ")})
       ORDER BY data.created DESC
      `,
      ids
    );
  }).then((rows) => {
    let result = [];
    for (let i=0; i<rows.length; i++) {
      let row = rows[i];
      let json = JSON.parse(row.value);
      if (json === null) {
        console.warn("Unable to parse json for row", row.deviceid, row.id, row.value);
      } else {
        let shot;
        try {
          shot = new Shot(row.deviceid, backend, row.id, json);
        } catch (e) {
          console.warn("Error instantiating shot:", e);
          continue;
        }
        result.push(shot);
      }
    }
    return result;
  });
};

Shot.deleteEverythingForDevice = function (backend, deviceId) {
  return db.select(
    `SELECT DISTINCT devices.id
     FROM devices, devices AS devices2
     WHERE devices.id = $1
           OR (devices.accountid = devices2.accountid
               AND devices2.id = $1)
    `,
    [deviceId]
  ).then((rows) => {
    let ids = [];
    for (let i=0; i<rows.length; i++) {
      ids.push(rows[i].id);
    }
    let deleteSql = `DELETE FROM devices WHERE
     id IN (${db.markersForArgs(1, ids.length)})`;
    return db.update(
      deleteSql,
      ids
    );

  });
};

ClipRewrites = class ClipRewrites {

  constructor(shot) {
    this.shot = shot;
    this.committed = false;
    this.unedited = [];
    this.toInsertClipIds = [];
    this.toInsert = {};
    for (let name of this.shot.clipNames()) {
      let clip = this.shot.getClip(name);
      if (clip.image && clip.isDataUrl()) {
        this.toInsertClipIds.push(clip.id);
        let imageId = uuid.v4();
        this.toInsert[clip.id] = {
          uuid: imageId,
          url: linker.imageLinkWithHost(imageId),
          binary: clip.imageBinary()
        };
      } else {
        this.unedited.push(clip.id);
      }
    }
    this.toInsertThumbnail = null;
    this.oldFullScreenThumbnail = this.shot.fullScreenThumbnail;

    let url = this.shot.fullScreenThumbnail;
    let match = (/^data:([^;]*);base64,/).exec(url);
    if (match) {
      let imageData = url.substr(match[0].length);
      imageData = new Buffer(imageData, 'base64');
      let imageId = uuid.v4();
      this.toInsertThumbnail = {
        contentType: match[1],
        binary: imageData,
        uuid: imageId,
        url: linker.imageLinkWithHost(imageId)
      };
    }
  }

  rewriteShotUrls() {
    for (let clipId of this.toInsertClipIds) {
      let url = this.toInsert[clipId].url;
      let clip = this.shot.getClip(clipId);
      clip.image.url = url;
    }
    if (this.toInsertThumbnail !== null) {
      this.shot.fullScreenThumbnail = this.toInsertThumbnail.url;
    }
  }

  revertShotUrls() {
    for (let clipId of this.toInsertClipIds) {
      let data = this.toInsert[clipId];
      let clip = this.shot.getClip(clipId);
      clip.setUrlFromBinary(data.binary);
    }
    this.shot.fullScreenThumbnail = this.oldFullScreenThumbnail;
  }

  commands() {
    let commands = [];
    if (this.toInsertThumbnail !== null) {
      commands.push({updateThumbnailUrl: this.toInsertThumbnail.url});
    }
    for (let clipId of this.toInsertClipIds) {
      let url = this.toInsert[clipId].url;
      commands.push({updateClipUrl: {clipId, url}});
    }
    return commands;
  }

  commit(client) {
    if (! this.toInsertClipIds.length) {
      return Promise.resolve();
    }
    let query;
    if (this.unedited.length) {
      query = `DELETE FROM images
               WHERE shotid = $1
                    AND clipid NOT IN (${db.markersForArgs(2, this.unedited.length)})
              `;
    } else {
      query = `DELETE FROM images WHERE shotid = $1`;
    }
    return db.queryWithClient(
      client,
      query,
      [this.shot.id].concat(this.unedited)
    ).then(() => {
      return Promise.all(
        this.toInsertClipIds.map((clipId) => {
          let data = this.toInsert[clipId];
          return db.queryWithClient(
            client,
            `INSERT INTO images (id, shotid, clipid, image, contenttype)
             VALUES ($1, $2, $3, $4, $5)
            `,
            [data.uuid, this.shot.id, clipId, data.binary.data, data.binary.contentType]);
        })
      );
    }).then(() => {
      return db.queryWithClient(
        client,
        `INSERT INTO images (id, shotid, clipid, image, contenttype)
        VALUES ($1, $2, $3, $4, $5)
        `,
        // Since we don't have a clipid for the thumbnail and the column is NOT NULL,
        // Use the thumbnail uuid as the clipid. This allows figuring out which
        // images are thumbnails, too.
        [this.toInsertThumbnail.uuid, this.shot.id, this.toInsertThumbnail.uuid,
        this.toInsertThumbnail.binary, this.toInsertThumbnail.contentType]);
    }).then(() => {
      this.committed = true;
    });
  }

};
<commit_msg>Handle cases where there are no clips to insert but there is a thumbnail to insert, and also where there are clips to insert but no thumbnail to insert.<commit_after>const { AbstractShot } = require("../shared/shot");
const renderOembedString = require("./views/oembed.js").renderString;
const db = require("./db");
const uuid = require("uuid");
const linker = require("./linker");
const config = require("./config").root();
let ClipRewrites;

class Shot extends AbstractShot {

  constructor(ownerId, backend, id, attrs) {
    super(backend, id, attrs);
    this.ownerId = ownerId;
  }

  oembedJson({maxheight, maxwidth}) {
    let body = renderOembedString({shot: this, maxheight, maxwidth, backend: this.backend});
    return {
      // Attributes we could set, but don't (yet):
      //author_name: "",
      //author_url: "",
      //cache_age: "",
      //thumbnail_url: "",
      //thumbnail_width: "",
      //thumbnail_height: "",
      type: "rich",
      version: "1.0",
      title: this.title,
      provider_name: "PageShot",
      provider_url: this.backend,
      html: body,
      // I don't really understand how these relate to maxheight/maxwidth, or how we should set them:
      height: 300,
      width: 400
    };
  }

  get contentUrl() {
    return "//" + config.contentOrigin + "/content/" + this.id;
  }

  insert() {
    return db.transaction((client) => {
      return db.queryWithClient(
        client, "SELECT id FROM data WHERE id = $1", [this.id]
      ).then((rows) => {
        if (rows.rowCount) {
          // duplicate key
          return false;
        }

        let clipRewrites = new ClipRewrites(this);
        clipRewrites.rewriteShotUrls();
        let oks = clipRewrites.commands();
        let json = this.asJson();
        let head = json.head;
        let body = json.body;
        json.head = null;
        json.body = null;
        oks.push({setHead: null});
        oks.push({setBody: null});
        return db.queryWithClient(
          client,
          `INSERT INTO data (id, deviceid, value, head, body, url)
           VALUES ($1, $2, $3, $4, $5, $6)`,
          [this.id, this.ownerId, JSON.stringify(json), head, body, json.url]
        ).then((rowCount) => {
          return clipRewrites.commit(client);
        }).then(() => {
          return oks;
        }).catch((err) => {
          if (err.code == '23505') {
            // This is a duplicate key error, means the insert failed
            clipRewrites.revertShotUrls();
            return false;
          }
          throw err;
        });
      });
    });
  }

  update() {
    let clipRewrites = new ClipRewrites(this);
    clipRewrites.rewriteShotUrls();
    let oks = clipRewrites.commands();
    let json = this.asJson();
    return db.transaction((client) => {
      let head = json.head;
      let body = json.body;
      json.head = null;
      json.body = null;
      if (head !== null) {
        oks.push({setHead: null});
      }
      if (body !== null) {
        oks.push({setBody: null});
      }
      let promise;
      if (head === null && body === null) {
        promise = db.queryWithClient(
          client,
          `UPDATE data SET value = $1
          WHERE id = $2 AND deviceid = $3`,
          [JSON.stringify(json), this.id, this.ownerId]
        );
      } else {
        promise = db.queryWithClient(
          client,
          `UPDATE data SET value = $1, head = $2, body = $3
          WHERE id = $4 AND deviceid = $5`,
          [JSON.stringify(json), head, body, this.id, this.ownerId]
        );
      }
      return promise.then((rowCount) => {
        if (! rowCount) {
          throw new Error("No row updated");
        }
        return clipRewrites.commit(client);
      }).then(() => {
        return oks;
      });
    });
  }

}

Shot.getRawBytesForClip = function (uid) {
  return db.select(
    "SELECT image, contenttype FROM images WHERE id = $1", [uid]
  ).then((rows) => {
    if (! rows.length) {
      return null;
    } else {
      return {data: rows[0].image, contentType: rows[0].contenttype};
    }
  });
};

exports.Shot = Shot;

class ServerClip extends AbstractShot.prototype.Clip {
  imageBinary() {
    if (! (this.image && this.image.url)) {
      throw new Error("Not an image clip");
    }
    let url = this.image.url;
    let match = (/^data:([^;]*);base64,/).exec(url);
    if (! match) {
      throw new Error("Bad clip URL");
    }
    let imageData = url.substr(match[0].length);
    imageData = new Buffer(imageData, 'base64');
    return {
      contentType: match[1],
      data: imageData
    };
  }

  setUrlFromBinary(binaryData) {
    let url = "data:" + binaryData.contentType + ";base64,";
    url += binaryData.data.toString("base64");
    this.image.url = url;
  }
}

Shot.prototype.Clip = ServerClip;

Shot.get = function (backend, id) {
  return Shot.getRawValue(id).then((rawValue) => {
    if (! rawValue) {
      return null;
    }
    let json = JSON.parse(rawValue.value);
    let shot = new Shot(rawValue.userid, backend, id, json);
    shot.urlIfDeleted = rawValue.url;
    shot.expireTime = rawValue.expireTime;
    shot.deleted = rawValue.deleted;
    return shot;
  });
};

Shot.getFullShot = function (backend, id) {
  if (! id) {
    throw new Error("Empty id: " + id);
  }
  return db.select(
    `SELECT value, deviceid, head, body FROM data
    WHERE data.id = $1`,
    [id]
  ).then((rows) => {
    if (! rows.length) {
      return null;
    }
    let row = rows[0];
    let json = JSON.parse(row.value);
    let shot = new Shot(row.userid, backend, id, json);
    shot.head = row.head;
    shot.body = row.body;
    return shot;
  });
};

Shot.getRawValue = function (id) {
  if (! id) {
    throw new Error("Empty id: " + id);
  }
  return db.select(
    `SELECT value, deviceid, url, expire_time, deleted FROM data WHERE id = $1`,
    [id]
  ).then((rows) => {
    if (! rows.length) {
      return null;
    }
    let row = rows[0];
    return {
      userid: row.deviceid,
      value: row.value,
      url: row.url,
      expireTime: row.expire_time,
      deleted: row.deleted
    };
  });
};

Shot.getShotsForDevice = function (backend, deviceId) {
  if (! deviceId) {
    throw new Error("Empty deviceId: " + deviceId);
  }
  return db.select(
    `SELECT DISTINCT devices.id
     FROM devices, devices AS devices2
     WHERE devices.id = $1
           OR (devices.accountid = devices2.accountid
               AND devices2.id = $1)
    `,
    [deviceId]
  ).then((rows) => {
    let ids = [];
    let idNums = [];
    for (let i=0; i<rows.length; i++) {
      ids.push(rows[i].id);
      idNums.push("$" + (i+1));
    }
    return db.select(
      `SELECT data.id, data.value, data.deviceid
       FROM data
       WHERE data.deviceid IN (${idNums.join(", ")})
       ORDER BY data.created DESC
      `,
      ids
    );
  }).then((rows) => {
    let result = [];
    for (let i=0; i<rows.length; i++) {
      let row = rows[i];
      let json = JSON.parse(row.value);
      if (json === null) {
        console.warn("Unable to parse json for row", row.deviceid, row.id, row.value);
      } else {
        let shot;
        try {
          shot = new Shot(row.deviceid, backend, row.id, json);
        } catch (e) {
          console.warn("Error instantiating shot:", e);
          continue;
        }
        result.push(shot);
      }
    }
    return result;
  });
};

Shot.deleteEverythingForDevice = function (backend, deviceId) {
  return db.select(
    `SELECT DISTINCT devices.id
     FROM devices, devices AS devices2
     WHERE devices.id = $1
           OR (devices.accountid = devices2.accountid
               AND devices2.id = $1)
    `,
    [deviceId]
  ).then((rows) => {
    let ids = [];
    for (let i=0; i<rows.length; i++) {
      ids.push(rows[i].id);
    }
    let deleteSql = `DELETE FROM devices WHERE
     id IN (${db.markersForArgs(1, ids.length)})`;
    return db.update(
      deleteSql,
      ids
    );

  });
};

ClipRewrites = class ClipRewrites {

  constructor(shot) {
    this.shot = shot;
    this.committed = false;
    this.unedited = [];
    this.toInsertClipIds = [];
    this.toInsert = {};
    for (let name of this.shot.clipNames()) {
      let clip = this.shot.getClip(name);
      if (clip.image && clip.isDataUrl()) {
        this.toInsertClipIds.push(clip.id);
        let imageId = uuid.v4();
        this.toInsert[clip.id] = {
          uuid: imageId,
          url: linker.imageLinkWithHost(imageId),
          binary: clip.imageBinary()
        };
      } else {
        this.unedited.push(clip.id);
      }
    }
    this.toInsertThumbnail = null;
    this.oldFullScreenThumbnail = this.shot.fullScreenThumbnail;

    let url = this.shot.fullScreenThumbnail;
    let match = (/^data:([^;]*);base64,/).exec(url);
    if (match) {
      let imageData = url.substr(match[0].length);
      imageData = new Buffer(imageData, 'base64');
      let imageId = uuid.v4();
      this.toInsertThumbnail = {
        contentType: match[1],
        binary: imageData,
        uuid: imageId,
        url: linker.imageLinkWithHost(imageId)
      };
    }
  }

  rewriteShotUrls() {
    for (let clipId of this.toInsertClipIds) {
      let url = this.toInsert[clipId].url;
      let clip = this.shot.getClip(clipId);
      clip.image.url = url;
    }
    if (this.toInsertThumbnail !== null) {
      this.shot.fullScreenThumbnail = this.toInsertThumbnail.url;
    }
  }

  revertShotUrls() {
    for (let clipId of this.toInsertClipIds) {
      let data = this.toInsert[clipId];
      let clip = this.shot.getClip(clipId);
      clip.setUrlFromBinary(data.binary);
    }
    this.shot.fullScreenThumbnail = this.oldFullScreenThumbnail;
  }

  commands() {
    let commands = [];
    if (this.toInsertThumbnail !== null) {
      commands.push({updateThumbnailUrl: this.toInsertThumbnail.url});
    }
    for (let clipId of this.toInsertClipIds) {
      let url = this.toInsert[clipId].url;
      commands.push({updateClipUrl: {clipId, url}});
    }
    return commands;
  }

  commit(client) {
    let query;
    if (this.unedited.length) {
      query = `DELETE FROM images
               WHERE shotid = $1
                    AND clipid NOT IN (${db.markersForArgs(2, this.unedited.length)})
              `;
    } else {
      query = `DELETE FROM images WHERE shotid = $1`;
    }
    return db.queryWithClient(
      client,
      query,
      [this.shot.id].concat(this.unedited)
    ).then(() => {
      return Promise.all(
        this.toInsertClipIds.map((clipId) => {
          let data = this.toInsert[clipId];
          return db.queryWithClient(
            client,
            `INSERT INTO images (id, shotid, clipid, image, contenttype)
             VALUES ($1, $2, $3, $4, $5)
            `,
            [data.uuid, this.shot.id, clipId, data.binary.data, data.binary.contentType]);
        })
      );
    }).then(() => {
      if (this.toInsertThumbnail === null) {
        return Promise.resolve();
      }
      return db.queryWithClient(
        client,
        `INSERT INTO images (id, shotid, clipid, image, contenttype)
        VALUES ($1, $2, $3, $4, $5)
        `,
        // Since we don't have a clipid for the thumbnail and the column is NOT NULL,
        // Use the thumbnail uuid as the clipid. This allows figuring out which
        // images are thumbnails, too.
        [this.toInsertThumbnail.uuid, this.shot.id, this.toInsertThumbnail.uuid,
        this.toInsertThumbnail.binary, this.toInsertThumbnail.contentType]);
    }).then(() => {
      this.committed = true;
    });
  }

};
<|endoftext|>


<commit_before>var Core = (function() {

	var register, start, startAll, stop, stopAll,
		boot, debug, log, publish, subscribe, unsubscribe;

	var moduleData = {}, // module instances stored
		channels = {},
		debug = true;    // enable debugging feature

	register =  function(moduleId, creator) {
		if (typeof moduleId === 'string' && typeof creator === 'function') {
			if (!moduleData[moduleId]) {
				moduleData[moduleId] = {};
				moduleData[moduleId].creator = creator;
				moduleData[moduleId].instance = null;
			} else {
				log("Registration Failed: Module with moduleId '" + moduleId + "' already exists.");
			}

		} else {
			log("Registration Failed: Module '" + moduleId + "' Either moduleId or creator are of incorrect formats");
		}
	};

	startAll = function() {
		var moduleId;
		for (moduleId in moduleData) {
			console.log("isnide startAll + " + moduleId);
			if (moduleData.hasOwnProperty(moduleId)) {
				console.log("calling start");
				start(moduleId);
			}
		}
	};

	stop = function(moduleId) {
		if (typeof moduleId === 'string') {
			var data = moduleData[moduleId];
			if(data) {
				data.instance.destroy();
				data.instance = null;
			}
		} else {
			log("stop method FAILED: ModuleId is of incorrect format.");
		}
	};

	stopAll = function() {
		var moduleId;
		for (moduelId in moduleData) {
			if (moduleData.hasOwnProperty(moduleId)) {
				stop(moduleId);
			}
		}
	};

	boot = function() {
		console.log("inside boot");
		startAll();
	};
	start = function(moduleId) {
		if (typeof moduleId === 'string') {
			var data = moduleData[moduleId];
			console.log(data);
			console.log(this);
			console.log(Core);
			if(data) {
				data.instance = data.creator(new Sandbox(Core));
				data.instance.init();
			} else {
				log("Start Failed: module '" + moduleId + "' does not exist.");
			}
		} else {
			log("start method FAILED: ModuleId is of incorrect format.");
		}
	};

	debug = function(on) {
		debug = on ? true : false;
	};

	log = function(message, severity) {
		var method = (severity || severity == 1) ? 'log' : (severity == 2) ? 'warn' : 'error';
		console[method](message);
	};

	publish = function(topic, data) {
		var subscriptions = channels[topic];
		if (subscriptions) {
			for (var i = 0; i < subscriptions.length; i++) {
				var item = subscriptions[i];
				item.callback.call(item.context, data);
			}
		}
	};

	subscribe = function(topic, fn, target) {
		target = target || this;
		var subscriptions = channels[topic] || [];
		subscriptions.push({callback: fn, context: target});
		channels[topic] = subscriptions;
	};

	unsubscribe = function(topic, evt, target) {
		target = target || this;
		var subscriptions = channels[topic] || [];
		for(var index = 0; index < subscriptions.length; index++) {
			if (subscriptions[index].callback == fn && subscriptions[index].context == target){
				break;
			}
		}
		subscriptions.splice(index, 1);
		channels[topic] = subscriptions;
	};

	// dom methods
	query = function(selector, context) {
		var elems;
		if (context && context.find) {
			elems = context.find(selector);
		} else {
			elems = $(selector);
		}
		/*var ret = elems.get();
			ret.length = elems.length;*/
		return elems;
	};

	bind = function(element, evt, fn) {
		if (typeof evt === 'function') {
			fn = evt;
			evt = 'click';
		}
		$(element).bind(evt, fn);
	};

	unbind = function(element, evt, fn) {
		if (typeof evt === 'function') {
			fn = evt;
			evt = 'click';
		}
		$(element).unbind(evt, fn);
	};

	trim = function(data) {
		if (typeof data === 'string') {
			return $.trim(data);
		}else {
			throw new Error("Illegal input format in trim()");
		}
	};
	request = function(options) {

		options = options || {};
		if (options.url == undefined){
			throw new Error("Request Failed in Core: Cannot send request with undefined URL");
		}
		var method = options.method || "GET";
		var dataType = options.dataType || "json";

		$.ajax({
			url : options.url,
			type: method,
			dataType: dataType,
			success: options.success
		});

	};

	tmpl = function(template, data) {
		return $.tmpl(template, data);
	};

	return {
		register: register,
		start: start,
		stop: stop,
		startAll: startAll,
		stopAll: stopAll,
		log: log,
		debug: debug,
		publish: publish,
		subscribe: subscribe,
		unsubscribe: unsubscribe,
		boot: boot,
		dom : {
			query: query,
			bind: bind,
			unbind: unbind,
			trim: trim
		},
		request: request,
		tmpl: tmpl
	};
}());<commit_msg>core  modified<commit_after>var Core = (function() {

	var register, start, startAll, stop, stopAll,
		boot, debug, log, publish, subscribe, unsubscribe;

	var moduleData = {}, // module instances stored
		channels = {},
		debug = true;    // enable debugging feature

	register =  function(moduleId, creator) {
		if (typeof moduleId === 'string' && typeof creator === 'function') {
			if (!moduleData[moduleId]) {
				moduleData[moduleId] = {};
				moduleData[moduleId].creator = creator;
				moduleData[moduleId].instance = null;
			} else {
				log("Registration Failed: Module with moduleId '" + moduleId + "' already exists.");
			}

		} else {
			log("Registration Failed: Module '" + moduleId + "' Either moduleId or creator are of incorrect formats");
		}
	};

	startAll = function() {
		var moduleId;
		for (moduleId in moduleData) {
			console.log("isnide startAll + " + moduleId);
			if (moduleData.hasOwnProperty(moduleId)) {
				console.log("calling start");
				start(moduleId);
			}
		}
	};

	stop = function(moduleId) {
		if (typeof moduleId === 'string') {
			var data = moduleData[moduleId];
			if(data) {
				data.instance.destroy();
				data.instance = null;
			}
		} else {
			log("stop method FAILED: ModuleId is of incorrect format.");
		}
	};

	stopAll = function() {
		var moduleId;
		for (moduelId in moduleData) {
			if (moduleData.hasOwnProperty(moduleId)) {
				stop(moduleId);
			}
		}
	};

	boot = function() {
		console.log("inside boot");
		startAll();
	};
	start = function(moduleId) {
		if (typeof moduleId === 'string') {
			var data = moduleData[moduleId];
			console.log(data);
			console.log(this);
			console.log(Core);
			if(data) {
				data.instance = data.creator(new Sandbox(Core));
				data.instance.init();
			} else {
				log("Start Failed: module '" + moduleId + "' does not exist.");
			}
		} else {
			log("start method FAILED: ModuleId is of incorrect format.");
		}
	};

	debug = function(on) {
		debug = on ? true : false;
	};

	log = function(message, severity) {
		var method = (severity || severity == 1) ? 'log' : (severity == 2) ? 'warn' : 'error';
		console[method](message);
	};

	publish = function(topic, data) {
		var subscriptions = channels[topic];
		if (subscriptions) {
			for (var i = 0; i < subscriptions.length; i++) {
				var item = subscriptions[i];
				item.callback.call(item.context, data);
			}
		}
	};

	subscribe = function(topic, fn, target) {
		target = target || this;
		var subscriptions = channels[topic] || [];
		subscriptions.push({callback: fn, context: target});
		channels[topic] = subscriptions;
	};

	unsubscribe = function(topic, evt, target) {
		target = target || this;
		var subscriptions = channels[topic] || [];
		for(var index = 0; index < subscriptions.length; index++) {
			if (subscriptions[index].callback == fn && subscriptions[index].context == target){
				break;
			}
		}
		subscriptions.splice(index, 1);
		channels[topic] = subscriptions;
	};

	// dom methods
	query = function(selector, context) {
		var elems;
		if (context && context.find) {
			elems = context.find(selector);
		} else {
			elems = $(selector);
		}
		/*var ret = elems.get();
			ret.length = elems.length;*/
		return elems;
	};

	bind = function(element, evt, fn) {
		if (typeof evt === 'function') {
			fn = evt;
			evt = 'click';
		}
		$(element).bind(evt, fn);
	};

	unbind = function(element, evt, fn) {
		if (typeof evt === 'function') {
			fn = evt;
			evt = 'click';
		}
		$(element).unbind(evt, fn);
	};

	trim = function(data) {
		if (typeof data === 'string') {
			return $.trim(data);
		}else {
			throw new Error("Illegal input format in trim()");
		}
	};
	request = function(options) {

		options = options || {};
		if (options.url == undefined){
			throw new Error("Request Failed in Core: Cannot send request with undefined URL");
		}
		var method = options.method || "GET";
		var dataType = options.dataType || "json";
		$.ajax({
			url : options.url,
			type: method,
			dataType: dataType,
			success: options.success
		});
	};

	tmpl = function(template, data) {
		return $.tmpl(template, data);
	};

	return {
		register: register,
		start: start,
		stop: stop,
		startAll: startAll,
		stopAll: stopAll,
		log: log,
		debug: debug,
		publish: publish,
		subscribe: subscribe,
		unsubscribe: unsubscribe,
		boot: boot,
		dom : {
			query: query,
			bind: bind,
			unbind: unbind,
			trim: trim
		},
		request: request,
		tmpl: tmpl
	};
}());<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WebApp Tabs.
 *
 * The Initial Developer of the Original Code is
 * Dave Townsend <dtownsend@oxymoronical.com>
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

const EXPORTED_SYMBOLS = ["OverlayManager"];

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://webapptabs/modules/LogManager.jsm");
LogManager.createLogger(this, "OverlayManager");

const Cc = Components.classes;
const Ci = Components.interfaces;
const Ce = Components.Exception;
const Cr = Components.results;

const OverlayManager = {
  addOverlays: function(aOverlayList) {
    OverlayManagerInternal.addOverlays(aOverlayList);
  },

  unload: function() {
    OverlayManagerInternal.unload();
  }
};

const OverlayManagerInternal = {
  windowEntryMap: new WeakMap(),
  windowEntries: {},
  styles: {},
  scripts: {},

  init: function() {
    LOG("init");
    try {
      let windows = Services.wm.getEnumerator(null);
      while (windows.hasMoreElements()) {
        let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
        this.createWindowEntry(domWindow);
      }

      Services.wm.addListener(this);
    }
    catch (e) {
      ERROR("Exception during init", e);
    }
  },

  unload: function() {
    LOG("unload");
    try {
      Services.wm.removeListener(this);

      let windows = Services.wm.getEnumerator(null);
      while (windows.hasMoreElements()) {
        let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
        this.destroyWindowEntry(domWindow);
      }
    }
    catch (e) {
      ERROR("Exception during unload", e);
    }
  },

  createWindowEntry: function(aDOMWindow) {
    aDOMWindow.addEventListener("unload", this, false);

    let spec = aDOMWindow.location.toString();
    LOG("Creating window entry for " + spec);
    if (this.windowEntryMap.has(aDOMWindow))
      throw new Ce("Already registered window entry for " + spec);

    if (!(spec in this.windowEntries))
      this.windowEntries[spec] = [];

    let newEntry = {
      window: aDOMWindow,
      scripts: [],
      styles: [],
    };

    this.windowEntries[spec].push(newEntry);
    this.windowEntryMap.set(aDOMWindow, newEntry);

    if (spec in this.styles) {
      this.styles[spec].forEach(function(aStyleURL) {
        this.loadStyleOverlay(newEntry, aStyleURL);
      }, this);
    }

    if (spec in this.scripts) {
      this.scripts[spec].forEach(function(aScriptURL) {
        this.loadScriptOverlay(newEntry, aScriptURL);
      }, this);
    }
  },

  destroyWindowEntry: function(aDOMWindow) {
    aDOMWindow.removeEventListener("unload", this, false);

    let spec = aDOMWindow.location.toString();
    LOG("Destroying window entry for " + spec);
    if (!(spec in this.windowEntries) || !this.windowEntryMap.has(aDOMWindow))
      throw new Ce("Missing window entry for " + spec);

    let windowEntry = this.windowEntryMap.get(aDOMWindow);
    this.windowEntryMap.delete(aDOMWindow);

    this.unloadStyleOverlays(windowEntry);
    this.unloadScriptOverlays(windowEntry);

    let pos = this.windowEntries[spec].indexOf(windowEntry);
    if (pos == -1)
      throw new Ce("Missing window entry for " + spec);

    this.windowEntries[spec].splice(pos, 1);
  },

  loadStyleOverlay: function(aWindowEntry, aStyleURL) {
    LOG("Loading style overlay " + aStyleURL);

    let styleNode = aWindowEntry.window.document.createElementNS("http://www.w3.org/1999/xhtml", "link");
    styleNode.setAttribute("rel", "stylesheet");
    styleNode.setAttribute("type", "text/css");
    styleNode.setAttribute("href", aStyleURL);
    styleNode.setAttribute("style", "display: none");
    aWindowEntry.window.document.documentElement.appendChild(styleNode);

    aWindowEntry.styles.push(styleNode);
  },

  unloadStyleOverlays: function(aWindowEntry) {
    aWindowEntry.styles.forEach(function(aStyleNode) {
      aStyleNode.parentNode.removeChild(aStyleNode);
    }, this);

    aWindowEntry.styles = [];
  },

  loadScriptOverlay: function(aWindowEntry, aScriptURL) {
    LOG("Loading script overlay " + aScriptURL);

    let sandbox = Components.utils.Sandbox(aWindowEntry.window, {
      sandboxName: aScriptURL,
      sandboxPrototype: aWindowEntry.window
    });

    try {
      Components.utils.evalInSandbox(
        "Components.classes['@mozilla.org/moz/jssubscript-loader;1']" +
                  ".createInstance(Components.interfaces.mozIJSSubScriptLoader)" +
                  ".loadSubScript('" + aScriptURL + "');", sandbox, "ECMAv5");

      if ("OverlayListener" in sandbox && "load" in sandbox.OverlayListener)
        sandbox.OverlayListener.load();
    }
    catch (e) {
      WARN("Exception loading script overlay " + aScriptURL, e);
    }

    aWindowEntry.scripts.push(sandbox);
  },

  unloadScriptOverlays: function(aWindowEntry) {
    aWindowEntry.scripts.forEach(function(aSandbox) {
      if ("OverlayListener" in aSandbox && "unload" in aSandbox.OverlayListener)
        aSandbox.OverlayListener.unload();
    }, this);

    aWindowEntry.scripts = [];
  },

  addOverlays: function(aOverlayList) {
    try {
      for (windowURL in aOverlayList) {
        let windows = [];
        if (windowURL in this.windowEntries)
          windows = this.windowEntries[windowURL];

        aOverlayList[windowURL].styles.forEach(function(aStyleURL) {
          if (!(windowURL in this.styles))
            this.styles[windowURL] = [];

          this.styles[windowURL].push(aStyleURL);

          windows.forEach(function(aWindowEntry) {
            this.loadStyleOverlay(aWindowEntry, aStyleURL);
          }, this);
        }, this);

        aOverlayList[windowURL].scripts.forEach(function(aScriptURL) {
          if (!(windowURL in this.scripts))
            this.scripts[windowURL] = [];

          this.scripts[windowURL].push(aScriptURL);

          windows.forEach(function(aWindowEntry) {
            this.loadScriptOverlay(aWindowEntry, aScriptURL);
          }, this);
        }, this);
      }
    }
    catch (e) {
      ERROR("Exception adding overlay list", e);
    }
  },

  // nsIEventListener implementation
  handleEvent: function(aEvent) {
    try {
      let domWindow = aEvent.currentTarget;

      switch (aEvent.type) {
      case "load":
        domWindow.removeEventListener("load", this, false);
        OverlayManagerInternal.createWindowEntry(domWindow);
        break;
      case "unload":
        OverlayManagerInternal.destroyWindowEntry(domWindow);
        break;
      }
    }
    catch (e) {
      ERROR("Error during window " + aEvent.type, e);
    }
  },

  // nsIWindowMediatorListener implementation
  onOpenWindow: function(aXULWindow) {
    let domWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowInternal);

    domWindow.addEventListener("load", this, false);
  },

  onWindowTitleChange: function() { },
  onCloseWindow: function() { },
};

OverlayManagerInternal.init();
<commit_msg>Only keep references to windows that we actually have overlays for<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WebApp Tabs.
 *
 * The Initial Developer of the Original Code is
 * Dave Townsend <dtownsend@oxymoronical.com>
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

const EXPORTED_SYMBOLS = ["OverlayManager"];

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://webapptabs/modules/LogManager.jsm");
LogManager.createLogger(this, "OverlayManager");

const Cc = Components.classes;
const Ci = Components.interfaces;
const Ce = Components.Exception;
const Cr = Components.results;

const OverlayManager = {
  addOverlays: function(aOverlayList) {
    OverlayManagerInternal.addOverlays(aOverlayList);
  },

  unload: function() {
    OverlayManagerInternal.unload();
  }
};

const OverlayManagerInternal = {
  windowEntryMap: new WeakMap(),
  windowEntries: {},
  overlays: {},

  init: function() {
    LOG("init");
    Services.wm.addListener(this);
  },

  unload: function() {
    LOG("unload");
    try {
      Services.wm.removeListener(this);

      for (let windowURL in this.windowEntries) {
        this.windowEntries[windowURL].forEach(function(aWindowEntry) {
          this.destroyWindowEntry(aWindowEntry);
        }, this);
      }
    }
    catch (e) {
      ERROR("Exception during unload", e);
    }
  },

  createWindowEntry: function(aDOMWindow, aOverlays) {
    aDOMWindow.addEventListener("unload", this, false);

    let windowURL = aDOMWindow.location.toString();
    LOG("Creating window entry for " + windowURL);
    if (this.windowEntryMap.has(aDOMWindow))
      throw new Ce("Already registered window entry for " + windowURL);

    if (!(windowURL in this.windowEntries))
      this.windowEntries[windowURL] = [];

    let newEntry = {
      window: aDOMWindow,
      scripts: [],
      nodes: [],
    };

    this.windowEntries[windowURL].push(newEntry);
    this.windowEntryMap.set(aDOMWindow, newEntry);

    this.applyWindowEntryOverlays(newEntry, aOverlays);
    return newEntry
  },

  destroyWindowEntry: function(aWindowEntry) {
    aWindowEntry.window.removeEventListener("unload", this, false);

    let windowURL = aWindowEntry.window.location.toString();
    LOG("Destroying window entry for " + windowURL);

    this.windowEntryMap.delete(aWindowEntry.window);

    aWindowEntry.scripts.forEach(function(aSandbox) {
      try {
        if ("OverlayListener" in aSandbox && "unload" in aSandbox.OverlayListener)
          aSandbox.OverlayListener.unload();
      }
      catch (e) {
        ERROR("Exception calling script unload listener", e);
      }
    }, this);
    aWindowEntry.scripts = [];

    aWindowEntry.nodes.forEach(function(aNode) {
      aNode.parentNode.removeChild(aNode);
    }, this);
    aWindowEntry.nodes = [];

    if (!(windowURL in this.windowEntries))
      throw new Ce("Missing window entry for " + windowURL);
    let pos = this.windowEntries[windowURL].indexOf(aWindowEntry);
    if (pos == -1)
      throw new Ce("Missing window entry for " + windowURL);

    this.windowEntries[windowURL].splice(pos, 1);
    if (this.windowEntries[windowURL].length == 0)
      delete this.windowEntries[windowURL];
  },

  applyWindowEntryOverlays: function(aWindowEntry, aOverlays) {
    if ("styles" in aOverlays) {
      aOverlays.styles.forEach(function(aStyleURL) {
        this.loadStyleOverlay(aWindowEntry, aStyleURL);
      }, this);
    }

    if ("scripts" in aOverlays) {
      aOverlays.scripts.forEach(function(aScriptURL) {
        this.loadScriptOverlay(aWindowEntry, aScriptURL);
      }, this);
    }
  },

  loadStyleOverlay: function(aWindowEntry, aStyleURL) {
    LOG("Loading style overlay " + aStyleURL);

    let styleNode = aWindowEntry.window.document.createElementNS("http://www.w3.org/1999/xhtml", "link");
    styleNode.setAttribute("rel", "stylesheet");
    styleNode.setAttribute("type", "text/css");
    styleNode.setAttribute("href", aStyleURL);
    styleNode.setAttribute("style", "display: none");
    aWindowEntry.window.document.documentElement.appendChild(styleNode);

    aWindowEntry.nodes.push(styleNode);
  },

  loadScriptOverlay: function(aWindowEntry, aScriptURL) {
    LOG("Loading script overlay " + aScriptURL);

    let sandbox = Components.utils.Sandbox(aWindowEntry.window, {
      sandboxName: aScriptURL,
      sandboxPrototype: aWindowEntry.window
    });

    try {
      Components.utils.evalInSandbox(
        "Components.classes['@mozilla.org/moz/jssubscript-loader;1']" +
                  ".createInstance(Components.interfaces.mozIJSSubScriptLoader)" +
                  ".loadSubScript('" + aScriptURL + "');", sandbox, "ECMAv5");

      if ("OverlayListener" in sandbox && "load" in sandbox.OverlayListener)
        sandbox.OverlayListener.load();
    }
    catch (e) {
      WARN("Exception loading script overlay " + aScriptURL, e);
    }

    aWindowEntry.scripts.push(sandbox);
  },

  addOverlays: function(aOverlayList) {
    try {
      // First check over the new overlays, merge them into the master list
      // and if any are for already tracked windows apply them
      for (windowURL in aOverlayList) {
        let newOverlays = aOverlayList[windowURL];

        if (!(windowURL in this.overlays))
          this.overlays[windowURL] = {};
        let existingOverlays = this.overlays[windowURL];

        ["styles", "scripts"].forEach(function(aType) {
          if (!(aType in newOverlays))
            return;

          if (!(aType in existingOverlays))
            existingOverlays[aType] = newOverlays[aType].slice(0);
          else
            existingOverlays[aType].push(newOverlays[aType]);
        }, this);

        // Apply the new overlays to any already tracked windows
        if (windowURL in this.windowEntries) {
          this.windowEntries[windowURL].forEach(function(aWindowEntry) {
            this.applyWindowEntryOverlays(aWindowEntry, newOverlays);
          }, this);
        }
      }

      // Search over existing windows to see if any need to be tracked now
      let windows = Services.wm.getEnumerator(null);
      while (windows.hasMoreElements()) {
        let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
        let windowURL = domWindow.location.toString();

        // If we are adding overlays for this window and not already tracking
        // this window then start to track it and add the new overlays
        if ((windowURL in aOverlayList) && !(windowURL in this.windowEntries)) {
          let windowEntry = this.createWindowEntry(domWindow, aOverlayList[windowURL]);
        }
      }
    }
    catch (e) {
      ERROR("Exception adding overlay list", e);
    }
  },

  // nsIEventListener implementation
  handleEvent: function(aEvent) {
    try {
      let domWindow = aEvent.currentTarget;

      switch (aEvent.type) {
      case "load":
        domWindow.removeEventListener("load", this, false);
        OverlayManagerInternal.createWindowEntry(domWindow);
        break;
      case "unload":
        if (!this.windowEntryMap.has(domWindow)) {
          ERROR("Saw unload event for unknown window " + domWindow.location);
          return;
        }
        let windowEntry = this.windowEntryMap.get(aDOMWindow);
        OverlayManagerInternal.destroyWindowEntry(windowEntry);
        break;
      }
    }
    catch (e) {
      ERROR("Error during window " + aEvent.type, e);
    }
  },

  // nsIWindowMediatorListener implementation
  onOpenWindow: function(aXULWindow) {
    let domWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowInternal);

    let windowURL = domWindow.location.toString();
    // If this is a window we have overlays for then wait for it to load
    if (windowURL in this.overlays)
      domWindow.addEventListener("load", this, false);
  },

  onWindowTitleChange: function() { },
  onCloseWindow: function() { },
};

OverlayManagerInternal.init();
<|endoftext|>


<commit_before>Ext.define('sunerp.view.Viewport', {
    extend: 'Ext.container.Viewport',
    requires: [
        'Ext.tab.Panel',
        'Ext.layout.container.Border',
        'sunerp.view.Header',
        'sunerp.view.LeftPanel',
        'sunerp.view.ContentPanel',
        'sunerp.layout.Center',
        'sunerp.controller.NavigationCtr'
    ],

    controller: "sunerp.controller.NavigationCtr",

    layout: 'border',

    items: [
        {
            region: 'north',
            xtype: 'appHeader'
        },
        {
            region: 'west',
            xtype: 'navigation',
            width: 230,
            minWidth: 100,
            height: 200,
            split: true,
            stateful: true,
            stateId: 'mainnav.west',
            collapsible: true,
            tools: [
                {
                    type: 'gear',
                    regionTool: true
                }
            ]
        },
        {
            region: 'center',
            xtype: 'contentPanel',
            id: 'content-panel',
            layout: 'ux.center',
            items: [
                {
                    border: false,
                    width: 200,
                    height: '30%', // assign 90% of the container height to the item
                    html: '
' +
                        '' +
                        '
Công ty TNHH SunriseSoft
' +
                        '
Website: http://sunrisesoft.vn
' +
                        '
',
                    bodyStyle: 'background-image: url(/assets/img/square.gif)'
                }
            ]
        }
    ]
});<commit_msg>Fix can see website on small screen.<commit_after>Ext.define('sunerp.view.Viewport', {
    extend: 'Ext.container.Viewport',
    requires: [
        'Ext.tab.Panel',
        'Ext.layout.container.Border',
        'sunerp.view.Header',
        'sunerp.view.LeftPanel',
        'sunerp.view.ContentPanel',
        'sunerp.layout.Center',
        'sunerp.controller.NavigationCtr'
    ],

    controller: "sunerp.controller.NavigationCtr",

    layout: 'border',

    items: [
        {
            region: 'north',
            xtype: 'appHeader'
        },
        {
            region: 'west',
            xtype: 'navigation',
            width: 230,
            minWidth: 100,
            height: 200,
            split: true,
            stateful: true,
            stateId: 'mainnav.west',
            collapsible: true,
            tools: [
                {
                    type: 'gear',
                    regionTool: true
                }
            ]
        },
        {
            region: 'center',
            xtype: 'contentPanel',
            id: 'content-panel',
            layout: 'ux.center',
            items: [
                {
                    border: false,
                    width: 200,
                    height: '35%', // assign 90% of the container height to the item
                    html: '
' +
                        '' +
                        '
Công ty TNHH SunriseSoft
' +
                        '
Website: http://sunrisesoft.vn
' +
                        '
',
                    bodyStyle: 'background-image: url(/assets/img/square.gif)'
                }
            ]
        }
    ]
});<|endoftext|>


<commit_before>/**
 * Module dependencies
 */
var anchor = require('anchor'),
  _ = require('lodash');

/**
 * Left outer join
 *
 * Return a result set with data from right and left
 * merged on rightKey===leftKey, where t.e. at least one
 * entry for each row of left (unmatched columns in right are null).
 *
 * @option {Array} left    [rows from the "lefthand table"]
 * @option {Array} right   [rows from the "righthand table"]
 * @option {String} leftKey     [primary key of the "lefthand table"]
 * @option {String} rightKey     [foreign key from the "righthand table" to the "lefthand table"]
 * @return {Array}          [a new array of joined row data]
 *
 * @throws {Error} on invalid input
 * @synchronous
 */
module.exports = function _leftOuterJoin (options) {

  // Usage
  var invalid = false;
  invalid = invalid || anchor(options).to({ type: 'object' });
  invalid = invalid || anchor(options.left).to({ type: 'array' });
  invalid = invalid || anchor(options.right).to({ type: 'array' });
  invalid = invalid || anchor(options.leftKey).to({ type: 'string' });
  invalid = invalid || anchor(options.rightKey).to({ type: 'string' });
  if (invalid) throw invalid;

  return _.reduce(options.left, function (memo, leftRow) {

      // For each rightRow whose childKey matches
      // this leftRow's parentKey...
    var foundMatch = _.reduce(options.right, function (hasFoundMatchYet, rightRow) {

      var newRow = partial_leftOuterJoin({
        leftRow: leftRow,
        rightRow: rightRow,
        leftKey: options.leftKey,
        rightKey: options.rightKey
      });

      // Save the new row for the join result if it exists
      // and mark the match as found
      if (newRow){
        memo.push(newRow);
        return true;
      }
      return hasFoundMatchYet;
    }, false);

    // If we didn't find a match for this leftRow,
    // go ahead and add it to the result set anyways.
    if (!foundMatch) {
      memo.push(_.cloneDeep(leftRow));
    }

    return memo;
  }, []);

};

/**
 * partial_leftOuterJoin
 *
 * Return a result set with data from rightRow and leftRow,
 * merged on rightKey===leftKey, where t.e. at least one
 * entry for each original leftRow (unmatched columns are null).
 *
 * @param  {[type]} options
 * @return {Object|False}   If false, don't save the join row.
 */
function partial_leftOuterJoin (options) {
  // Usage
  var invalid = false;
  invalid = invalid || anchor(options).to({ type: 'object' });
  invalid = invalid || anchor(options.leftKey).to({ type: 'string' });
  invalid = invalid || anchor(options.rightKey).to({ type: 'string' });
  invalid = invalid || anchor(options.leftRow).to({ type: 'object' });
  invalid = invalid || anchor(options.rightRow).to({ type: 'object' });
  if (invalid) throw invalid;

  // Doesn't match
  if (
    options.rightRow[options.rightKey] !==
    options.leftRow[options.leftKey]
    ) {
    return false;
  }

  // deep clone it, then prune `rightKey` from the copy.
  var newJoinRow = _.cloneDeep(options.rightRow);
  delete newJoinRow[options.rightKey];

  // Merge values from current leftRow into the copy.
  _.merge(newJoinRow, options.leftRow);

  // Return the newly joined row.
  return newJoinRow;
}
<commit_msg>A bit more exposition on limitations of streaming computation of joins, and added a caveat about memory-usage optimization for in-memory joins.<commit_after>/**
 * Module dependencies
 */
var anchor = require('anchor'),
  _ = require('lodash');

/**
 * Left outer join
 *
 * Return a result set with data from right and left
 * merged on rightKey===leftKey, where t.e. at least one
 * entry for each row of left (unmatched columns in right are null).
 *
 * @option {Array} left    [rows from the "lefthand table"]
 * @option {Array} right   [rows from the "righthand table"]
 * @option {String} leftKey     [primary key of the "lefthand table"]
 * @option {String} rightKey     [foreign key from the "righthand table" to the "lefthand table"]
 * @return {Array}          [a new array of joined row data]
 *
 * @throws {Error} on invalid input
 * @synchronous
 */
module.exports = function _leftOuterJoin (options) {

  // Usage
  var invalid = false;
  invalid = invalid || anchor(options).to({ type: 'object' });
  invalid = invalid || anchor(options.left).to({ type: 'array' });
  invalid = invalid || anchor(options.right).to({ type: 'array' });
  invalid = invalid || anchor(options.leftKey).to({ type: 'string' });
  invalid = invalid || anchor(options.rightKey).to({ type: 'string' });
  if (invalid) throw invalid;

  return _.reduce(options.left, function (memo, leftRow) {

      // For each rightRow whose childKey matches
      // this leftRow's parentKey...
    var foundMatch = _.reduce(options.right, function (hasFoundMatchYet, rightRow) {

      var newRow = partial_leftOuterJoin({
        leftRow: leftRow,
        rightRow: rightRow,
        leftKey: options.leftKey,
        rightKey: options.rightKey
      });

      // Save the new row for the join result if it exists
      // and mark the match as found
      if (newRow){
        memo.push(newRow);
        return true;
      }
      return hasFoundMatchYet;
    }, false);

    // If we didn't find a match for this leftRow,
    // go ahead and add it to the result set anyways.
    if (!foundMatch) {
      memo.push(_.cloneDeep(leftRow));
    }

    return memo;
  }, []);

};

/**
 * partial_leftOuterJoin
 *
 * Check whether two rows match on the specified keys,
 * and if they do, merge `leftRow` into a copy of `rightRow`.
 * (omit `rightRow`'s foreign key, since it === `leftRow`'s PK)
 *
 * Hypothetically, this function could be operated by a stream,
 * but in the case of a left outer join, at least, the final
 * result set cannot be accurately known until both the complete
 * contents of both the `left` and `right` data set have been checked.
 *
 * An optimization from polynomial to logarithmic computational
 * complexity could potentially be achieved by taking advantage
 * of the known L[k..l] and R[m..n] values as each new L[i] or R[j]
 * arrives from a stream, but a comparably-sized cache would have to
 * be maintained, so we'd still be stuck with polynomial memory usage.
 * i.eO(|R|*|L|)
 *
 * Still worth investigating as an computational optimization though,
 * since this is a hot code path for cross-adapter joins.
 *
 * @param  {Object} options
 * @return {Object|False}   If false, don't save the join row.
 */
function partial_leftOuterJoin (options) {
  // Usage
  var invalid = false;
  invalid = invalid || anchor(options).to({ type: 'object' });
  invalid = invalid || anchor(options.leftKey).to({ type: 'string' });
  invalid = invalid || anchor(options.rightKey).to({ type: 'string' });
  invalid = invalid || anchor(options.leftRow).to({ type: 'object' });
  invalid = invalid || anchor(options.rightRow).to({ type: 'object' });
  if (invalid) throw invalid;

  // Doesn't match
  if (
    options.rightRow[options.rightKey] !==
    options.leftRow[options.leftKey]
    ) {
    return false;
  }

  // deep clone it, then prune `rightKey` from the copy.
  var newJoinRow = _.cloneDeep(options.rightRow);
  delete newJoinRow[options.rightKey];

  // Merge values from current leftRow into the copy.
  _.merge(newJoinRow, options.leftRow);

  // Return the newly joined row.
  return newJoinRow;
}
<|endoftext|>


<commit_before>describe('ng-mfb', function() {

  var $compile,
      $rootScope;

  beforeEach(module('ng-mfb'));

  beforeEach(inject(function(_$compile_, _$rootScope_){
    $compile = _$compile_;
    $rootScope = _$rootScope_;
  }));

  /**
   * This directive uses transclusion so for testing purposes we have to
   * wrap the tested element in a wrapping div and then fetch its
   * contents. See:
   * Testing Transclusion Directives @ https://docs.angularjs.org/guide/unit-testing
   *
   **/
  function compile( template ){
    var node = $compile('
' + template + '
')($rootScope);
    return node.contents();
  }

  describe('the directive', function(){

    it('should properly transclude the div element', function(){
      var tpl = '
' +
                '  ' +
                '
';

      var node = compile(tpl);
      $rootScope.$digest();

      expect(node.hasClass('mfb-component--tr')).toBeTruthy();
      expect(node.find('ul').hasClass('mfb-component__list')).toBeTruthy();
    });

    it('should be possibile to be instantiated as an attribute', function(){
        var tpl = '

',
            node = compile(tpl);
        $rootScope.$digest();

        expect(node.parent().find('ul')).toBeTruthy();
    });

    it('should be possibile to be instantiated as an element', function(){
        var tpl = '<mfb-menu></mfb-menu>',
            node = compile(tpl);
        $rootScope.$digest();

        expect(node.parent().find('mfb-menu')).toBeTruthy();
    });

  });

  describe('when passing an option to the position attribute', function(){

    var node;

    function generateTpl( pos ){
      var tpl = '

';

      node = compile(tpl);
      $rootScope.$digest();
    }

    it('should place the menu on top right if "tr" is passed', function(){
      generateTpl('tr');
      expect(node.hasClass('mfb-component--tr')).toBeTruthy();
    });

    it('should place the menu on top left if "tl" is passed', function(){
      generateTpl('tl');
      expect(node.hasClass('mfb-component--tl')).toBeTruthy();
    });

    it('should place the menu on bottom right if "br" is passed', function(){
      generateTpl('br');
      expect(node.hasClass('mfb-component--br')).toBeTruthy();
    });

    it('should place the menu on top left if "bl" is passed', function(){
      generateTpl('bl');
      expect(node.hasClass('mfb-component--bl')).toBeTruthy();
    });
  });

  describe('when passing an option to the effect attribute', function(){

    it('should assign the correspondig animation class', function(){

      var tpl = '

';

      var node = compile(tpl);
      $rootScope.$digest();

      expect(node.hasClass('mfb-zoomin')).toBeTruthy();
    });
  });

  describe('when passing some text to the label attribute', function(){

    it('should assign this text to the main button "data-mfb-label" attribute', function(){

      var label = 'some text',
          tpl = '

',
          node = compile(tpl),
          main_button;

      $rootScope.$digest();

      var main_button = node.find('a').eq(0);
      expect(main_button.attr('data-mfb-label')).toBe(label);
    });
  });

  describe('when passing values to the icons attributes', function() {

    var main_button;

    beforeEach(function(){
      var tpl = '

',
          node = compile(tpl);

      $rootScope.$digest();

      main_button = node.find('a').eq(0);
    });

    it('should assign the right class for the icon shown at rest', function() {
      var icon1 = main_button.find('i').eq(0);
      expect( icon1.hasClass('mfb-component__main-icon--resting') ).toBeTruthy();
      expect( icon1.hasClass('ion-plus-round') ).toBeTruthy();
    });

    it('should assign the right class for the icon shown for active state', function() {
      var icon2 = main_button.find('i').eq(1);
      expect( icon2.hasClass('mfb-component__main-icon--active') ).toBeTruthy();
      expect( icon2.hasClass('ion-edit') ).toBeTruthy();
    });
  });

  describe('number of child buttons should be the right one', function() {

    it('if manually specified by the user', function() {
      var tpl = '
' +
                '  ' +
                '  ' +
                '  ' +
                '
',
          node = compile(tpl),
          buttons;

      $rootScope.$digest();

      buttons = node.find('ul').children();

      expect(buttons.length).toEqual(3);

    });

    it('if specified through an ng-repeat', function() {

      $rootScope.buttons = [{
          label: 'a link',
          icon: 'ion-paper-airplane'
        },{
          label: 'a link',
          icon: 'ion-paper-airplane'
        },{
          label: 'a link',
          icon: 'ion-paper-airplane'
        }];

      var tpl = '
' +
                '  ' +
                '
',
          node = compile(tpl),
          buttons;

      $rootScope.$digest();

      buttons = node.find('ul').children();

      expect(buttons.length).toEqual(3);

    });
  });

});<commit_msg>fix jshint error in tests<commit_after>describe('ng-mfb', function() {

  var $compile,
      $rootScope;

  beforeEach(module('ng-mfb'));

  beforeEach(inject(function(_$compile_, _$rootScope_){
    $compile = _$compile_;
    $rootScope = _$rootScope_;
  }));

  /**
   * This directive uses transclusion so for testing purposes we have to
   * wrap the tested element in a wrapping div and then fetch its
   * contents. See:
   * Testing Transclusion Directives @ https://docs.angularjs.org/guide/unit-testing
   *
   **/
  function compile( template ){
    var node = $compile('
' + template + '
')($rootScope);
    return node.contents();
  }

  describe('the directive', function(){

    it('should properly transclude the div element', function(){
      var tpl = '
' +
                '  ' +
                '
';

      var node = compile(tpl);
      $rootScope.$digest();

      expect(node.hasClass('mfb-component--tr')).toBeTruthy();
      expect(node.find('ul').hasClass('mfb-component__list')).toBeTruthy();
    });

    it('should be possibile to be instantiated as an attribute', function(){
        var tpl = '

',
            node = compile(tpl);
        $rootScope.$digest();

        expect(node.parent().find('ul')).toBeTruthy();
    });

    it('should be possibile to be instantiated as an element', function(){
        var tpl = '<mfb-menu></mfb-menu>',
            node = compile(tpl);
        $rootScope.$digest();

        expect(node.parent().find('mfb-menu')).toBeTruthy();
    });

  });

  describe('when passing an option to the position attribute', function(){

    var node;

    function generateTpl( pos ){
      var tpl = '

';

      node = compile(tpl);
      $rootScope.$digest();
    }

    it('should place the menu on top right if "tr" is passed', function(){
      generateTpl('tr');
      expect(node.hasClass('mfb-component--tr')).toBeTruthy();
    });

    it('should place the menu on top left if "tl" is passed', function(){
      generateTpl('tl');
      expect(node.hasClass('mfb-component--tl')).toBeTruthy();
    });

    it('should place the menu on bottom right if "br" is passed', function(){
      generateTpl('br');
      expect(node.hasClass('mfb-component--br')).toBeTruthy();
    });

    it('should place the menu on top left if "bl" is passed', function(){
      generateTpl('bl');
      expect(node.hasClass('mfb-component--bl')).toBeTruthy();
    });
  });

  describe('when passing an option to the effect attribute', function(){

    it('should assign the correspondig animation class', function(){

      var tpl = '

';

      var node = compile(tpl);
      $rootScope.$digest();

      expect(node.hasClass('mfb-zoomin')).toBeTruthy();
    });
  });

  describe('when passing some text to the label attribute', function(){

    it('should assign this text to the main button "data-mfb-label" attribute', function(){

      var label = 'some text',
          tpl = '

',
          node = compile(tpl),
          main_button;

      $rootScope.$digest();

      main_button = node.find('a').eq(0);
      expect(main_button.attr('data-mfb-label')).toBe(label);
    });
  });

  describe('when passing values to the icons attributes', function() {

    var main_button;

    beforeEach(function(){
      var tpl = '

',
          node = compile(tpl);

      $rootScope.$digest();

      main_button = node.find('a').eq(0);
    });

    it('should assign the right class for the icon shown at rest', function() {
      var icon1 = main_button.find('i').eq(0);
      expect( icon1.hasClass('mfb-component__main-icon--resting') ).toBeTruthy();
      expect( icon1.hasClass('ion-plus-round') ).toBeTruthy();
    });

    it('should assign the right class for the icon shown for active state', function() {
      var icon2 = main_button.find('i').eq(1);
      expect( icon2.hasClass('mfb-component__main-icon--active') ).toBeTruthy();
      expect( icon2.hasClass('ion-edit') ).toBeTruthy();
    });
  });

  describe('number of child buttons should be the right one', function() {

    it('if manually specified by the user', function() {
      var tpl = '
' +
                '  ' +
                '  ' +
                '  ' +
                '
',
          node = compile(tpl),
          buttons;

      $rootScope.$digest();

      buttons = node.find('ul').children();

      expect(buttons.length).toEqual(3);

    });

    it('if specified through an ng-repeat', function() {

      $rootScope.buttons = [{
          label: 'a link',
          icon: 'ion-paper-airplane'
        },{
          label: 'a link',
          icon: 'ion-paper-airplane'
        },{
          label: 'a link',
          icon: 'ion-paper-airplane'
        }];

      var tpl = '
' +
                '  ' +
                '
',
          node = compile(tpl),
          buttons;

      $rootScope.$digest();

      buttons = node.find('ul').children();

      expect(buttons.length).toEqual(3);

    });
  });

});<|endoftext|>


<commit_before>var _ = require('underscore');
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var tm = require('../lib/tm');
var source = require('../lib/source');
var tilelive = require('tilelive');

describe('source', function() {

var tmppath = '/tmp/tm2-test-' + +new Date;
before(function(done) {
    tm.config({
        db: path.join(tmppath, 'app.db'),
        cache: path.join(tmppath, 'cache')
    }, done);
});
after(function(done) {
    try { fs.unlinkSync(path.join(tmppath, 'app.db')); } catch(err) {}
    try { fs.rmdirSync(path.join(tmppath, 'cache')); } catch(err) {}
    try { fs.rmdirSync(tmppath); } catch(err) {}
    done();
});

describe('source util', function() {
    it('normalize', function() {
        var n = source.normalize({
            id: 'tmsource://' + __dirname + '/fixtures-localsource',
            Layer: [{
                id: 'box',
                fields: {
                    Id: 'Valid helptext for a field',
                    missing: 'Invalid helptext no field named missing'
                },
                Datasource: {
                    type: 'shape',
                    file: __dirname + '/fixtures-localsource/10m-900913-bounding-box.shp',
                    bogus: 'true'
                }
            }]
        });
        assert.deepEqual(n.Layer.length, 1);
        assert.deepEqual(n.vector_layers.length, 1);
        assert.deepEqual(n.vector_layers[0].fields, {'Id':'Valid helptext for a field'},
            'Populates field help');
        assert.deepEqual(Object.keys(tm.sortkeys(n.Layer[0])), ['id','Datasource','description','fields','properties','srs'],
            'Populates deep defaults in Layer objects');
        assert.deepEqual(Object.keys(tm.sortkeys(n.Layer[0].Datasource)), ['file','type'],
            'Strips invalid datasource properties based on type');
        // @TODO check postgis auto srs extent generation ... without postgis.
    });
});

describe('source remote', function() {
    it('loads', function(done) {
        source('mapbox:///mapbox.mapbox-streets-v2', function(err, source) {
            assert.ifError(err);
            assert.equal('MapBox Streets V2', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(14, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('loads via tilelive', function(done) {
        tilelive.load('mapbox:///mapbox.mapbox-streets-v2', function(err, source) {
            assert.ifError(err);
            assert.equal('MapBox Streets V2', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(14, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('error bad protocol', function(done) {
        source('invalid://www.google.com', function(err, source) {
            assert.ok(err);
            assert.equal('Invalid source protocol', err.message);
            done();
        });
    });
    it('noop remote write', function(done) {
        source.save({id:'mapbox:///mapbox.mapbox-streets-v2'}, function(err, source) {
            assert.ifError(err);
            done();
        });
    });
});

describe('source local', function() {
    var tmp = '/tmp/tm2-source-' + (+new Date);
    var data = {
        name: 'Test source',
        attribution: '© John Doe 2013.',
        minzoom: 0,
        maxzoom: 6,
        Layer: [ {
            id: 'box',
            name: 'box',
            description: '',
            srs: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over',
            properties: {
                'buffer-size': 0,
                minzoom: 0,
                maxzoom: 6
            },
            Datasource: {
                file: __dirname + '/fixtures-localsource/10m-900913-bounding-box.shp',
                type: 'shape'
            }
        } ]
    };
    after(function(done) {
        setTimeout(function() {
            ['data.xml','data.yml'].forEach(function(file) {
                try { fs.unlinkSync(tmp + '/' + file) } catch(err) {};
            });
            try { fs.rmdirSync(tmp) } catch(err) {};
            try { fs.unlinkSync(tmp + '.tm2z') } catch(err) {};
            done();
        }, 250);
    });
    it('loads', function(done) {
        source('tmsource://' + __dirname + '/fixtures-localsource', function(err, source) {
            assert.ifError(err);
            assert.equal('Test source', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(6, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('loads via tilelive', function(done) {
        tilelive.load('tmsource://' + __dirname + '/fixtures-localsource', function(err, source) {
            assert.ifError(err);
            assert.equal('Test source', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(6, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('saves source in memory', function(done) {
        source.save(_({id:'tmsource:///tmp-12345678'}).defaults(data), function(err, source) {
            assert.ifError(err);
            assert.ok(source);
            done();
        });
    });
    it('saves source to disk', function(done) {
        source.save(_({id:'tmsource://' + tmp}).defaults(data), function(err, source) {
            assert.ifError(err);
            assert.ok(source);
            assert.ok(/maxzoom: 6/.test(fs.readFileSync(tmp + '/data.yml', 'utf8')), 'saves data.yml');
            assert.ok(/<Map srs/.test(fs.readFileSync(tmp + '/data.xml', 'utf8')), 'saves data.xml');
            // This setTimeout is here because thumbnail generation on save
            // is an optimistic operation (e.g. callback does not wait for it
            // to complete).
            setTimeout(function() {
                assert.ok(fs.existsSync(tmp + '/.thumb.png'), 'saves thumb');
                done();
            }, 1000);
        });
    });
});

});
<commit_msg>test for http and https source protocols<commit_after>var _ = require('underscore');
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var tm = require('../lib/tm');
var source = require('../lib/source');
var tilelive = require('tilelive');

describe('source', function() {

var tmppath = '/tmp/tm2-test-' + +new Date;
before(function(done) {
    tm.config({
        db: path.join(tmppath, 'app.db'),
        cache: path.join(tmppath, 'cache')
    }, done);
});
after(function(done) {
    try { fs.unlinkSync(path.join(tmppath, 'app.db')); } catch(err) {}
    try { fs.rmdirSync(path.join(tmppath, 'cache')); } catch(err) {}
    try { fs.rmdirSync(tmppath); } catch(err) {}
    done();
});

describe('source util', function() {
    it('normalize', function() {
        var n = source.normalize({
            id: 'tmsource://' + __dirname + '/fixtures-localsource',
            Layer: [{
                id: 'box',
                fields: {
                    Id: 'Valid helptext for a field',
                    missing: 'Invalid helptext no field named missing'
                },
                Datasource: {
                    type: 'shape',
                    file: __dirname + '/fixtures-localsource/10m-900913-bounding-box.shp',
                    bogus: 'true'
                }
            }]
        });
        assert.deepEqual(n.Layer.length, 1);
        assert.deepEqual(n.vector_layers.length, 1);
        assert.deepEqual(n.vector_layers[0].fields, {'Id':'Valid helptext for a field'},
            'Populates field help');
        assert.deepEqual(Object.keys(tm.sortkeys(n.Layer[0])), ['id','Datasource','description','fields','properties','srs'],
            'Populates deep defaults in Layer objects');
        assert.deepEqual(Object.keys(tm.sortkeys(n.Layer[0].Datasource)), ['file','type'],
            'Strips invalid datasource properties based on type');
        // @TODO check postgis auto srs extent generation ... without postgis.
    });
});

describe('source remote', function() {
    it('loads', function(done) {
        source('mapbox:///mapbox.mapbox-streets-v2', function(err, source) {
            assert.ifError(err);
            assert.equal('MapBox Streets V2', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(14, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('loads via tilelive', function(done) {
        tilelive.load('mapbox:///mapbox.mapbox-streets-v2', function(err, source) {
            assert.ifError(err);
            assert.equal('MapBox Streets V2', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(14, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('loads via http', function (done) {
        source('http://a.tiles.mapbox.com/v3/mapbox.mapbox-streets-v4.json', function (err, source) {
            assert.ifError(err);
            assert.equal('MapBox Streets V4', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(14, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('loads via https', function (done) {
        source('https://a.tiles.mapbox.com/v3/mapbox.mapbox-streets-v4.json', function (err, source) {
            assert.ifError(err);
            assert.equal('MapBox Streets V4', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(14, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('error bad protocol', function(done) {
        source('invalid://www.google.com', function(err, source) {
            assert.ok(err);
            assert.equal('Invalid source protocol', err.message);
            done();
        });
    });
    it('noop remote write', function(done) {
        source.save({id:'mapbox:///mapbox.mapbox-streets-v2'}, function(err, source) {
            assert.ifError(err);
            done();
        });
    });
});

describe('source local', function() {
    var tmp = '/tmp/tm2-source-' + (+new Date);
    var data = {
        name: 'Test source',
        attribution: '© John Doe 2013.',
        minzoom: 0,
        maxzoom: 6,
        Layer: [ {
            id: 'box',
            name: 'box',
            description: '',
            srs: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over',
            properties: {
                'buffer-size': 0,
                minzoom: 0,
                maxzoom: 6
            },
            Datasource: {
                file: __dirname + '/fixtures-localsource/10m-900913-bounding-box.shp',
                type: 'shape'
            }
        } ]
    };
    after(function(done) {
        setTimeout(function() {
            ['data.xml','data.yml'].forEach(function(file) {
                try { fs.unlinkSync(tmp + '/' + file) } catch(err) {};
            });
            try { fs.rmdirSync(tmp) } catch(err) {};
            try { fs.unlinkSync(tmp + '.tm2z') } catch(err) {};
            done();
        }, 250);
    });
    it('loads', function(done) {
        source('tmsource://' + __dirname + '/fixtures-localsource', function(err, source) {
            assert.ifError(err);
            assert.equal('Test source', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(6, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('loads via tilelive', function(done) {
        tilelive.load('tmsource://' + __dirname + '/fixtures-localsource', function(err, source) {
            assert.ifError(err);
            assert.equal('Test source', source.data.name);
            assert.equal(0, source.data.minzoom);
            assert.equal(6, source.data.maxzoom);
            assert.ok(!!source.style);
            done();
        });
    });
    it('saves source in memory', function(done) {
        source.save(_({id:'tmsource:///tmp-12345678'}).defaults(data), function(err, source) {
            assert.ifError(err);
            assert.ok(source);
            done();
        });
    });
    it('saves source to disk', function(done) {
        source.save(_({id:'tmsource://' + tmp}).defaults(data), function(err, source) {
            assert.ifError(err);
            assert.ok(source);
            assert.ok(/maxzoom: 6/.test(fs.readFileSync(tmp + '/data.yml', 'utf8')), 'saves data.yml');
            assert.ok(/<Map srs/.test(fs.readFileSync(tmp + '/data.xml', 'utf8')), 'saves data.xml');
            // This setTimeout is here because thumbnail generation on save
            // is an optimistic operation (e.g. callback does not wait for it
            // to complete).
            setTimeout(function() {
                assert.ok(fs.existsSync(tmp + '/.thumb.png'), 'saves thumb');
                done();
            }, 1000);
        });
    });
});

});
<|endoftext|>