let Logger = require("../library/logger");
let logger = Logger.logger;
let fs = require("fs");
let path = require("path");
let Sequelize = require("sequelize");
let uuidv4 = require("uuid/v4");
let basename = path.basename(__filename);
let Op = Sequelize.Op;
let db = {};
/**
* Passing logger.silly directly to sequelize results in a TypeError (since
* the logger seems to be uninitialized at some point). This wrapper function
* was suggested as workaround.
* @param {Object} value - Contains the actual value that should be logged to
* the transports.
*/
function logForSequelize(value) {
// Ignore DB log for now - this only logs the sql queries that were made
// console.log(value);
return;
}
let sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: "postgres",
pool: {
max: process.env.DB_MAX_CONNECTIONS,
min: process.env.DB_MIN_CONNECTIONS,
idle: 60000,
acquire: 120000,
},
operatorsAliases: false,
logging: logForSequelize,
},
);
fs
.readdirSync(__dirname)
.filter((file) => {
return (file.indexOf(".") !== 0) &&
(file !== basename) &&
file.slice(-3) === ".js";
})
.forEach((file) => {
let model = sequelize["import"](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
/**
* Executes a raw query with the provided string.
* Used to enable retries
* @param {String} queryString The SQL query string with placeholders
* that should be used for the query
* @param {Array.<String|number|boolean>} replacements Replacements for the
* placeholders
* @return {Object} Returns the raw returnvalue of the db
*/
async function executeQuery(queryString, replacements) {
let result = await db.sequelize.query(
queryString,
{replacements: replacements}
).catch((err) => {
logger.error(err.message);
logger.error(err.stack);
return null;
});
return result;
}
/**
* Insert a new URI into the database. The values will only be inserted if not
* yet existent. However, the return value will always contain the ID's of the
* corresponding entries. No update will be done on the entries if they exist.
* @param {string} baseUrl - The base url to be inserted
* @param {śtring} subdomain - The URIs subdomain
* @param {string} path - The path of the uir to be inserted
* @param {number} depth - Indicates the search depth at which the entry is
* to be inserted.
* @param {boolean} secure=false - Indicate whether the uri uses http(s)
* @param {Sequelize.transaction} transaction=undefined - The transaction to be
* used for this insert.
* This ensures higher
* writing performance,
* since only one commit
* will be executed for
* a bunch of insertions
* @return {UUIDV4[]} Returns the IDs of the inserted values
* Those are always sorted as follows:
* [baseUrlId, pathId]
*/
db.insertUri = async function(
baseUrl,
subdomain,
path,
depth,
secure=false,
transaction=undefined
) {
logger.info("Insert new entry: " + baseUrl + path);
let [baseUrlEntry] = await db.baseUrl.findOrCreate({
where: {
baseUrl: baseUrl,
subdomain: subdomain,
},
defaults: {
baseUrl: baseUrl,
subdomain: subdomain,
},
transaction: transaction,
});
let random = Math.random();
let [pathEntry] = await db.path.findOrCreate({
where: {
baseUrlBaseUrlId: baseUrlEntry.baseUrlId,
path: path,
subdomain: subdomain,
},
defaults: {
lastStartedTimestamp: 0,
lastFinishedTimestamp: 0,
lastSuccessfulTimestamp: 0,
path: path,
depth: depth,
baseUrlBaseUrlId: baseUrlEntry.baseUrlId,
secure: secure,
random: random,
inProgress: false,
},
transaction: transaction,
});
return [baseUrlEntry.baseUrlId, pathEntry.pathId];
};
db.queriesToRetry = {};
/**
* @typedef {URIDefinition}
* @type {Object}
* @property {!string} subdomain Potential subdomains
* @property {!string} baseUrl The baseUrl of the URI
* @property {!string} path The path part of the URI
* @property {!number} depth The search depth at which this entry was found
* @property {!boolean} secure Indicates whether a secure connection should
* be used or not
*/
/**
* Insert a bulk of uris into the baseUrls and paths table.
* In order to achieve the same result as with findOrCreate, we create a unique
* index on (path, baseUrlId) in paths and a unique index on baseUrl in
* baseUrls. We then first try to insert the batch of baseUrls into the
* database. We add a "ON CONFLICT DO NOTHING" to every request, so we only get
* the successful entries in the return value. We then calculate the remainder
* (unsuccessful inserts) and get them by findAll.
* This way we retrieve all the baseUrlIds needed to insert the paths.
* For the paths we just try to insert them, again with the ON CONFLICT DO
* NOTHING set. We do not have to worry about the not inserted paths, since
* they are already present.
* @param {Array.<URIDefinition>} uriDefinitions - All the URIs that should be
* inserted if not yet existent
* @param {number} numOfRetries=3 How often should a request that times out be
* retried
* @return {Array.<UUIDv4>} Return the touched pathIds
*/
db.bulkInsertUri = async function(
uriDefinitions,
numOfRetries=3
) {
/* eslint-disable no-multi-str */
// First: create SQL request for bulkCreate ON CONFLICT DO NOTHING
// This is not yet implemented in sequelize, so we write our own version
// here. Feedback is currently stale, so not hoping that it will be resolved
// soon
// We need to generate the uuid on our own, but we can make use of automatic
// insertion of updatedAt/createdAt times
// We cannot have duplicates in our insertion list, so we have to filter
// those
// Filter to remove duplicates
uriDefinitions = uriDefinitions.filter((uriDefinition, index, self) => {
let auxiliaryIndex = self.findIndex((secondaryUriDefinition) => {
if (!uriDefinition.path) {
// in some cases path is null/undefined. Since we do not allow
// NULL in the DB, we must set it here to ""
uriDefinition.path = "";
}
return secondaryUriDefinition.baseUrl === uriDefinition.baseUrl
&& secondaryUriDefinition.path === uriDefinition.path;
});
return index == auxiliaryIndex;
});
if (uriDefinitions.length <= 0) {
return [];
}
// Now lets build those requests:
let baseUrlRequestString = "\
LOCK TABLE ONLY \"baseUrls\" IN SHARE ROW EXCLUSIVE MODE;\
INSERT INTO \"baseUrls\" (\"baseUrlId\", \"baseUrl\")\n\
VALUES\n\
";
let pathRequestString = "\
LOCK TABLE ONLY \"paths\" IN SHARE ROW EXCLUSIVE MODE;\
INSERT INTO \"paths\"\n\
(\"pathId\",\n\
\"lastStartedTimestamp\",\n\
\"lastFinishedTimestamp\",\n\
\"inProgress\",\n\
\"lastSuccessfulTimestamp\",\n\
\"depth\",\n\
\"path\",\n\
\"secure\",\n\
\"random\",\n\
\"baseUrlBaseUrlId\",\n\
\"subdomain\"\n\
)\
VALUES\n\
";
// First we have to build the baseUrl insertion, since for the path
// we need to know the actual baseUrlIds
let baseUrlDefinitions = uriDefinitions.filter(
(uriDefinition, index, self) => {
let auxiliaryIndex = self.findIndex((secondaryUriDefinition) => {
return uriDefinition.baseUrl === secondaryUriDefinition.baseUrl;
});
return auxiliaryIndex == index;
}
);
baseUrlDefinitions.sort((a, b) => {
let primaryX = a.baseUrl.toLowerCase();
let primaryY = b.baseUrl.toLowerCase();
return primaryX <= primaryY;
});
let replacementsForBaseUrlRequest = [];
for (let i = 0; i < baseUrlDefinitions.length; i++) {
let newBaseUrlId = uuidv4();
let baseUrlDefinition = baseUrlDefinitions[i];
if (!baseUrlDefinition.baseUrl) {
continue; // If baseUrl was empty, we just ignore this case
}
let value = "(?, ?)";
replacementsForBaseUrlRequest.push(newBaseUrlId);
replacementsForBaseUrlRequest.push(baseUrlDefinition.baseUrl);
if (i == baseUrlDefinitions.length - 1) {
value += "\n";
} else {
value += ",\n";
}
baseUrlRequestString += value;
}
baseUrlRequestString += "ON CONFLICT (\"baseUrl\")\n\
DO UPDATE SET \"numberOfHits\" = \"baseUrls\".\"numberOfHits\" + 1\n\
RETURNING \"baseUrlId\", \"baseUrl\"";
/** @type {Array.<Array|number>} baseUrlReturnValue contains two entries,
* one Array and a number, indicating the
* number of affected rows. Since we
* constructed the query such that all inputs
* are affecte, the size of the return array
* and the number should be equal. The array
* itself contains objects, indexed by column
* name.
*/
let baseUrlRetryCounter = 0;
let baseUrlReturnValue = await executeQuery(
baseUrlRequestString,
replacementsForBaseUrlRequest
);
while (baseUrlReturnValue == null && baseUrlRetryCounter < numOfRetries) {
baseUrlRetryCounter += 1;
baseUrlReturnValue = await executeQuery(
baseUrlRequestString,
replacementsForBaseUrlRequest
);
}
if (baseUrlReturnValue == null) {
logger.error("Could not insert baseUrls successfully.");
logger.error(
"Failed on \n"
+ baseUrlRequestString + "\n"
+ JSON.stringify(replacementsForBaseUrlRequest)
);
return []; // This allows to continue without interruption
}
baseUrlReturnValue = baseUrlReturnValue[0];
let baseUrlIdByBaseUrl = {};
for (let i = 0; i < baseUrlReturnValue.length; i++) {
let baseUrlEntry = baseUrlReturnValue[i];
baseUrlIdByBaseUrl[baseUrlEntry.baseUrl] = baseUrlEntry.baseUrlId;
}
let replacementsForPathRequest = [];
uriDefinitions.sort((a, b) => {
let primaryX = a.path.toLowerCase();
let primaryY = b.path.toLowerCase();
if (primaryX === primaryY) {
// Note: we can use the baseUrl as secondary sorting,
// since we already uniqueified baseUrl/Path pairs above
let secondaryX = a.baseUrl.toLowerCase();
let secondaryY = b.baseUrl.toLowerCase();
return secondaryX < secondaryY;
}
return primaryX < primaryY;
});
for (let i = 0; i < uriDefinitions.length; i++) {
let newPathId = uuidv4();
let random = Math.random();
let uriDefinition = uriDefinitions[i];
let path = uriDefinition.path;
let value = "( ?, 0, 0, FALSE, 0, ?, ?, ?, ?, ?, ?)";
replacementsForPathRequest.push(...[
newPathId,
uriDefinition.depth,
path,
uriDefinition.secure,
random,
baseUrlIdByBaseUrl[uriDefinition.baseUrl],
uriDefinition.subdomain,
]);
if (i == uriDefinitions.length - 1) {
value += "\n";
} else {
value += ",\n";
}
pathRequestString += value;
}
pathRequestString += "ON CONFLICT (\
\"baseUrlBaseUrlId\",\
\"path\",\
\"subdomain\")\n\
DO UPDATE SET \"numberOfHits\" = \"paths\".\"numberOfHits\" + 1\n\
RETURNING \"pathId\"";
let pathRetryCounter = 0;
let pathsReturnValue = await executeQuery(
pathRequestString,
replacementsForPathRequest
);
while (pathsReturnValue == null && pathRetryCounter < numOfRetries) {
pathRetryCounter += 1;
pathsReturnValue = await executeQuery(
pathRequestString,
replacementsForPathRequest
);
}
if (pathsReturnValue == null) {
logger.error("Could not insert paths successfully.");
logger.error(
"Failed on \n"
+ pathRequestString + "\n"
+ JSON.stringify(replacementsForPathRequest)
);
return []; // This allows to continue without interruption
}
pathsReturnValue = pathsReturnValue[0];
return pathsReturnValue.map((pathIdObj) => {
return pathIdObj.pathId;
});
/* eslint-enable no-multi-str */
};
/**
* Update the URI entry with new data. This is typically done after a network
* request finished (successfully or not). This method won't insert any data
* into the database, only update already existing entries. (The updated entry
* is naturally the one just downloaded, so only one at a time will be updated)
* @param {UUIDv4} baseUrlId - The ID of the baseUrl entry to be updated
* @param {UUIDv4} pathId - The ID of the path entry to be updated
* @param {number} lastStartedTimestamp - Contains the timestamp of the start of
* the download
* @param {number} lastFinishedTimestamp - Contains the timestamp when the last
* download finished (independent of if
* it was successful or not)
* @param {boolean} secure=false - Indicate whether a secure or insecure
* connection was used
* @param {boolean} successful=true - Indicate whether the download was finished
* successfully or not. This will accordingly
* set the lastSuccessfulTimestamp to
* lastFinishedTimestamp (if set to true)
* @param {boolean} finished=true - If set to true, this will reset the
* inProgress flag to false. Note that
* successful=true implies that the request is
* finished.
*/
db.updateUri = async function(
baseUrlId,
pathId,
lastStartedTimestamp,
lastFinishedTimestamp,
secure=false,
successful=true,
finished=true,
) {
let inProgress = true;
if (finished) {
inProgress = false;
}
let updateObj = {
lastStartedTimestamp,
lastFinishedTimestamp,
secure,
inProgress,
};
let whereClause = {
baseUrlBaseUrlId: baseUrlId,
pathId: pathId,
};
if (successful) {
updateObj["lastSuccessfulTimestamp"] = lastFinishedTimestamp;
updateObj["inProgress"] = false;
} else if (finished) {
updateObj["inProgress"] = false;
}
await db.path.update(updateObj, {
where: whereClause,
});
};
/**
* Inserts the body of a message as on content entry into the database.
* This function assumes that the data is already filtered and sanitized.
* @param {UUIDV4} pathId - ID of the corresponding path entry.
* @param {string} body - A JSON or HTML String containing the body.
* @param {string} mimeType - A string containing the indentifier for the
* mime type of the response.
* @param {number} timestamp - A timestamp in ms, indicating when the data
* was fetched.
* @param {number} statusCode=200 - HTTP Status code returned. Indicates whether
* the download was successful or not.
* @return {object} Returns the created content entry.
*/
db.insertBody = async function(
pathId,
body,
mimeType,
timestamp,
statusCode=200
) {
let response = await db.content.create({
scrapeTimestamp: timestamp,
contentType: mimeType,
content: body,
pathPathId: pathId,
statusCode: statusCode,
});
return response;
};
/**
* Resets the inProgress flag. The timeDelta parameter describes how high the
* timedelta of the lastModified flag has to be in order to be a stale entry.
* If set to 0, every entry gets reset (this is especially useful to
* restart the scraper after it was killed or crashed)
* @param {number} timeDelta=0 - Indicate how long an entry has to be in
* onProgress mode to be considered stale.
*/
db.resetStaleEntries = async function(
timeDelta
) {
if (timeDelta != 0 && !timeDelta) {
timeDelta = 0;
}
let latestStaleTime = (new Date).getTime() - timeDelta;
await db.path.update(
{
inProgress: false,
},
{
where: {
inProgress: true,
updatedAt: {
[Op.lte]: latestStaleTime,
},
},
},
);
};
/**
* @typedef Link
* @type {object}
* @property {!UUIDv4} sourcePathId - The ID of the path entry where the
* link originated.
* @property {!UUIDv4} destinationPathId - The ID of the target path entry.
* @property {!number} timestamp - Indicates at which time the link was in
* place. This does not mean, that the
* target was reachable, it only indicates
* the existence of a link at that given
* point in time. To check for reachability
* please refer to the attached content
* entries. They contain a successful flag.
*/
/**
* @typedef DbResult
* @type {object}
* @property {!string} url - The base url
* @property {!UUIDv4} baseUrlId - The ID of the base url entry.
* @property {!string} path - The path of the entry
* @property {!UUIDv4} pathId - The ID of the path entry.
* @property {!number} depth - The search depth at which this entry resides.
* @property {?string} content - If available or requested, it can contain
* the content of the entry
* @property {?UUIDv4} contentId - The ID of the content entry in the db.
* @property {?Link} link - A Link object, indicating a link between
* two paths/sites.
* @property {boolean} secure - Indicate if http or https should be used
*/
/**
* Retrieve entries from the database that are older or as old as the passed
* dateTime param
* @param {number} dateTime=0 - Specify the dateTime from which the newest
* entry should be. The default will only
* retrieve not yet scraped entries.
* @param {number} limit=100 - Specify how many entries the function
* should return. This is seen as an upper bound.
* If no more matching entries are available, only
* the remainder will be returned
* @param {number} offset=0 - Set if you have already received a certain
* amount of data. This way one can gather all
* entries of a certain timestamp or older
* @param {number} cutoffValue=1 - Set which is the deepest entry one should
* resolve. This ensures controlled termination
* of the scraper
* @param {Array.<UUIDv4>} excludedHosts=[] In order to not fill the memory up
* with entries that cannot yet be
* downloaded, one can pass an array
* of baseUrlIds that should be ignored
* @return {Array.<DbResult>|boolean} The DbResult contains the results
* returned by the Database.
* The boolean indicates whether more data is available
* as of now.
* Note: this can change, when the network fetches
* new data. If both, network and DB do not have
* anything pending, we can conclude that we have
* finished and exit.
*/
db.getEntriesRandomized = async function({
dateTime=0,
limit=100,
cutoffValue=1,
excludedHosts=[],
} = {}) {
if (limit == 0) {
return [[], false];
}
let dbResults = [];
// Note: Since we are using Op.lte dateTime = 0, we do
// not currently need an offset: the already scraped
// data has no 0 timestamp anymore
let paths = await db.path.findAll({
where: {
lastStartedTimestamp: {
[Op.lte]: dateTime,
},
inProgress: {
[Op.eq]: false,
},
depth: {
[Op.lte]: cutoffValue,
},
baseUrlBaseUrlId: {
[Op.notIn]: excludedHosts,
},
},
order: [
["depth", "ASC"],
["random", "DESC"],
],
limit: limit,
include: [db.baseUrl],
});
// collect pathIds for the update
let pathIds = paths.map((path) => path.pathId);
// Do a update with the same conditions as the get.
// This should never lead to issues, since only one
// getter is always run at the same time.
await db.path.update(
{inProgress: true},
{
where: {
pathId: {
[Op.in]: pathIds,
},
},
},
);
for (let path of paths) {
let dbResult = {
"path": path.path,
"pathId": path.pathId,
"depth": path.depth,
"secure": path.secure,
"url": null,
"baseUrlId": null,
"subdomain": path.subdomain,
"content": null,
"link": null,
};
dbResult["url"] = path.baseUrl.baseUrl;
dbResult["baseUrlId"] = path.baseUrl.baseUrlId;
dbResults.push(dbResult);
}
this.offsetForDbRequest += paths.length;
// We do not reset the offset counter yet, even if we did not find any
// new data, since we do not know whether new data will arrive from the
// network moduel. This is left to decide to the controller.
let moreData = dbResults.length != 0;
return [dbResults, moreData];
};
/**
* Get the entries to the download pool in a recursive manner: we allways
* collect downloads from all hosts first before starting a next round
* @param {number} dateTime Cutoff unix timestamp: From which timestamp on
* the scraper should see entries
* @param {number} limit How many entries should be returned at maximum
* @param {number} depth The deepest level that will be returned
* @param {Array.<UUIDv4>} excludedHosts UUIDs of hosts that should not be
* considered for the pool
* @param {boolean} inverse Indicate whether the entries with the highest
* or lowest link count should be returned first
* Depending on the case, it is useful to either
* return the most linked page (presumably this
* page is of high importance) or the least
* linked page (if the most linked urls are
* always Icons or similar).
* @return {Array.<DbResult>|boolean} The DbResult contains the results
* returned by the Database.
* The boolean indicates whether more data is available
* as of now.
* Note: this can change, when the network fetches
* new data. If both, network and DB do not have
* anything pending, we can conclude that we have
* finished and exit.
*/
db.getEntriesRecursive = async function(
dateTime,
limit,
depth,
excludedHosts,
inverse
) {
/* eslint-disable no-multi-str */
if (limit == 0) {
return [[], false];
}
let sorting = (inverse ? "ASC" : "DESC");
let dbResults = [];
let baseUrlReplacements = [dateTime, depth];
let baseUrlRequestString = "\
SELECT COUNT(contents.\"pathPathId\") as downcnt, \"baseUrls\".\"baseUrlId\"\n\
FROM \n\
\"baseUrls\"\n\
INNER JOIN paths \"pScr\"\
ON \"pScr\".\"baseUrlBaseUrlId\" = \"baseUrls\".\"baseUrlId\"\n\
LEFT JOIN contents ON \"pScr\".\"pathId\" = contents.\"pathPathId\"\n\
WHERE EXISTS ( \
SELECT \"pUnscr\".\"pathId\"\n\
FROM paths \"pUnscr\"\n\
WHERE \n\
\"pUnscr\".\"baseUrlBaseUrlId\" = \"baseUrls\".\"baseUrlId\"\n\
AND \"pUnscr\".\"lastFinishedTimestamp\" <= ?\n\
AND \"pUnscr\".\"depth\" <= ?\n\
AND \"pUnscr\".\"inProgress\" = false\n\
)";
if (excludedHosts.length > 0) {
baseUrlRequestString += " AND \"baseUrls\".\"baseUrlId\" NOT IN (";
for (let i = 0; i < excludedHosts.length; i++) {
baseUrlReplacements.push(excludedHosts[i]);
if (i == excludedHosts.length - 1) {
baseUrlRequestString += "?";
} else {
baseUrlRequestString += "?,\n";
}
}
baseUrlRequestString += ")";
}
baseUrlReplacements.push(limit);
baseUrlRequestString += "\
GROUP BY \"baseUrls\".\"baseUrlId\"\n\
ORDER BY downcnt ASC \n\
LIMIT ?";
let baseUrlResult = await executeQuery(
baseUrlRequestString,
baseUrlReplacements
);
if (baseUrlResult[0].length == 0) {
return [[], false];
}
let pathsReplacements = [];
let pathsRequestString = "WITH result AS (\
SELECT \"res\".\"subdomain\" AS subdomain,\n\
\"baseUrls\".\"baseUrl\" AS url,\n\
\"baseUrls\".\"baseUrlId\" AS baseUrlId,\n\
\"res\".\"path\" AS path,\n\
\"res\".\"pathId\" AS pathId,\n\
\"res\".\"depth\" AS depth,\n\
\"res\".\"secure\" AS secure,\n\
ROW_NUMBER() OVER (PARTITION BY res.\"baseUrlBaseUrlId\"\n\
ORDER BY res.\"numberOfDistinctHits\" " +
sorting +
") AS rk\n\
FROM \n\
paths AS res \n\
JOIN (\n\
values \n\
";
for (let i = 0; i < baseUrlResult[0].length; i++) {
pathsReplacements.push(baseUrlResult[0][i].baseUrlId);
if (i == baseUrlResult[0].length -1) {
pathsRequestString += "(UUID(?)," + i + ")\n";
} else {
pathsRequestString += "(UUID(?)," + i + "),\n";
}
}
pathsReplacements.push(dateTime);
pathsRequestString += "\
) AS X (id, ordering) ON X.id = res.\"baseUrlBaseUrlId\"\n\
INNER JOIN \"baseUrls\"\
ON res.\"baseUrlBaseUrlId\" = \"baseUrls\".\"baseUrlId\"\n\
WHERE res.\"lastFinishedTimestamp\" <= ?\n\
AND res.\"inProgress\" = FALSE \n\
ORDER BY X.ordering ASC \n\
)\n\
SELECT r.* \n\
FROM result r \n\
WHERE r.rk = 1 \n\
LIMIT ?;";
pathsReplacements.push(limit);
let entriesToScrape = await executeQuery(
pathsRequestString,
pathsReplacements
);
entriesToScrape = entriesToScrape[0];
let pathIds = entriesToScrape.map((entry) => entry.pathid);
await db.path.update(
{inProgress: true},
{
where: {
pathId: {
[Op.in]: pathIds,
},
},
},
);
for (let i = 0; i < entriesToScrape.length; i++) {
let entryToScrape = entriesToScrape[i];
dbResults.push({
"path": entryToScrape.path,
"pathId": entryToScrape.pathid,
"depth": entryToScrape.depth,
"secure": entryToScrape.secure,
"url": entryToScrape.url,
"baseUrlId": entryToScrape.baseurlid,
"subdomain": entryToScrape.subdomain,
"content": null,
"link": null,
});
}
let hasMore = true;
if (dbResults.length < limit) {
hasMore = false;
}
return [dbResults, hasMore];
/* eslint-enable no-multi-str */
};
/**
* Get entries from DB ordered by network (incomin links) prioritization
* @param {number} dateTime Entries which were scraped before this
* timestamp may be rescraped to see a temporal
* change in the network
* @param {number} limit Return at most limit entries, but might be
* less, if not as many entries are available.
* @param {number} depth Describe the maximum depth, the scraper will
* be looking for new entries
* @param {Array.<UUIDv4>} excludedHosts Exclude hosts, that should not be
* returned
* @return {Array.<DbResult>} Return an array of DbResults
*/
db.getEntriesPrioritized = async function(
dateTime,
limit,
depth,
excludedHosts
) {
let paths = await db.path.findAll({
where: {
lastStartedTimestamp: {
[Op.lte]: dateTime,
},
inProgress: {
[Op.eq]: false,
},
depth: {
[Op.lte]: depth,
},
baseUrlBaseUrlId: {
[Op.notIn]: excludedHosts,
},
},
order: [
["numberOfDistinctHits", "DESC"],
],
limit: limit,
include: [db.baseUrl],
});
// collect pathIds for the update
let pathIds = paths.map((path) => path.pathId);
// Do a update with the same conditions as the get.
// This should never lead to issues, since only one
// getter is always run at the same time.
await db.path.update(
{inProgress: true},
{
where: {
pathId: {
[Op.in]: pathIds,
},
},
},
);
let dbResults = [];
for (let path of paths) {
let dbResult = {
"path": path.path,
"pathId": path.pathId,
"depth": path.depth,
"secure": path.secure,
"url": null,
"baseUrlId": null,
"subdomain": path.subdomain,
"content": null,
"link": null,
};
dbResult["url"] = path.baseUrl.baseUrl;
dbResult["baseUrlId"] = path.baseUrl.baseUrlId;
dbResults.push(dbResult);
}
this.offsetForDbRequest += paths.length;
let moreData = dbResults.length != 0;
return [dbResults, moreData];
};
/**
* Get entries from DB that we have not yet scraped (grouped by host)
* @param {number} limit How many entries that we should try to get
* @param {number} cutoffValue The maximum depth that we should look for
* @return {Array.<DbResult>} Array of DbResults
*/
db.getNeverScrapedEntries = async function(limit, cutoffValue) {
/* eslint-disable no-multi-str */
if (limit == 0) {
return [[], false];
}
let dbResults = [];
let pathIds = [];
let [entriesToScrape] = await db.sequelize.query("\
SELECT DISTINCT ON (\"paths\".\"baseUrlBaseUrlId\")\n\
\"paths\".\"subdomain\" AS subdomain,\n\
\"baseUrls\".\"baseUrl\" AS url,\n\
\"baseUrls\".\"baseUrlId\" AS baseUrlId,\n\
\"paths\".\"path\" AS path,\n\
\"paths\".\"pathId\" AS pathId,\n\
\"paths\".\"depth\" AS depth,\n\
\"paths\".\"secure\" AS secure\n\
FROM \n\
(\n\
SELECT \n\
\"baseUrlBaseUrlId\",\n\
MAX(\"lastFinishedTimestamp\") AS maxtime,\n\
BOOL_OR(\"inProgress\") AS \"ongoing\"\n\
FROM \n\
\"paths\"\n\
JOIN \"baseUrls\" ON \
\"baseUrls\".\"baseUrlId\" = \"paths\".\"baseUrlBaseUrlId\"\n\
GROUP BY \"paths\".\"baseUrlBaseUrlId\"\n\
HAVING \n\
BOOL_OR(\"inProgress\") = false \n\
AND MAX(\"lastFinishedTimestamp\") = 0\n\
LIMIT 2000\n\
) t \n\
JOIN paths ON \"paths\".\"baseUrlBaseUrlId\" = t.\"baseUrlBaseUrlId\"\n\
JOIN \"baseUrls\" ON\
\"baseUrls\".\"baseUrlId\" = t.\"baseUrlBaseUrlId\"\n\
LIMIT 2000;\n\
");
for (let i = 0; i<entriesToScrape.length; i++) {
let entryToScrape = entriesToScrape[i];
pathIds.push(entryToScrape.pathid);
let dbResult = {
baseUrlId: entryToScrape.baseurlid,
url: entryToScrape.url,
path: entryToScrape.path,
pathId: entryToScrape.pathid,
depth: entryToScrape.depth,
content: null,
contentId: null,
link: null,
secure: entryToScrape.secure,
subdomain: entryToScrape.subdomain,
};
dbResults.push(dbResult);
}
// Loop and generate return Array, as well as the pathId Array to set flag
await db.path.update(
{inProgress: true},
{
where: {
pathId: {
[Op.in]: pathIds,
},
},
},
);
return [dbResults, dbResults.length >= limit];
/* eslint-enable no-multi-str */
};
/**
* Sets the inProgress flag of the specified dbResult to the passed flag.
* @param {DbResult} dbResult The dbResult for which the flag should be set
* @param {boolean} inProgress Pass the value to which the flag should be set
*/
db.setInProgressFlag = async function(dbResult, inProgress) {
await db.path.update(
{
inProgress,
},
{
where: {
baseUrlBaseUrlId: dbResult.baseUrlId,
},
}
);
};
module.exports = db;


Taxes! Seem familiar but why would a KGPian gossiping at tikka or catching up on the new season of GOT be concerned about it, right? Forward the timeline a bit and imagine some 20% of your salary being deducted from your hard earned income. And yes, the percentage increasing with the moolah you rake in. Sad, isn’t it?
Some financial knowledge about how to save your taxes wouldn’t hurt in the long run.  So what are exemptions? They are amounts that are deducted from your income “virtually” that give you your “taxable income”.
The first step to understand the workings of Income Tax in India is to be aware of the taxation slabs released each financial year by the Indian Government. The taxation slabs for the financial year 2014-15 for General tax payers and Women, below 60 years of age are:
| Slab | Income Slab (Rs.) | Income Tax Rate |
|---|---|---|
| 0 | 0 to 2,50,000 | NIL |
| I | 2,50,001 to 5,00,000 | 10% |
| II | 5,00,001 to 10,00,000 | 20% |
| III | 10,00,001 and above | 30% |
Say for example, Mr. Mehra earns 14 lakhs p.a.( Slab III). The first 2.5 lakhs are tax free while on the rest of the amount, the tax is calculated as follows:-
Income tax for income in Slab III
= 10% of Slab I + 20% of Slab II + 30% of Slab III
= 0.1*2,50,000 + 0.2*5,00,000 + 0.3*(14,00,000 – 10,00,000)
= 2,45,000
A whopping 17.5% of your salary goes to the government!
As you can see, a few exemptions here and there can be of great importance.
Some exemptions that can be availed on your income tax are:
Utilising Section 80C
Sec 80C of the Income Tax Act is the section that deals with these tax breaks. It states that qualifying investments, up to a maximum of Rs. 1.50 Lakhs , are deductible from your income. This means that your taxable income gets reduced by this investment amount (up to Rs. 1.50 Lakhs), and you end up paying no tax on it at all! Some of the qualifying investments are:
- Provident Fund
- Home Loan Principal
- Life Insurance Premiums
- Voluntary Provident Fund (VPF)
- Public Provident Fund (PPF)
- Equity Linked Savings Scheme (ELSS)
Section 80G
Donations to specified funds and charitable institutions are counted under exemptions under this section.
Tax on Bonus
If you anticipate tax rates to be reduced or slabs to be modified in the subsequent year, see if you could push the bonus payment to the subsequent year and request your employer for the same so as to reduce your tax on the bonus.
Some simple salary restructuring
- Opt for food coupons instead of lunch allowances, as they are exempt from tax up to Rs. 50 per meal
- Include medical allowance, transport allowance, education allowance, uniform expenses (if any), and telephone expenses as part of salary. Produce bills of actual expenses incurred for these allowances to reduce tax
- Opt for the company car instead of using your own car, to reduce high prerequisite taxation.
Fixed deposit v/s Savings account
Though interest incurred is higher in a FD account but investing in a savings account could be more profitable. No tax is deducted on interest and no tax is levied in savings account along with an allowable deduction of ₹10,000
So for Mr.Mehra earning ₹14,00,00 per year , the calculations(after exemptions) would proceed as:
| Different Heads | Amount |
|---|---|
| A) Income | 14,00,000 |
| B) 80C deduction | 1,50,000 |
| C) Home loan interest | 1,50,000 |
| D) Medical insurance premium | 15,000 |
| E) Total exemption(B+C+D) | 3,15,000 |
| F) Any other exemptions (Education loan, donations, saving banks interest upto 10k) | 15,000 |
| G) Taxable Income[A-(E+F)] | 10,70,000 |
| Tax calculation | 0.1 x 2,50,000 + 0.2 x 5,00,000 + 0.3 x (G-10,00,000) |
| Income Tax | 1,46,000 |
| Income tax(without exemptions) | 2,45,000 |
A major saving of ₹99,000!!
People in the lower income groups do not need to invest or delve into a lot to seek the options available. The majorly affected is the upper income class which may want to invest more to save its taxes. Harnessing the power of equities is highly recommended by some experts while a FD account could be the calling for someone’s short term financial goals.
Those of you interested in trading in the stock market would be happy to know that dividends earned from domestic companies are tax free too.
A knowledge of these exemptions and the nitty gritties of investments could craft a major alteration in your lifetime savings!


#include <editors.h>
Inheritance diagram for TFileEditor::
Public Methods | |
| TFileEditor (const TRect &bounds, TScrollBar *aHScrollBar, TScrollBar *aVScrollBar, TIndicator *aIndicator, const char *aFileName) | |
| virtual void | doneBuffer () |
| virtual void | handleEvent (TEvent &) |
| virtual void | initBuffer () |
| Boolean | loadFile () |
| Boolean | save () |
| Boolean | saveAs () |
| Boolean | saveFile () |
| virtual Boolean | setBufSize (uint newSize) |
| virtual void | shutDown () |
| virtual void | updateCommands () |
| virtual Boolean | valid (ushort) |
Static Public Methods | |
| TStreamable* | build () |
Public Attributes | |
| char | fileName [PATH_MAX] |
Static Public Attributes | |
| const char* const | name |
Protected Methods | |
| TFileEditor (StreamableInit) | |
| virtual void | write (opstream &) |
| virtual void* | read (ipstream &) |
Definition at line 1185 of file editors.h.
|
|
Creates a TFileEditor object with the given scroll bars and indicator and loads the contents of the file given in `aFileName'. If the file is not found or invalid, an error message will be displayed and the object's valid() member function will return False. The options variable is set to ofSelectable and the eventMask is set to allow the handling of broadcast events. Any of `aHScrollBar', `aVScrollBar' or `aIndicator' arguments can be set to 0 if you do not want them. |
|
|
Each streamable class needs a "builder" to allocate the correct memory for its objects together with the initialized virtual table pointers. This is achieved by calling this constructor with an argument of type StreamableInit. |
|
|
Called to create an object in certain stream-reading situations. Reimplemented from TEditor. |
|
|
Deletes the buffer. Reimplemented from TEditor. |
|
|
Calls TEditor::handleEvent(), then handles cmSave and cmSaveAs commands. The cmSave command invokes save(); the cmSaveAs command invokes saveAs(). Reimplemented from TEditor. |
|
|
Allocates bufSize bytes of memory for the file editor buffer. Reimplemented from TEditor. |
|
|
Reads the fileName file from disk and checks for errors. Returns True if all is well; otherwise returns False. Depending on the reason for failure, the edOutOfMemory or edReadError dialog box is displayed with a call to editorDialog. |
|
|
Reads from the input stream `is'. Reimplemented from TEditor. |
|
|
Calls saveAs() if the file being edited is "Untitled" (that is, no fileName is allocated) and returns the return value from saveAs(). If there is a valid fileName, saveFile() is invoked, and save() returns the return value of saveFile(). |
|
|
Invokes the edSaveAs dialog, which prompts for a "save as" file name. If a valid file name is supplied, the current text will be saved with this name using the saveFile() member function.
False is also returned if the edSaveAs dialog is cancelled. |
|
|
Saves the fileName file to disk. Returns False if the save fails; otherwise returns True. If editorFlags has the efBackupFiles bit set, a backup file is created. The edCreateError or edWriteError dialog box will be displayed to indicate the reason for failure if save fails.
|
|
|
Overrides TEditor::setBufSize() to grow and shrink the buffer. Will grow and shrink the buffer in 4K byte increments. gapLen data member is adjusted appropriately. Reimplemented from TEditor. |
|
|
Undocumented. Reimplemented from TEditor. |
|
|
Calls TEditor::updateCommands(), then enables the cmSave and cmSaveAs commands. These commands are only valid if the selected view is an editor, otherwise they should be disabled. Reimplemented from TEditor. |
|
|
Overrides TEditor::valid() to warn that the file might need saving before the program exits. The edSaveUntitled or edSaveModify dialogs are displayed as appropriate.
Reimplemented from TEditor. |
|
|
Writes to the output stream `os'. Reimplemented from TEditor. |
|
|
The name of the file being edited. |
|
|
Undocumented. Reimplemented from TEditor. |


{% extends "base.html" %} {% load compress humanize staticfiles hc_extras %} {% block title %} Notification Channels - healthchecks.io {% endblock %} {% block content %}
You can set up healthchecks.io to send you sms' directly to an to your phone number.


Before Change
Raises:
ValueError: If epsilon is invalid.
observation_and_action_constraint_splitter = getattr(
policy, "observation_and_action_constraint_splitter", None)
accepts_per_arm_features = getattr(policy, "accepts_per_arm_features",
False)
self._greedy_policy = greedy_policy.GreedyPolicy(policy)
self._epsilon = epsilon
After Change
policy.observation_and_action_constraint_splitter)
except AttributeError:
observation_and_action_constraint_splitter = None
try:
accepts_per_arm_features = policy.accepts_per_arm_features
except AttributeError:
accepts_per_arm_features = False
self._greedy_policy = greedy_policy.GreedyPolicy(policy)
self._epsilon = epsilon
self._random_policy = random_tf_policy.RandomTFPolicy(
policy.time_step_spec,


So, we will look at how to use them. Not how to create them yet.
The then method is the technique to use promise.
We talk about promises being thenable . It is a term used in the JavaScript world
.. which means we can use the method then with it.
Being able to determine when that promise has completed, so we call some function.
... that function is setup to return a promise
... and then to see the results. -- its VALUE.
.. OK - lets look at that..


Next: make-hash-table, Previous: Hash Tables Dictionary, Up: Hash Tables Dictionary
Hash tables provide a way of mapping any object (a key) to an associated object (a value).
Hash Table Concepts, Printing Other Objects
The intent is that this mapping be implemented by a hashing mechanism, such as that described in Section 6.4 "Hashing" of The Art of Computer Programming, Volume 3 (pp506-549). In spite of this intent, no conforming implementation is required to use any particular technique to implement the mapping.


import {MatDatepickerInputHarness} from '@angular/material/datepicker/testing';
MatDatepickerInputHarness
Harness for interacting with a standard Material datepicker inputs in tests.
| Name | Description |
|---|---|
|
| blur | |
|---|---|
|
Blurs the input and returns a promise that indicates when the action is complete. |
|
| Returns | |
Promise<void>
|
|
async
closeCalendar
|
|
|---|---|
|
Closes the calendar associated with the input. |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
| focus | |
|---|---|
|
Focuses the input and returns a promise that indicates when the action is complete. |
|
| Returns | |
Promise<void>
|
|
async
getCalendar
|
|
|---|---|
|
Gets the |
|
| Parameters | |
|
filter CalendarHarnessFilters = {}
|
Optionally filters which calendar is included. |
| Returns | |
Promise<MatCalendarHarness>
|
|
| getMax | |
|---|---|
|
Gets the formatted maximum date for the input's value. |
|
| Returns | |
Promise<string | null>
|
|
| getMin | |
|---|---|
|
Gets the formatted minimum date for the input's value. |
|
| Returns | |
Promise<string | null>
|
|
| getPlaceholder | |
|---|---|
|
Gets the placeholder of the input. |
|
| Returns | |
Promise<string>
|
|
| getValue | |
|---|---|
|
Gets the value of the input. |
|
| Returns | |
Promise<string>
|
|
async
hasCalendar
|
|
|---|---|
|
Whether a calendar is associated with the input. |
|
| Returns | |
Promise<boolean>
|
|
async
host
|
|
|---|---|
|
Gets a |
|
| Returns | |
Promise<TestElement>
|
|
async
isCalendarOpen
|
|
|---|---|
|
Gets whether the calendar associated with the input is open. |
|
| Returns | |
Promise<boolean>
|
|
| isDisabled | |
|---|---|
|
Whether the input is disabled. |
|
| Returns | |
Promise<boolean>
|
|
| isFocused | |
|---|---|
|
Whether the input is focused. |
|
| Returns | |
Promise<boolean>
|
|
| isRequired | |
|---|---|
|
Whether the input is required. |
|
| Returns | |
Promise<boolean>
|
|
async
openCalendar
|
|
|---|---|
|
Opens the calendar associated with the input. |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
| setValue | |
|---|---|
|
Sets the value of the input. The value will be set by simulating keypresses that correspond to the given value. |
|
| Parameters | |
|
newValue string
|
|
| Returns | |
Promise<void>
|
|
static
with
|
|
|---|---|
|
Gets a |
|
| Parameters | |
|
options DatepickerInputHarnessFilters = {}
|
Options for filtering which input instances are considered a match. |
| Returns | |
HarnessPredicate<MatDatepickerInputHarness>
|
a |
MatDatepickerToggleHarness
Harness for interacting with a standard Material datepicker toggle in tests.
| Name | Description |
|---|---|
|
| closeCalendar | |
|---|---|
|
Closes the calendar if it is open. |
|
| Returns | |
Promise<void>
|
|
| getCalendar | |
|---|---|
|
Gets the |
|
| Parameters | |
|
filter CalendarHarnessFilters = {}
|
Optionally filters which calendar is included. |
| Returns | |
Promise<MatCalendarHarness>
|
|
| hasCalendar | |
|---|---|
|
Gets whether there is a calendar associated with the trigger. |
|
| Returns | |
Promise<boolean>
|
|
async
host
|
|
|---|---|
|
Gets a |
|
| Returns | |
Promise<TestElement>
|
|
async
isCalendarOpen
|
|
|---|---|
|
Gets whether the calendar associated with the toggle is open. |
|
| Returns | |
Promise<boolean>
|
|
async
isDisabled
|
|
|---|---|
|
Whether the toggle is disabled. |
|
| Returns | |
Promise<boolean>
|
|
| openCalendar | |
|---|---|
|
Opens the calendar if the trigger is enabled and it has a calendar. |
|
| Returns | |
Promise<void>
|
|
static
with
|
|
|---|---|
|
Gets a |
|
| Parameters | |
|
options DatepickerToggleHarnessFilters = {}
|
Options for filtering which datepicker toggle instances are considered a match. |
| Returns | |
HarnessPredicate<MatDatepickerToggleHarness>
|
a |
MatStartDateHarness
Harness for interacting with a standard Material date range start input in tests.
| Name | Description |
|---|---|
|
| blur | |
|---|---|
|
Blurs the input and returns a promise that indicates when the action is complete. |
|
| Returns | |
Promise<void>
|
|
| focus | |
|---|---|
|
Focuses the input and returns a promise that indicates when the action is complete. |
|
| Returns | |
Promise<void>
|
|
| getMax | |
|---|---|
|
Gets the formatted maximum date for the input's value. |
|
| Returns | |
Promise<string | null>
|
|
| getMin | |
|---|---|
|
Gets the formatted minimum date for the input's value. |
|
| Returns | |
Promise<string | null>
|
|
| getPlaceholder | |
|---|---|
|
Gets the placeholder of the input. |
|
| Returns | |
Promise<string>
|
|
| getValue | |
|---|---|
|
Gets the value of the input. |
|
| Returns | |
Promise<string>
|
|
async
host
|
|
|---|---|
|
Gets a |
|
| Returns | |
Promise<TestElement>
|
|
| isDisabled | |
|---|---|
|
Whether the input is disabled. |
|
| Returns | |
Promise<boolean>
|
|
| isFocused | |
|---|---|
|
Whether the input is focused. |
|
| Returns | |
Promise<boolean>
|
|
| isRequired | |
|---|---|
|
Whether the input is required. |
|
| Returns | |
Promise<boolean>
|
|
| setValue | |
|---|---|
|
Sets the value of the input. The value will be set by simulating keypresses that correspond to the given value. |
|
| Parameters | |
|
newValue string
|
|
| Returns | |
Promise<void>
|
|
static
with
|
|
|---|---|
|
Gets a |
|
| Parameters | |
|
options DatepickerInputHarnessFilters = {}
|
Options for filtering which input instances are considered a match. |
| Returns | |
HarnessPredicate<MatStartDateHarness>
|
a |
MatEndDateHarness
Harness for interacting with a standard Material date range end input in tests.
| Name | Description |
|---|---|
|
| blur | |
|---|---|
|
Blurs the input and returns a promise that indicates when the action is complete. |
|
| Returns | |
Promise<void>
|
|
| focus | |
|---|---|
|
Focuses the input and returns a promise that indicates when the action is complete. |
|
| Returns | |
Promise<void>
|
|
| getMax | |
|---|---|
|
Gets the formatted maximum date for the input's value. |
|
| Returns | |
Promise<string | null>
|
|
| getMin | |
|---|---|
|
Gets the formatted minimum date for the input's value. |
|
| Returns | |
Promise<string | null>
|
|
| getPlaceholder | |
|---|---|
|
Gets the placeholder of the input. |
|
| Returns | |
Promise<string>
|
|
| getValue | |
|---|---|
|
Gets the value of the input. |
|
| Returns | |
Promise<string>
|
|
async
host
|
|
|---|---|
|
Gets a |
|
| Returns | |
Promise<TestElement>
|
|
| isDisabled | |
|---|---|
|
Whether the input is disabled. |
|
| Returns | |
Promise<boolean>
|
|
| isFocused | |
|---|---|
|
Whether the input is focused. |
|
| Returns | |
Promise<boolean>
|
|
| isRequired | |
|---|---|
|
Whether the input is required. |
|
| Returns | |
Promise<boolean>
|
|
| setValue | |
|---|---|
|
Sets the value of the input. The value will be set by simulating keypresses that correspond to the given value. |
|
| Parameters | |
|
newValue string
|
|
| Returns | |
Promise<void>
|
|
static
with
|
|
|---|---|
|
Gets a |
|
| Parameters | |
|
options DatepickerInputHarnessFilters = {}
|
Options for filtering which input instances are considered a match. |
| Returns | |
HarnessPredicate<MatEndDateHarness>
|
a |
MatDateRangeInputHarness
Harness for interacting with a standard Material date range input in tests.
| Name | Description |
|---|---|
|
| closeCalendar | |
|---|---|
|
Closes the calendar if it is open. |
|
| Returns | |
Promise<void>
|
|
| getCalendar | |
|---|---|
|
Gets the |
|
| Parameters | |
|
filter CalendarHarnessFilters = {}
|
Optionally filters which calendar is included. |
| Returns | |
Promise<MatCalendarHarness>
|
|
async
getEndInput
|
|
|---|---|
|
Gets the inner start date input inside the range input. |
|
| Returns | |
Promise<MatEndDateHarness>
|
|
async
getSeparator
|
|
|---|---|
|
Gets the separator text between the values of the two inputs. |
|
| Returns | |
Promise<string>
|
|
async
getStartInput
|
|
|---|---|
|
Gets the inner start date input inside the range input. |
|
| Returns | |
Promise<MatStartDateHarness>
|
|
async
getValue
|
|
|---|---|
|
Gets the combined value of the start and end inputs, including the separator. |
|
| Returns | |
Promise<string>
|
|
| hasCalendar | |
|---|---|
|
Gets whether there is a calendar associated with the trigger. |
|
| Returns | |
Promise<boolean>
|
|
async
host
|
|
|---|---|
|
Gets a |
|
| Returns | |
Promise<TestElement>
|
|
async
isCalendarOpen
|
|
|---|---|
|
Opens the calendar associated with the input. |
|
| Returns | |
Promise<boolean>
|
|
async
isDisabled
|
|
|---|---|
|
Gets whether the range input is disabled. |
|
| Returns | |
Promise<boolean>
|
|
async
isRequired
|
|
|---|---|
|
Gets whether the range input is required. |
|
| Returns | |
Promise<boolean>
|
|
| openCalendar | |
|---|---|
|
Opens the calendar if the trigger is enabled and it has a calendar. |
|
| Returns | |
Promise<void>
|
|
static
with
|
|
|---|---|
|
Gets a |
|
| Parameters | |
|
options DateRangeInputHarnessFilters = {}
|
Options for filtering which input instances are considered a match. |
| Returns | |
HarnessPredicate<MatDateRangeInputHarness>
|
a |
MatCalendarHarness
extends
ComponentHarness
Harness for interacting with a standard Material calendar in tests.
| Name | Description |
|---|---|
|
async
changeView
|
|
|---|---|
|
Changes the calendar view by clicking on the view toggle button. |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
async
getCells
|
|
|---|---|
|
Gets a list of cells inside the calendar. |
|
| Parameters | |
|
filter CalendarCellHarnessFilters = {}
|
Optionally filters which cells are included. |
| Returns | |
Promise<MatCalendarCellHarness[]>
|
|
async
getCurrentView
|
|
|---|---|
|
Gets the current view that is being shown inside the calendar. |
|
| Returns | |
Promise<CalendarView>
|
|
async
getCurrentViewLabel
|
|
|---|---|
|
Gets the label of the current calendar view. |
|
| Returns | |
Promise<string>
|
|
async
host
|
|
|---|---|
|
Gets a |
|
| Returns | |
Promise<TestElement>
|
|
async
next
|
|
|---|---|
|
Goes to the next page of the current view (e.g. next month when inside the month view). |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
async
previous
|
|
|---|---|
|
Goes to the previous page of the current view (e.g. previous month when inside the month view). |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
async
selectCell
|
|
|---|---|
|
Selects a cell in the current calendar view. |
|
| Parameters | |
|
filter CalendarCellHarnessFilters = {}
|
An optional filter to apply to the cells. The first cell matching the filter will be selected. |
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
static
with
|
|
|---|---|
|
Gets a |
|
| Parameters | |
|
options CalendarHarnessFilters = {}
|
Options for filtering which calendar instances are considered a match. |
| Returns | |
HarnessPredicate<MatCalendarHarness>
|
a |
MatCalendarCellHarness
extends
ComponentHarness
Harness for interacting with a standard Material calendar cell in tests.
| Name | Description |
|---|---|
|
async
blur
|
|
|---|---|
|
Removes focus from the calendar cell. |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
async
focus
|
|
|---|---|
|
Focuses the calendar cell. |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
async
getAriaLabel
|
|
|---|---|
|
Gets the aria-label of the calendar cell. |
|
| Returns | |
Promise<string>
|
|
async
getText
|
|
|---|---|
|
Gets the text of the calendar cell. |
|
| Returns | |
Promise<string>
|
|
async
host
|
|
|---|---|
|
Gets a |
|
| Returns | |
Promise<TestElement>
|
|
async
hover
|
|
|---|---|
|
Hovers over the calendar cell. |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
async
isActive
|
|
|---|---|
|
Whether the cell is currently activated using keyboard navigation. |
|
| Returns | |
Promise<boolean>
|
|
async
isComparisonRangeEnd
|
|
|---|---|
|
Whether the cell is the end of the comparison range. |
|
| Returns | |
Promise<boolean>
|
|
async
isComparisonRangeStart
|
|
|---|---|
|
Whether the cell is the start of the comparison range. |
|
| Returns | |
Promise<boolean>
|
|
async
isDisabled
|
|
|---|---|
|
Whether the cell is disabled. |
|
| Returns | |
Promise<boolean>
|
|
async
isInComparisonRange
|
|
|---|---|
|
Whether the cell is inside of the comparison range. |
|
| Returns | |
Promise<boolean>
|
|
async
isInPreviewRange
|
|
|---|---|
|
Whether the cell is inside of the preview range. |
|
| Returns | |
Promise<boolean>
|
|
async
isInRange
|
|
|---|---|
|
Whether the cell is part of the main range. |
|
| Returns | |
Promise<boolean>
|
|
async
isPreviewRangeEnd
|
|
|---|---|
|
Whether the cell is the end of the preview range. |
|
| Returns | |
Promise<boolean>
|
|
async
isPreviewRangeStart
|
|
|---|---|
|
Whether the cell is the start of the preview range. |
|
| Returns | |
Promise<boolean>
|
|
async
isRangeEnd
|
|
|---|---|
|
Whether the cell is the end of the main range. |
|
| Returns | |
Promise<boolean>
|
|
async
isRangeStart
|
|
|---|---|
|
Whether the cell is the start of the main range. |
|
| Returns | |
Promise<boolean>
|
|
async
isSelected
|
|
|---|---|
|
Whether the cell is selected. |
|
| Returns | |
Promise<boolean>
|
|
async
isToday
|
|
|---|---|
|
Whether the cell represents today's date. |
|
| Returns | |
Promise<boolean>
|
|
async
mouseAway
|
|
|---|---|
|
Moves the mouse away from the calendar cell. |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
async
select
|
|
|---|---|
|
Selects the calendar cell. Won't do anything if the cell is disabled. |
|
| Returns | |
Promise<void>
|
Promise that resolves when the action completes. |
static
with
|
|
|---|---|
|
Gets a |
|
| Parameters | |
|
options CalendarCellHarnessFilters = {}
|
Options for filtering which cell instances are considered a match. |
| Returns | |
HarnessPredicate<MatCalendarCellHarness>
|
a |
DatepickerInputHarnessFilters
A set of criteria that can be used to filter a list of datepicker input instances.
| Name | Description |
|---|---|
|
Filters based on the placeholder text of the input. |
|
Filters based on the value of the input. |
DatepickerToggleHarnessFilters
A set of criteria that can be used to filter a list of datepicker toggle instances.
CalendarHarnessFilters
A set of criteria that can be used to filter a list of calendar instances.
CalendarCellHarnessFilters
A set of criteria that can be used to filter a list of calendar cell instances.
| Name | Description |
|---|---|
|
Filters based on whether the cell is activated using keyboard navigation |
|
Filters based on whether the cell is disabled. |
|
Filters based on whether the cell is inside of the comparison range. |
|
Filters based on whether the cell is inside of the preview range. |
|
Filters based on whether the cell is inside of the main range. |
|
Filters based on whether the cell is selected. |
|
Filters based on the text of the cell. |
|
Filters based on whether the cell represents today's date. |
DateRangeInputHarnessFilters
A set of criteria that can be used to filter a list of date range input instances.
| Name | Description |
|---|---|
|
Filters based on the value of the input. |


{% extends "base.html" %} {% block content %}
You have not added any addresses
You have not added any Work places
You have not added any Education Events


This is a browser-based tool that allows you to define a custom JavaScript function which is used to bulk-convert a folder of files (using the new File System Access API).
Your data folder should contain an 'input' folder (filled with files and/or sub-folders) and an empty 'output' folder. Your data folder can optionally contain 'transform.js' which defines an async function called 'transform' that takes as input an object like this: {fileHandle, path, i, n} (the input file handle, the folder path to that file, the file index, and total number of files) and returns an object like this: {blob, name} (or an array of these objects, if each input should be converted into multiple output files). If the output name contains forward-slashes, sub-folders are made in the output folder to match the given path. If the data folder doesn't contain transform.js, you can use the code editor below to write the transform function.
The files in the input folder will be copied, transformed, and placed into the output folder with the new filenames as specified by the transform function. Note that your transform function is executed in Web Workers, using as many threads as you like (specified below).
The code for this web app is at @josephrocca/bulk-file-transformer on Github.


TELL US ABOUT YOURSELF
NAME:
PHONE:
EMAIL:
ADDRESS:
CITY:
STATE:
ZIP CODE:
CONTACT:
PHONE
EMAIL
WHAT SORT OF INFORMATION DO YOU NEED?
NEW LOAN:
NEW MORTGAGE
REFINANCE
FIXED RATE
ADJUSTABLE
LOAN TYPE:
HOME LOAN
COMMERCIAL LOAN
2ND MORTGAGE
OTHER
ADDITIONAL COMMENTS
COMMENTS:
SUBMIT FORM >>>


How this works:
The Wallet is running on the blockchain and can hold Ether itself. You can send Ether to the wallet and instruct the wallet to send Ether from it's own balance to somewhere else. But you can only do this, if you are "allowed" to do so.
How can you be allowed?
Either you are the one who deployed the smart contract (owner), or the owner is adding you to the "allowed senders" list.
Deploy the smart contract => be the owner
Add another address to the wallet
In MetaMask switch the account
Copy the account
Switch back to the owner account
Run "Allow Sender" with the copied account#2
Send Ether to the smart contract (deposit)
Switch to account#2 in MetaMask, reload the page and instruct the smart contract to send Ether somewhere else (withdrawal)


| linkedin.com/in/susanprestage |<EMAIL_ADDRESS>| |
| portfolio.susanprestage.com |<PHONE_NUMBER> | |
| github.com/sprestage | Seattle, WA | |
Software Development and Quality Engineer with extensive experience releasing diverse software and hardware systems. I am a Ruby on Rails developer with proven abilities in test plan development, networking, multi-platform environments, agile methodologies, and rapid absorption of new technology and ideas. Software development life cycle expertise in a variety of roles designing, testing, and supporting software and hardware products. Proficiency in test automation using various scripting and programming languages. Specialist in UNIX environments and TCP/IP networking. I am happiest when deep in code or crafting from a command line.
| Unix/Linux | agile | Rails | git |
| command line | TCP/IP | Ruby | SQL |
| shell scripting | networking | Capybara | HTML5/CSS3 |
workZeit
2014-2015
A SAAS web application that provides an innovative environment for teammates, managers, and companies to send and receive feedback. The service is notable for its extensive and sophisticated analytics tools provided to team managers and administrators. featherlight provides a better tool for improving one-on-one feedback and supporting the dreaded yearly review.
DreamBox Learning, Server Quality Engineer
2010-2011
Agile team designing, implementing, and maintaining services which support the client-server lesson delivery system and related data analysis, aggregation, and reporting systems.
Expedia, Software Test Engineer
2006-2008
Specialized in testing the back end of Expedia's payment processing and booking transactions.
Google via WorkForce Logic, Software Test Engineer
2005-2006
Sitemaps group enables webmasters to publish lists of links from across their websites.
Microsoft via Volt, Software Test Engineer
2005-2005
Part of the team building Outlook Web Access (OWA) Basic from the ground up. Designed test plans and wrote test cases for Attachment Manager and Calendar areas. Wrote automated tests in C# to test OWA using HTTP. Took the initiative to learn and instruct others in use of screen reader accessibility software, JAWS.
Aventail, Senior Test Engineer
2002-2004
Led the QA effort through 6 successful releases of the Appliance Management Console (AMC) for Aventail's SSL VPN (UNIX-based) appliance. Designed test plans, wrote instructional documents, integrated new QA team members, coordinated across multiple teams to ensure smooth integration of different product areas. Tested GUI and backend of the UNIX-based appliance. Oversaw the automation of all new tests.
Cray Inc., OS Test Engineer
2000-2001
Tested a NetBSD-based operating system on Cray's multi-threaded architecture project. Designed test suites using C and UNIX shell scripts, ported tests from different operating systems, automated all test suites using shell scripts and cron jobs.
Cisco Systems, Development Test Engineer
1996-1999
Member of the development team for Cisco's cable modem product development kit (PDK). Responsible for testing TCP/IP protocols for the cable modem. Designed comprehensive test suites from technical product specifications, RFC's, and input from product developers to assure compliance with design specifications.
Professional Portfolio, a Rails 3 application
2013-2014
A Ruby on Rails website showcasing the various full stack web developer skills gained through Code Fellows. This Rails 3 application demonstrates my working knowledge of Ruby, HTML, CSS, Javascript, jQuery, Twitter Bootstrap, and many gems.
The Unofficial Trakehner Database, a Rails 4 application
2013-2014
Collaborative pair project for Code Fellows. A website for the Trakehner horse enthusiast, with emphasis for Trakehner breeders.
RailsBridge, TA
2014
Free weekend workshops where women learn Ruby on Rails from volunteers. The students at RailsBridge workshops range from folks who are completely new to programming to QA engineers and professional developers who want to learn Rails. During the Installfest (usually on a Friday evening), Teachers and TAs get students' laptops setup with the requisite technologies. The next day, students break into small group based on experience level and build a web app!
Ada Academy, TA
2014
Ada Developers Academy (Ada) is an intensive software developer training school for women, teaching full-stack web development. Students are learning Ruby, Rails, HTML/CSS, and JavaScript. Additionally, students are learning how to work on agile development teams, using pair programming, scrum, and test-driven development.
Code Fellows, Ruby on Rails Dev Accelerator
Fall 2013
An 8 week intensive Ruby on Rails course with 4 hours of classroom study and 4+ hours of project work each day. I was one of 21 students selected from over 400, based on interviews, technical aptitude, and grit. In addition to Ruby and Rails, we also used TDD, BDD, Postgres, Travis CI, Heroku, Github, HTML5, CSS3, Javascript, jQuery, Capybara, minitest, and many others. Code Fellows alumni are trained to find resources and learn quickly.
University of California, Santa Cruz
BS Computer Engineering
I spend weekends on my horse ranch in North Bend, enjoying my horses, indulging my cats, gaming with my family, and learning about new things geeky and techy.


A selection of my coursework, research, and personal projects done over the years. The technical projects are primarily done in C++, while the production projects are done primarily in Houdini. Areas that interest me include rendering and simulation.
A toolset created in Houdini that allows the user to control melting simulations on different parts of an input 3D model by specifying control zones for viscosity and temperature.
Houdini, VEX
Real time rendering engine with features such as forward and deferred rendering, SSAO, and a PBR workflow.
C++, OpenGL, GLSL, ImGUI, Assimp, Visual Studio
A 3D short movie about a young apprentice's battle to achieve inner peace. A group project done in collaboration with students from The School Of Visual Arts, New York.
Houdini, VEX, Python, Maya
Monte Carlo path tracing algorithm with materials like plastic and metals, and properties like reflection and refraction.
C++, OpenGL
Developed using Craig Reynolds 1987 SIGGRAPH paper Flocks, Herds, and Schools: A Distributed Behavioral Model.
Processing
A 3D short movie about an old kite's troubles with its replacement. Created under the guidance of supervisors and senior artists from Walt Disney Animation Studios.
Houdini, Vex, Maya, Python, Substance Painter


Skills
I know:
C, R, Python
I am Learning:
HTTP, HTML, CSS, JS
DOM, NoSQL, SQL :)
Education
- Biomolecular Sciences
- Computer Sciences
- MSc. Basic Physics
About Me
I concluded a bachelor's degree on Physics and Biomolecular Sciences at USP in 2018. I worked at CAASO high school as a math mentor. I am currently working simultaneously on my Masters programme at IFSC-USP and on my undergraduate on Computer Science, at ICMC-USP. Last but not least, I am enjoying a lot learning Web Development!


2014
August 2014
July
June
May
April
March
February
January
2013
December
November
October
September
August
July
May
April
March
February
January
2012
November
October
September
August
July
June
April
March
February
2011
December
November
October
September
June
May
April
February
January
2010
November
October
September
August
July
April
March
February
January
2009
December
November
September
March
February
January
2008
September
August
July
June
May
February
January
2007
September
August
July
June
May
April
March
February
January
2006
December
November
October
September
August
June
May
April
March
February
January
2005
August


Loading...
Format (languagenotes.txt): title tags, comma-separated date and reference, page number notes ... more notes ... even more notes -- next title next tags... Run python process.py languagenotes


neck older pain (.) chiropractic chiropractors patients reported adult practice adults treatment headache Australian = .) {border-style care clinical = .). health px;} study survey use #ffffff;} research number years F(, pain. services significantly used ) = ., = .), associated cervicogenic male manage migraine outcomes = .%, age estimated higher proportion therapy (.%) Chiropractic However, Ther aged chiropractors’ data female group headache: manual massage response sample tension-type treatments visits according burden important methods px; {font-family () = ., = ., Research adults. and/or differ font-size management pain, patient people practitioners representative respondents results service using years (Table (with ./j.jmpt... ./s--- = .; ACORN Adams Australia Chiropractors Manip Physiol Spine [], average, chiropractors, dizziness double; falls findings gender manuscript. order participants population population. prevalence review surveyed (.%), (for .%, Neck Table addition adults, analysis approximately border-color border-width characteristics common differences effectiveness estimates exercise frequently global grid.. headache, high national nationally need particularly physical practices presented provide risk solid; studies systematic t() = − ., techniques workforce {font-style {font-weight (i.e. (in (including Amorin-Woods Carroll Chiropr Ethics Health Health, Man M = New Older Sibbritt Steel Survey Treatment University adjustment analgesics bias bold chiropractors. chiropractor’s co-morbid commonly considered contributed courier; cross-sectional current did em; employed experience favourable followed future greater guidelines headaches indicate interventions investigate living manipulation musculoskeletal nature needed network possible practicing presenting pt;} rate recommend significant spine stretching thoracic treat Private – years


public class ShardStateAction extends AbstractComponent
| 限定符和类型 | 类和说明 |
|---|---|
static interface |
ShardStateAction.Listener |
static class |
ShardStateAction.NoLongerPrimaryShardException |
static class |
ShardStateAction.ShardEntry |
static class |
ShardStateAction.ShardFailedClusterStateTaskExecutor |
static class |
ShardStateAction.ShardStartedClusterStateTaskExecutor |
| 限定符和类型 | 字段和说明 |
|---|---|
static java.lang.String |
SHARD_FAILED_ACTION_NAME |
static java.lang.String |
SHARD_STARTED_ACTION_NAME |
deprecationLogger, logger, settings
| 构造器和说明 |
|---|
ShardStateAction(Settings settings,
ClusterService clusterService,
TransportService transportService,
AllocationService allocationService,
RoutingService routingService,
ThreadPool threadPool) |
| 限定符和类型 | 方法和说明 |
|---|---|
void |
localShardFailed(ShardRouting shardRouting,
java.lang.String message,
java.lang.Exception failure,
ShardStateAction.Listener listener)
Send a shard failed request to the master node to update the cluster state when a shard on the local node failed.
|
void |
localShardFailed(ShardRouting shardRouting,
java.lang.String message,
java.lang.Exception failure,
ShardStateAction.Listener listener,
ClusterState currentState)
Send a shard failed request to the master node to update the cluster state when a shard on the local node failed.
|
void |
remoteShardFailed(ShardId shardId,
java.lang.String allocationId,
long primaryTerm,
java.lang.String message,
java.lang.Exception failure,
ShardStateAction.Listener listener)
Send a shard failed request to the master node to update the cluster state with the failure of a shard on another node.
|
void |
shardStarted(ShardRouting shardRouting,
java.lang.String message,
ShardStateAction.Listener listener) |
void |
shardStarted(ShardRouting shardRouting,
java.lang.String message,
ShardStateAction.Listener listener,
ClusterState currentState) |
protected void |
waitForNewMasterAndRetry(java.lang.String actionName,
ClusterStateObserver observer,
ShardStateAction.ShardEntry shardEntry,
ShardStateAction.Listener listener,
java.util.function.Predicate<ClusterState> changePredicate) |
logDeprecatedSetting, logRemovedSetting, nodeName
public static final java.lang.String SHARD_STARTED_ACTION_NAME
public static final java.lang.String SHARD_FAILED_ACTION_NAME
@Inject public ShardStateAction(Settings settings, ClusterService clusterService, TransportService transportService, AllocationService allocationService, RoutingService routingService, ThreadPool threadPool)
public void remoteShardFailed(ShardId shardId, java.lang.String allocationId, long primaryTerm, java.lang.String message, @Nullable java.lang.Exception failure, ShardStateAction.Listener listener)
shardId
- shard id of the shard to failallocationId
- allocation id of the shard to failprimaryTerm
- the primary term associated with the primary shard that is failing the shard. Must be strictly positive.message
- the reason for the failurefailure
- the underlying cause of the failurelistener
- callback upon completion of the requestpublic void localShardFailed(ShardRouting shardRouting, java.lang.String message, @Nullable java.lang.Exception failure, ShardStateAction.Listener listener)
public void localShardFailed(ShardRouting shardRouting, java.lang.String message, @Nullable java.lang.Exception failure, ShardStateAction.Listener listener, ClusterState currentState)
protected void waitForNewMasterAndRetry(java.lang.String actionName, ClusterStateObserver observer, ShardStateAction.ShardEntry shardEntry, ShardStateAction.Listener listener, java.util.function.Predicate<ClusterState> changePredicate)
public void shardStarted(ShardRouting shardRouting, java.lang.String message, ShardStateAction.Listener listener)
public void shardStarted(ShardRouting shardRouting, java.lang.String message, ShardStateAction.Listener listener, ClusterState currentState)


SANDELL
This page contains an index of all the individuals in the database with the surname of SANDELL. Selecting the person’s name will take you to that person’s individual page.
| Given Name | Birth |
|---|---|
| amanda | |
| amy, florence vera | |
| carol | 1937 |
| charles, gaylord | |
| edna, mae | |
| elijah | 1850 |
| elizabeth | |
| george, norman | |
| george | 1876-04-08 |
| holly | |
| ian, leslie | 1934-11-01 |
| jacqueline, dorothy | |
| james | |
| james, alwyn | 1904 |
| joan | |
| joanne | |
| kelly | |
| kerri | |
| leslie | 1912-03-26 |
| lillian | |
| marion, b | |
| natasha | |
| norman | |
| norman, allen | |
| norman | 1917-02-05 |
| robert, william | |
| robert, william | |
| roger | |
| violet | |
| walter, george | 1908 |
| william, albert | |
| william |


Reformaent
Galaxy: Gemi’holon
Sector: Emhetra
System: Reg’tiak
Planet: Vit’os
Northern polar mountains
Pirx’vero Botha, exa’legionate trooper, long range infantry support (sniper/medic)
It was a bug hunt —a primal bug hunt; that’s all we ever get sent on; that’s all us —the expeditionary, the exa’legionate.
 On a bug drop, being on overwatch is mostly the same as any other; being a medic is much more difficult. Why are citizens living on this shadt hole of a world anyway? Haven’t seen any greenery, the weather is bad; it matches the landscape. Those clouds don’t look like rain clouds; too hot for water here; lots of lightning; blue, yellow, and white. The bolts extend the sharp angels of the mountain terrain; sometimes in lone strikes, sometimes in rolling volleys. Nature —at least on this world— used the same pallet for its black and grey clouds, as it did for the grey rocks and the shadows they cast. We’ve been in these mountains for half an caldex (~14 hours) scouring the area of the last known position of a lost militia. Haven’t seen a trace. Don’t think anyone expects to find any either.
When we had been told what to expect, —during pre-drop briefing on the Legionatus drop ship. Commander Ter Hemner had presented the mission intel with his usual faux enthusiasm, and standard extra pauses between phrases he deemed important.
Hemner: “…LISTEN UP! For extraa intel! Cross-void swarmers! Known in this space as vetaxa’s! Are believed to be swarming! They are capable of jumps in excess of 40 stardex! (~40LY) They are VERY mean and dangerous! They are believed to be responsible for unexplained disappearances! In systems: Matcha’tiak, Hol-dreda, and Eli’par! We are to drop! We are to conduct search and rescue! For Vit’os local militia! The last distress transmission was post fiiive cal cycles! (~ 5 days ago)”
*a few troopers are unable to hide their murmuring thoughts*
no chance they are still alive was the gist.
“QUIET AND LISTEN. UP. TROOPERS!!….there may still be survivors! I am authorizing extra gunners! And medical! Per squad! Keep your scanners on the horizon for initial intel! Look out for HIVE MOTHERS!! DO NOT ENGAGE IF ENCOUNTERED!! Reemember, the exa’legionate coollects intel! And seeks exfil! IF A QUEEN OR BRAIN IS FOUND! Heeaavy hitters! Will be brought in to deal with…”
Hemner said more but the intel was clear; command thinks bugs have either eaten, or captured dozens of citizens. We are to search for them and eleminate all hostiles on the planet surface, while the remaining citizens are evacuated. A bug hunt, so, lots of dead bugs, and like any good exa’legionate, keep our eyes on the horizon, for ‘intial intel’; on this drop that would be a sighting of queen or brain.
Coming up on five interdex (~15 hours) down here; long way to go until Hemner pulls us out; he’s kept us boots on the ground for a long time before, several caldex (~days), or until we have contact; be it bug or civilia…. Wait. What is that? Those shadows don’t look like locals; they look spiny and mean. It is easy to tell who the enemy is on bug hunts. Raising trooper lead.
Prix: “Dixo two, movement ahead”
Dixo: “copy”
Dixo: “contact ground swarmers”
PFump-PFump-PFump-Crck-Crck-Crck BRRapp BRRapp
Watch for a help request from the lead…looks like they’ve got it handled. They’re really gunning them down, reporting: 20..21..22 dead swarmers, dont even need grenades, just UHV* exa’bolts. Gotta remember to keep an eye on the rocks above…I can’t see anything with all the interference, are those clouds just ash?. 40….45… guess they aren’t going to let me splat any. No ones gotten nicked…….yet. 50…58…60. PFump-mpp-mp BRapp-app-ap *guns silent*
Attack seems over.
An amber symbol starts blinking in the upper HUD; the boss wants a casualty status.
Prix: “Squad I see all green?”
Each trooper confirms —through their own interface— that they have no injuries; as they do, green dots —one for each of the fourteen troopers— light up in the lower right corner of her HUD..
Prix: “Command we are all clear”
Commander Ter Hemner should be pleased at our efficiency?
Not by his tone….
Hemner: “AIRBORNE INCOMING!”
CRAAACKackkack *a red icon in her HUD lights, indicating Egor twelve —one of the other two support troopers— is engaging/engaged*
Oh FUJUT!! where are THEY? There’s one falling, dead, there’s the rest, do I even need to use the scope? Got one lined up; gentle squeeze on the trigger; CRAAACKackkack “one bolt, one nerve stem”, these bugs are fast, guess that’s what all the wings are for. FujuT! Ajats! CRAAACKackkack That one was charging me I think. CRAAACKackkack That one definitely was; we seem to have some mutual feelings. CRAAACKackkack do I need to activate?…yep CRAAACKackkack Activating distress signal.
Prix: “I’M GETTING CHARGED”
Dixo: “CONCUSSION GRENADES”
BOOOMM BOOOMM BOOOMM
Ummff!  Concussion blasts!…
BOOOMM BOOOMM
Ummppff.
BOOOMM BOOOMM BOOOMM
Dixo: “All contacts terminated”
Hemner: “Squad Hettra you are all clear”
Uhhhghh, just breath…the nausea will pass…the war shock symptoms always do…
*Two of the green lights turned blue in the corner of her HUD indicating casualties, another light indicates the commander has set the squad stance to defensive*
Looks like I’m on the clock. Both injured callsigns —Emyatta six, and Raetera eight— are part of the extra gunners we got, haven’t even met them yet. Powering up position translator, I see a good spot with rock cover. Close to one of the injuries; close my eyes, relax, it’ll be quick, BLAAPBANGannggang. So loud, and bright. Few things put a trooper through a more disorienting experience than a position translation by a standard issue, exa’legionate, location projector.
Opening eyes; stowing my carbine; some more nausea —different than before; also something I’ve experienced a thousand times. Wait till I can feel the ground under my feet…ok heading to the injured trooper *one of the blue dots in her HUD starts blinking*; Raetera eight is getting taken care of, one less injured on the field.
There’s my injured trooper, under plenty of cover, starting vital and data download, starting medical scan, checking-in her injury.
*Both dots start blinking in her HUD.*
Info downloaded: Semeshea Emyatta, callsign Emyatta six; Sedra’hk’not*, —a tough and resilient gene-nexus— has sustained a deep laceration; from mid abdomen, to mid chest; grazing a few bones; no organs damaged; no major blood vessels damaged. There is so much blood on her, the cut must have bled hard. Whatever hit her, put a really wide gash in her —and her armor. Arrestor foam did its job; seal the wound from the atmosphere, stop all the bleeding. She has two hearts, both beating at an elevated rate; she seems to be in some pain, not enough for an acid wound though, no sign of poisoning. Alright, shield detached, firing mount stakes — pmp —, activating atmospheric lock out field, medixae kit and wash cooker powered up.
Prix: “Can you hear me?”

One good looking trooper…stay focused Prix…
Her mouth is moving, she’s answering, ugh the translator is lagging…
Shemeshea: “Yes”
I could get used to that voice, translated or not…
Prix: “Once the local air has stabilized, I’m going to remove your armor to get a better scan so I can fix you up”
Shemeshea: “OK doc”
Prix: “Follow the light with your eyes”
Her pupils are dilated, not sure what that means, making a physio query; should do a quick cognitive test while that query runs.
Prix: “Whats you GN-type?”
Shemeshea “Sedra’hk’not”
Atmosphere has stabilized enough, ambient temp has come down, accessing her suit, unlocking her armor. Looks like the query is finished; for her, dilated pupils are a pretty standard parasympathetic response. Interesting…
Prix: “Your name and callsign?”
Shemeshea: “Shemeshea Emyatta, callsign Emyatta six”
Prix: “alright I’m taking your armor off and going to see how bad this is”
Shemeshea “OK doc”
This is definitely were all the blood came from. Impressive physiology; this Sedra trooper is all taught, limber muscle; a born warrior, those spot patterns are…all over…stay focused Prix. Standard medae’bot wash should do it, just have to calibrate it for her. Medae washes are cooking, just enough time to clean and prep the wound.
Prix: “I’ll have something for your pain soon, just have to synthesize it”
Shemeshea: *nods*
Looks like I got most of the bug bits out, wash should dissolve the rest, mag ointment applied.
Prix: “This will sting a bit at first, but should go numb right after”
Shemeshea: *nods*
Sting? Probably feels cool after a laceration with…ohh there was some acid. Definitely should be cooler. Applying the medae wash. According the her gene-nexus data, her body creates cells which can neutralize foreign acid.
Prix: “Your wound was a deep laceration, do you know what hit you?”
Shemeshea: “A flyer, with a talon”
“There was some acid, but your body neutralized it. I’ve applied a medae wash that is rebuilding your tissue. You should be in gunning shape within a setadex (~ 16 minutes) and back to full strength within a cal cycle (~ 1 day). Try to avoid operating heavy weapons until them”
Shemeshea: “I’ll try not to…”
Prix: “Any questions?”
Shemeshea: *shakes her head, her eyes staying on Prix’s face*
Her heart rates are going down, vitals are stable, wound is reacting well. Almost time to say goodbye. Those dark eyes; black discs with iris rings that looked made from living dark amethyst; also still dilated; I think it takes more than the pain drugs to explain…oh shadt! How long was I staring at her eyes!?
Might as well say something, as long as shes still checked-in as an injury they had a private channel. Breath deep.
Prix: “Do you like bunk visitors?”
She’s looking at me but not saying anything…did she not understand? maybe she’s…
Shemeshea: “Yes”
Whew…okay lets not draw any attention from the bosses. Deactivating atmospheric exclusion field, stowing medi kit, stowing shield.
Prix: “Can you get up?”
She’s taking my hand; already regaining strength, more than enough to walk again. A rare, easy remediation
Her heart rate has gone down more, but still slightly elevated.
What would she think if she could see my heart rate like I can see hers?
It’s now or never, remember to breath….
Prix: “Can I visit your bunk sometime?”
Why did that sound so awkward? Time and moments seemed to stretch, conspiring to make me wait as long as possible —no matter how long she took to make up her mind.
Shemeshea: *nods*
I can’t remember the last time I wanted a ‘yes’ so bad.
Prix: “Your clear for battle duty trooper, good luck”
Shemeshea: “Thanks doc”
As I sign off on her returning to duty, her dot stops blinking and turns from blue to green.
Shemeshea’s faceplate goes from clear to flat grey, matching the ground and sky; she attaches maga’suture buttons in several places on either side of the gash in her armor. Magautoriom fibers deploy from the buttons; the fibers grasp each other and tighten; the rift of tega’steel plate running up and down her chest and abdomen closes.
Her defenses restored, she turns and jogs off in the direction of her assigned position, just like I will soon. The remaining blue dot turns green and the commander sets the combat stance to combat ready: patrol. Time to get to high ground.
I feel guilty sometimes —using my position to find companionship, technically a breach of protocol. But I’ve never met a Sedra, and in the legionate, finding someone ‘compatible’ to share a bunk with is very rare. I certainly didn’t expect to find one on this drop. If someone is in a desert, can you blame them for wanting to find a way to water? Maybe the squad has to wait a little longer before moving on…but hey, it gives my patient some more time to rest before returning to eventual-almost-certian-death, and who knows what we are marching into at this point anyway? I seemed to remember, even in my short time in the legionate, a savant-of-war saying “…on occasion, more strategic value can be gained from waiting, then taking action“…or something like that.
Even for me, especially for me, death is never far. The long night will take me eventually; like it does almost all of the legionate troopers. It might be by a bug, or by a zealot, or by a billion other deadly things, but it will happen, and before it does, I intend to find what comfort I can.
UHV: Ultra High Velocity


The Gifanator
Saving the world, one gif at a time!
To start, send a text message to this number
Commands:
- To get a gif with a specific search term, just text that word
- To get a random gif, text random
- To send a gif to a friend, text the specific search term plus your friend's 10-digit number
- To send a random gif to a friend, text random plus your friend's 10-digit number
FUN WORDS TO TRY
Negative emotions:
- no
- facepalm
- fail
- deal with it
- frustrated
- confused
- unimpressed
- shocked
- surprised patrick
- eye roll
- yawn
- bitch please
- come at me bro
- haters gonna hate
- sorry
- feels
Positive emotions:
- yes
- thank you
- hug
- thumbs up
- win
- wink
- laughing
- smile
- hello
- excited
- applause
- high five
- love
- like a boss
- bitch im fabulous
- finger guns
- dance
- gangnam style
- swag
- fangirling
- keep calm and
- family guy