<commit_before>/**
 * Builds the query that retrieve the list of all classifications.
 */
const classifications = () => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT ?classification ?code ?label WHERE {
    ?classification rdf:type skos:ConceptScheme ; skos:notation ?code ; skos:prefLabel ?label .
  } ORDER BY ?code
 `

/**
 * Builds the query that gets the details about a classification.
 */
 //TODO @Franck workaround the fact the DB is not returning any line otherwise
const classificationDetails = uri => `
  PREFIX dcterms: <http://purl.org/dc/terms/>
  PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
  SELECT ?code ?label ?issued WHERE {
    <${uri}> skos:notation ?code ; skos:prefLabel ?label ; dcterms:issued ?issued .
  }
`

/**
 * Builds the query that gets the levels of a classification.
 * Using SPARQL 1.1 property paths, see https://www.w3.org/TR/sparql11-query/#propertypaths
 */
const classificationLevels = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT DISTINCT ?level ?depth ?label
  WHERE {
    <${uri}> xkos:levels/rdf:rest*/rdf:first ?level .
    ?level xkos:depth ?depth ; skos:prefLabel ?label .
  } ORDER BY ?depth
`
/**
 * Builds the query that gets the correspondence tables for a given classification.
 * TODO Improve the database, all tables don't have a label.
 */
const classificationCorrespondences = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT ?table ?definition ?code WHERE {
    ?table xkos:compares <${uri}> ;
           skos:definition ?definition
    OPTIONAL {
      ?table skos:notation ?code
    }
    # We miss somme correspondences where there is no definition in english
    FILTER ( langMatches(lang(?definition), "EN"))
  }
`

/**
 * Builds the query that gets all the correspondence tables.
 * TODO Improve the database, all tables don't have a label.
 */
const correspondences = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT ?table ?label WHERE {
    ?table rdf:type xkos:Corresponce .
    OPTIONAL { ?table skos:prefLabel ?label }
  }
`

/**
 * Builds the query that gets the list of items of a given level.
 */
const levelItems = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT ?item ?code ?label WHERE {
    <${uri}> skos:member ?item .
    ?item skos:notation ?code ; skos:prefLabel ?label .
  } ORDER BY ?code
`

/**
 * Builds the query that gets the list of items of a given level.
 */
const classificationItems = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT ?item ?code ?label WHERE {
    <${uri}> xkos:levels/rdf:rest*/rdf:first ?level .
    ?level skos:member ?item .
    ?item skos:notation ?code ; skos:prefLabel ?label .
  } ORDER BY ?code LIMIT 25
`

const itemDetails = item => `
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT ?label ?code ?label ?text ?cl ?clCode ?clLabel ?parent ?parentCode
      ?parentLabel {
    <${item}> skos:prefLabel ?label ;
              skos:notation ?code ;
              skos:inScheme ?cl .
    ?cl skos:prefLabel ?clLabel

    OPTIONAL {
      ?cl skos:notation ?clCode .
    }

    OPTIONAL {
      <${item}> skos:broader ?parent .
        ?parent skos:prefLabel ?parentLabel .
      OPTIONAL { ?parent skos:notation ?parentCode . }
      FILTER ( langMatches(lang(?parentLabel), "EN"))
    }

    # if we use only one OPTIONAL keyword, we will not receive the note
    # if there is no parent
    OPTIONAL {
     <${item}> xkos:coreContentNote ?content .
     ?content xkos:plainText ?text .
    }
    FILTER (
      langMatches(lang(?label), "EN") &&
      langMatches(lang(?clLabel), "EN"))
  }
`

const itemChildren = item => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT ?item ?code ?label {
    <${item}> skos:narrower ?item .
    ?item skos:notation ?code .
    ?item skos:prefLabel ?label
    FILTER (langMatches(lang(?label), "EN"))
  } ORDER BY ?code
`

const correspondenceAssociations = correspondence => `
SELECT
  ?association
  ?source ?sourceCode ?sourceLabel
  ?target ?targetCode ?targetLabel
WHERE {
  <${correspondence}> xkos:madeOf ?association .
  ?association xkos:sourceConcept ?source ;
               xkos:targetConcept ?target .
  OPTIONAL {
    ?source skos:notation ?sourceCode ;
            skos:prefLabel ?sourceLabel .
    FILTER (langMatches(lang(?sourceLabel), "EN"))
  }
  OPTIONAL {
    ?target skos:notation ?targetCode ;
            skos:prefLabel ?targetLabel
    FILTER (langMatches(lang(?targetLabel), "EN"))
  }
}
ORDER BY ?sourceCode
`

const itemCorrespondences = hash => {
  const  [item, correspondenceTable] = hash.split('||');
  return `
    PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
    PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
    SELECT ?association ?item ?code ?label {
    {
    <${correspondenceTable}> xkos:madeOf ?association .
  ?association xkos:sourceConcept <${item}> ;
               xkos:targetConcept ?item .
               ?item skos:notation ?code .
               ?item skos:prefLabel ?label
               FILTER (langMatches(lang(?label), "EN"))
    }
    UNION {

  <${correspondenceTable}> xkos:madeOf ?association .
  ?association xkos:targetConcept <${item}> ;
               xkos:sourceConcept ?item .
               ?item skos:notation ?code .
               ?item skos:prefLabel ?label
               FILTER (langMatches(lang(?label), "EN"))

  }
  }
  `
}

const correspondenceDetails = correspondence => `
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT ?classification ?code ?label {
    <${correspondence}> xkos:compares ?classification .
    ?classification skos:prefLabel ?label ;
                    skos:notation ?code
  }
`
const searchEverything = keyword => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT ?subject ?predicate ?match ?score WHERE {
    ?subject ?predicate ?match .
    (?match ?score) <tag:stardog:api:property:textMatch> '${keyword}*'.
  }
`

const searchItems = hash => {
  const  [keyword, searchForCode] = hash.split('||');
  if(searchForCode==='true'){
    return  `
    PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
    PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
    SELECT DISTINCT
      ?item ?code ?itemLabel
      ?classification ?classificationLabel ?coreContentNoteText ?additionalContentNoteText
    WHERE {
      ?item skos:inScheme ?classification ;
            skos:notation ?code ;
            skos:prefLabel ?itemLabel .
      ?classification skos:prefLabel ?classificationLabel .
      FILTER regex(?code, "${keyword}", "i")
    } order by ?code
    `
  }else{

  return  `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT
    ?item ?code ?itemLabel
    ?classification ?classificationLabel ?coreContentNoteText ?additionalContentNoteText
  WHERE {
    ?item skos:inScheme ?classification ;
          skos:notation ?code ;
          skos:prefLabel ?itemLabel ;
          xkos:coreContentNote ?coreContentNote ;
          xkos:additionalContentNote ?additionalContentNote .
    ?coreContentNote xkos:plainText ?coreContentNoteText .
    ?additionalContentNote   xkos:plainText ?additionalContentNoteText .
    ?classification skos:prefLabel ?classificationLabel .

    FILTER ( langMatches(lang(?itemLabel), "EN") &&
    (
      regex(?code, "${keyword}", "i") ||
      regex(?itemLabel, "${keyword}", "i") ||
      regex(?coreContentNoteText, "${keyword}", "i") ||
      regex(?additionalContentNoteText, "${keyword}", "i") ||
      regex(?classificationLabel, "${keyword}", "i")
    ))
  } order by ?code
`}
}

export default {
  correspondenceAssociations,
  classifications,
  classificationDetails,
  classificationItems,
  classificationLevels,
  classificationCorrespondences,
  correspondences,
  levelItems,
  itemCorrespondences,
  itemDetails,
  itemChildren,
  correspondenceDetails,
  searchEverything,
  searchItems
}
<commit_msg>Retrieve correspondences when lang is not set<commit_after>/**
 * Builds the query that retrieve the list of all classifications.
 */
const classifications = () => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT ?classification ?code ?label WHERE {
    ?classification rdf:type skos:ConceptScheme ; skos:notation ?code ; skos:prefLabel ?label .
  } ORDER BY ?code
 `

/**
 * Builds the query that gets the details about a classification.
 */
 //TODO @Franck workaround the fact the DB is not returning any line otherwise
const classificationDetails = uri => `
  PREFIX dcterms: <http://purl.org/dc/terms/>
  PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
  SELECT ?code ?label ?issued WHERE {
    <${uri}> skos:notation ?code ; skos:prefLabel ?label ; dcterms:issued ?issued .
  }
`

/**
 * Builds the query that gets the levels of a classification.
 * Using SPARQL 1.1 property paths, see https://www.w3.org/TR/sparql11-query/#propertypaths
 */
const classificationLevels = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT DISTINCT ?level ?depth ?label
  WHERE {
    <${uri}> xkos:levels/rdf:rest*/rdf:first ?level .
    ?level xkos:depth ?depth ; skos:prefLabel ?label .
  } ORDER BY ?depth
`
/**
 * Builds the query that gets the correspondence tables for a given classification.
 * TODO Improve the database, all tables don't have a label.
 */
const classificationCorrespondences = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT ?table ?definition ?code WHERE {
    ?table xkos:compares <${uri}> ;
           skos:definition ?definition
    OPTIONAL {
      ?table skos:notation ?code
    }
    # We miss somme correspondences where there is no definition in english
    FILTER ( langMatches(lang(?definition), "EN") || lang(?definition)="")
  }
`

/**
 * Builds the query that gets all the correspondence tables.
 * TODO Improve the database, all tables don't have a label.
 */
const correspondences = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT ?table ?label WHERE {
    ?table rdf:type xkos:Corresponce .
    OPTIONAL { ?table skos:prefLabel ?label }
  }
`

/**
 * Builds the query that gets the list of items of a given level.
 */
const levelItems = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT ?item ?code ?label WHERE {
    <${uri}> skos:member ?item .
    ?item skos:notation ?code ; skos:prefLabel ?label .
  } ORDER BY ?code
`

/**
 * Builds the query that gets the list of items of a given level.
 */
const classificationItems = uri => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT ?item ?code ?label WHERE {
    <${uri}> xkos:levels/rdf:rest*/rdf:first ?level .
    ?level skos:member ?item .
    ?item skos:notation ?code ; skos:prefLabel ?label .
  } ORDER BY ?code LIMIT 25
`

const itemDetails = item => `
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT ?label ?code ?label ?text ?cl ?clCode ?clLabel ?parent ?parentCode
      ?parentLabel {
    <${item}> skos:prefLabel ?label ;
              skos:notation ?code ;
              skos:inScheme ?cl .
    ?cl skos:prefLabel ?clLabel

    OPTIONAL {
      ?cl skos:notation ?clCode .
    }

    OPTIONAL {
      <${item}> skos:broader ?parent .
        ?parent skos:prefLabel ?parentLabel .
      OPTIONAL { ?parent skos:notation ?parentCode . }
      FILTER ( langMatches(lang(?parentLabel), "EN"))
    }

    # if we use only one OPTIONAL keyword, we will not receive the note
    # if there is no parent
    OPTIONAL {
     <${item}> xkos:coreContentNote ?content .
     ?content xkos:plainText ?text .
    }
    FILTER (
      langMatches(lang(?label), "EN") &&
      langMatches(lang(?clLabel), "EN"))
  }
`

const itemChildren = item => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT ?item ?code ?label {
    <${item}> skos:narrower ?item .
    ?item skos:notation ?code .
    ?item skos:prefLabel ?label
    FILTER (langMatches(lang(?label), "EN"))
  } ORDER BY ?code
`

const correspondenceAssociations = correspondence => `
SELECT
  ?association
  ?source ?sourceCode ?sourceLabel
  ?target ?targetCode ?targetLabel
WHERE {
  <${correspondence}> xkos:madeOf ?association .
  ?association xkos:sourceConcept ?source ;
               xkos:targetConcept ?target .
  OPTIONAL {
    ?source skos:notation ?sourceCode ;
            skos:prefLabel ?sourceLabel .
    FILTER (langMatches(lang(?sourceLabel), "EN"))
  }
  OPTIONAL {
    ?target skos:notation ?targetCode ;
            skos:prefLabel ?targetLabel
    FILTER (langMatches(lang(?targetLabel), "EN"))
  }
}
ORDER BY ?sourceCode
`

const itemCorrespondences = hash => {
  const  [item, correspondenceTable] = hash.split('||');
  return `
    PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
    PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
    SELECT ?association ?item ?code ?label {
    {
    <${correspondenceTable}> xkos:madeOf ?association .
  ?association xkos:sourceConcept <${item}> ;
               xkos:targetConcept ?item .
               ?item skos:notation ?code .
               ?item skos:prefLabel ?label
               FILTER (langMatches(lang(?label), "EN"))
    }
    UNION {

  <${correspondenceTable}> xkos:madeOf ?association .
  ?association xkos:targetConcept <${item}> ;
               xkos:sourceConcept ?item .
               ?item skos:notation ?code .
               ?item skos:prefLabel ?label
               FILTER (langMatches(lang(?label), "EN"))

  }
  }
  `
}

const correspondenceDetails = correspondence => `
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  PREFIX xkos:<http://rdf-vocabulary.ddialliance.org/xkos#>
  SELECT ?classification ?code ?label {
    <${correspondence}> xkos:compares ?classification .
    ?classification skos:prefLabel ?label ;
                    skos:notation ?code
  }
`
const searchEverything = keyword => `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT ?subject ?predicate ?match ?score WHERE {
    ?subject ?predicate ?match .
    (?match ?score) <tag:stardog:api:property:textMatch> '${keyword}*'.
  }
`

const searchItems = hash => {
  const  [keyword, searchForCode] = hash.split('||');
  if(searchForCode==='true'){
    return  `
    PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
    PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
    SELECT DISTINCT
      ?item ?code ?itemLabel
      ?classification ?classificationLabel ?coreContentNoteText ?additionalContentNoteText
    WHERE {
      ?item skos:inScheme ?classification ;
            skos:notation ?code ;
            skos:prefLabel ?itemLabel .
      ?classification skos:prefLabel ?classificationLabel .
      FILTER regex(?code, "${keyword}", "i")
    } order by ?code
    `
  }else{

  return  `
  PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT
    ?item ?code ?itemLabel
    ?classification ?classificationLabel ?coreContentNoteText ?additionalContentNoteText
  WHERE {
    ?item skos:inScheme ?classification ;
          skos:notation ?code ;
          skos:prefLabel ?itemLabel ;
          xkos:coreContentNote ?coreContentNote ;
          xkos:additionalContentNote ?additionalContentNote .
    ?coreContentNote xkos:plainText ?coreContentNoteText .
    ?additionalContentNote   xkos:plainText ?additionalContentNoteText .
    ?classification skos:prefLabel ?classificationLabel .

    FILTER ( langMatches(lang(?itemLabel), "EN") &&
    (
      regex(?code, "${keyword}", "i") ||
      regex(?itemLabel, "${keyword}", "i") ||
      regex(?coreContentNoteText, "${keyword}", "i") ||
      regex(?additionalContentNoteText, "${keyword}", "i") ||
      regex(?classificationLabel, "${keyword}", "i")
    ))
  } order by ?code
`}
}

export default {
  correspondenceAssociations,
  classifications,
  classificationDetails,
  classificationItems,
  classificationLevels,
  classificationCorrespondences,
  correspondences,
  levelItems,
  itemCorrespondences,
  itemDetails,
  itemChildren,
  correspondenceDetails,
  searchEverything,
  searchItems
}
<|endoftext|>


<commit_before>// -*- indent-tabs-mode: nil; tab-width: 2; -*-
// Copyright (C) 2013 Colin Walters <walters@verbum.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.

const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Format = imports.format;

const GSystem = imports.gi.GSystem;

const Builtin = imports.builtin;
const ArgParse = imports.argparse;
const ProcUtil = imports.procutil;
const Task = imports.task;
const LibQA = imports.libqa;
const JSUtil = imports.jsutil;
const JSONUtil = imports.jsonutil;

const TIMEOUT_SECONDS = 10 * 60;
const COMPLETE_IDLE_WAIT_SECONDS = 10;

const TestOneDisk = new Lang.Class({
    Name: 'TestOneDisk',

    _init: function(parentTask, testRequiredMessageIds, testFailedMessageIds, testStatusMessageId) {
        this._parentTask = parentTask;
        this._testRequiredMessageIds = testRequiredMessageIds;
        this._testFailedMessageIds = testFailedMessageIds;
        this._statusMessageId = testStatusMessageId;
    },

    _fail: function(message) {
        if (this._failed)
            return;
        this._failed = true;
        this._failedMessage = message;
        this._screenshot(true);
    },

    _onQemuExited: function(proc, result) {
        let [success, status] = ProcUtil.asyncWaitCheckFinish(proc, result);
        this._qemu = null;
        this._loop.quit();
        if (!success) {
            this._fail("Qemu exited with status " + status);
        }
    },

    _onTimeout: function() {
        print("Timeout reached");
        for (let msgid in this._pendingRequiredMessageIds) {
            print("Did not see MESSAGE_ID=" + msgid);
        }
        this._fail("Timed out");
        this._loop.quit();
    },

    _onJournalOpen: function(file, result) {
        try {
            this._journalStream = file.read_finish(result);
            this._journalDataStream = Gio.DataInputStream.new(this._journalStream);
            this._openedJournal = true;
            this._readingJournal = true;
            this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
                                                    Lang.bind(this, this._onJournalReadLine));
        } catch (e) {
            this._fail("Journal open failed: " + e);
            this._loop.quit();
        }
    },

    _onJournalReadLine: function(stream, result) {
        this._readingJournal = false;
        let line, len;
        try {
            [line, len] = stream.read_line_finish_utf8(result);
        } catch (e) {
            this._fail(e.toString());
            this._loop.quit();
            throw e;
        }
        if (this._foundAllMessageIds || this._failed)
            return;
        if (line) {
            let data = JSON.parse(line);
            let message = data['MESSAGE'];
            let messageId = data['MESSAGE_ID'];
            if (messageId) {
                if (this._pendingRequiredMessageIds[messageId]) {
                    print("Found required message ID " + messageId);
                    print(message);
                    delete this._pendingRequiredMessageIds[messageId];
                    this._countPendingRequiredMessageIds--;
                } else if (this._failMessageIds[messageId]) {
                    this._fail("Found failure message ID " + messageId);
                    print(message);
                    this._loop.quit();
                }
                if (messageId === this._statusMessageId) {
                    print(message);
	                  let statusTxtPath = Gio.File.new_for_path('status.txt');
	                  statusTxtPath.replace_contents(message + '\n', null, false,
				                                           Gio.FileCreateFlags.REPLACE_DESTINATION, this._cancellable);
                }
                this._parentTask._handleMessage(data, this._cancellable);
            }
            if (this._countPendingRequiredMessageIds == 0 && !this._foundAllMessageIds) {
                print("Found all required message IDs");
                this._foundAllMessageIds = true;
                GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, COMPLETE_IDLE_WAIT_SECONDS,
                                         Lang.bind(this, this._onFinalWait));
            } else {
                this._readingJournal = true;
                this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
                                                        Lang.bind(this, this._onJournalReadLine));
            }
        }
    },

    _onJournalChanged: function(monitor, file, otherFile, eventType) {
        if (this._foundAllMessageIds || this._failed)
            return;
        if (!this._openedJournal) {
            this._openedJournal = true;
            file.read_async(GLib.PRIORITY_DEFAULT,
                            this._cancellable,
                            Lang.bind(this, this._onJournalOpen));
        } else if (!this._readingJournal) {
            this._readingJournal = true;
            this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
                                                    Lang.bind(this, this._onJournalReadLine));
        }
    },

    _ensureQemuConnection: function() {
        if (!this._qemuSocketConn) {
            let path = Gio.File.new_for_path('.').get_relative_path(this._subworkdir.get_child("qemu.monitor"));
            let address = Gio.UnixSocketAddress.new_with_type(path, Gio.UnixSocketAddressType.PATH);
            let socketClient = new Gio.SocketClient();
            this._qemuSocketConn = socketClient.connect(address, this._cancellable);
            this._qemuOut = Gio.DataOutputStream.new(this._qemuSocketConn.get_output_stream());
            this._qemuIn = Gio.DataInputStream.new(this._qemuSocketConn.get_input_stream());
            let [response, len] = this._qemuIn.read_line_utf8(this._cancellable);
            this._qemuCommand({ "execute": "qmp_capabilities" });
        }
    },

    _qemuCommand: function(cmd) {
        this._ensureQemuConnection();
        let cmdStr = JSON.stringify(cmd);
        this._qemuOut.put_string(cmdStr, this._cancellable);
        let [response, len] = this._qemuIn.read_line_utf8(this._cancellable);
    },

    _screenshot: function(isFinal) {
        let filename;
        let modified = true;
        if (isFinal)
            filename = "screenshot-final.ppm";
        else
            filename = "screenshot-" + this._screenshotSerial + ".ppm";

        this._qemuCommand({"execute": "screendump", "arguments": { "filename": filename }});

        let filePath = this._subworkdir.get_child(filename);

        if (!isFinal) {
	          let contentsBytes = GSystem.file_map_readonly(filePath, this._cancellable);
	          let csum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256,
						                                           contentsBytes);

            modified = this._lastScreenshotChecksum != csum;
            if (!modified) {
                GSystem.file_unlink(filePath, this._cancellable);
            } else {
                this._lastScreenshotChecksum = csum;
            }
            this._screenshotSerial++;
        }

        // Convert to PNG if possible
        if (modified && imports.gi.GdkPixbuf) {
            let GdkPixbuf = imports.gi.GdkPixbuf;
            let pixbuf = null;
            try {
                pixbuf = GdkPixbuf.Pixbuf.new_from_file(filePath.get_path());

            } catch (e) {
                if (e.domain != GdkPixbuf.PixbufError)
                    throw e;
                print(e);
            }
            if (pixbuf != null) {
                let outFilename = this._subworkdir.get_child(filename.replace(/ppm$/, 'png'));
                pixbuf.savev(outFilename.get_path(), "png", [], []);
            }
            GSystem.file_unlink(filePath, this._cancellable);
        }
    },

    _idleScreenshot: function() {
        this._screenshot(false);
        return true;
    },

    _onFinalWait: function() {
        print("Final wait complete");

        this._screenshot(true);

        this._loop.quit();
    },

    execute: function(subworkdir, buildData, repo, diskPath, cancellable) {
        print("Testing disk " + diskPath.get_path());
        this._buildData = buildData;
        this._repo = repo;
        this._subworkdir = subworkdir;
        this._loop = GLib.MainLoop.new(null, true);
        this._foundAllMessageIds = false;
        this._failed = false;
        this._journalStream = null;
        this._journalDataStream = null;
        this._openedJournal = false;
        this._readingJournal = false;
        this._pendingRequiredMessageIds = {};
        this._failMessageIds = {};
        this._countPendingRequiredMessageIds = 0;
        this._screenshotSerial = 0;
        this._lastScreenshotChecksum = null;
        this._qemuSocket = null;
        print("Will wait for message IDs: " + JSON.stringify(this._testRequiredMessageIds));
        for (let i = 0; i < this._testRequiredMessageIds.length; i++) {
            this._pendingRequiredMessageIds[this._testRequiredMessageIds[i]] = true;
            this._countPendingRequiredMessageIds += 1;
        }
        for (let i = 0; i < this._testFailedMessageIds.length; i++) {
            this._failMessageIds[this._testFailedMessageIds[i]] = true;
        }
        this._cancellable = cancellable;

        // HACK
        if (diskPath.get_basename().indexOf('x86_64') >= 0)
            this._diskArch = 'x86_64';
        else
            this._diskArch = 'i686';

        let qemuArgs = LibQA.getDefaultQemuOptions({ parallel: true });

        let diskClone = subworkdir.get_child('testoverlay-' + diskPath.get_basename());
        GSystem.shutil_rm_rf(diskClone, cancellable);

        LibQA.createDiskSnapshot(diskPath, diskClone, cancellable);
        let [gfmnt, mntdir] = LibQA.newReadWriteMount(diskClone, cancellable);
        try {
            LibQA.modifyBootloaderAppendKernelArgs(mntdir, ["console=ttyS0"], cancellable);

            let [currentDir, currentEtcDir] = LibQA.getDeployDirs(mntdir, 'gnome-ostree');

            LibQA.injectExportJournal(currentDir, currentEtcDir, cancellable);
            LibQA.injectTestUserCreation(currentDir, currentEtcDir, 'testuser', {}, cancellable);
            LibQA.enableAutologin(currentDir, currentEtcDir, 'testuser', cancellable);

            this._parentTask._prepareDisk(mntdir, this._diskArch, cancellable);
        } finally {
            gfmnt.umount(cancellable);
        }

        let consoleOutput = subworkdir.get_child('console.out');
        let journalOutput = subworkdir.get_child('journal-json.txt');

        qemuArgs.push.apply(qemuArgs, ['-drive', 'file=' + diskClone.get_path() + ',if=virtio',
                                       '-vnc', 'none',
                                       '-serial', 'file:' + consoleOutput.get_path(),
                                       '-chardev', 'socket,id=charmonitor,path=qemu.monitor,server,nowait',
                                       '-mon', 'chardev=charmonitor,id=monitor,mode=control',
                                       '-device', 'virtio-serial',
                                       '-chardev', 'file,id=journaljson,path=' + journalOutput.get_path(),
                                       '-device', 'virtserialport,chardev=journaljson,name=org.gnome.journaljson']);

        let qemuContext = new GSystem.SubprocessContext({ argv: qemuArgs });
        qemuContext.set_cwd(subworkdir.get_path());
        let qemu = new GSystem.Subprocess({context: qemuContext});
        this._qemu = qemu;
        print("starting qemu : " + qemuArgs.join(' '));
        qemu.init(cancellable);

        qemu.wait(cancellable, Lang.bind(this, this._onQemuExited));

        let journalMonitor = journalOutput.monitor_file(0, cancellable);
        journalMonitor.connect('changed', Lang.bind(this, this._onJournalChanged));

        let timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, TIMEOUT_SECONDS,
                                                 Lang.bind(this, this._onTimeout));

        // Let's only do a screenshot every 3 seconds, I think it's slowing things down...
        let screenshotTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3,
                                                 Lang.bind(this, this._idleScreenshot));

        this._loop.run();

        if (this._qemu)
            this._qemu.force_exit();

        GLib.source_remove(timeoutId);

        if (this._failed) {
            throw new Error(this._failedMessage);
        }

        this._parentTask._postQemu(cancellable);

        print("Completed testing of " + diskPath.get_basename());
    }
});

const TestBase = new Lang.Class({
    Name: 'TestBase',
    Extends: Task.Task,

    TaskDef: {
        TaskName: "testbase",
        TaskAfter: ['builddisks'],
    },

    BaseRequiredMessageIDs: ["39f53479d3a045ac8e11786248231fbf", // graphical.target
                             "f77379a8490b408bbe5f6940505a777b",  // systemd-journald
                            ],

    BaseFailedMessageIDs: [],

    RequiredMessageIDs: [],
    FailedMessageIDs: [],

    StatusMessageID: [],

    CompletedTag: null,

    _prepareDisk: function(mntdir, cancellable) {
        // Nothing, intended for subclasses
    },

    _handleMessage: function(message, cancellable) {
        return false;
    },

    _postQemu: function(cancellable) {
    },

    execute: function(cancellable) {
	      let imageDir = this.workdir.get_child('images');
	      let currentImages = imageDir.get_child('current');

        let e = currentImages.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
                                                 cancellable);
        let info;
        let buildJson;
        let disksToTest = [];

        while ((info = e.next_file(cancellable)) != null) {
            let name = info.get_name();
            if (name.indexOf('build-') == 0 && JSUtil.stringEndswith(name, '.json')) {
                buildJson = e.get_child(info);
                continue;
            }
            if (!JSUtil.stringEndswith(name, '.qcow2'))
                continue;
            disksToTest.push(name);
        }
        e.close(null);
        this._buildData = null;
        if (buildJson != null)
            this._buildData = JSONUtil.loadJson(buildJson, cancellable);
        for (let i = 0; i < disksToTest.length; i++) {
            let name = disksToTest[i];
            let workdirName = 'work-' + name.replace(/\.qcow2$/, '');
            let subworkdir = Gio.File.new_for_path(workdirName);
            GSystem.file_ensure_directory(subworkdir, true, cancellable);
            let test = new TestOneDisk(this,
                                       this.BaseRequiredMessageIDs.concat(this.RequiredMessageIDs),
                                       this.BaseFailedMessageIDs.concat(this.FailedMessageIDs),
                                       this.StatusMessageID);
            test.execute(subworkdir, this._buildData, this.repo, currentImages.get_child(name), cancellable);
        }

        let buildData = this._buildData;
        if (buildJson != null && this.CompletedTag !== null) {
            let snapshot = buildData['snapshot'];
            this.ostreeRepo.prepare_transaction(cancellable);
            for (let targetName in buildData['targets']) {
                let targetRev = buildData['targets'][targetName];
                let lastSlash = targetName.lastIndexOf('/');
                let testedRefName = snapshot['osname'] + '/' + this.CompletedTag + targetName.substr(lastSlash);
                this.ostreeRepo.transaction_set_ref(null, testedRefName, targetRev);
                print(Format.vprintf("Wrote ref: %s => %s", [testedRefName, targetRev]));
            }
            this.ostreeRepo.commit_transaction(cancellable);
        } else {
            print("No build json found, not tagging");
        }
    }
});
<commit_msg>testbase: Fix hardcoding of gnome-ostree<commit_after>// -*- indent-tabs-mode: nil; tab-width: 2; -*-
// Copyright (C) 2013 Colin Walters <walters@verbum.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.

const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Format = imports.format;

const GSystem = imports.gi.GSystem;

const Builtin = imports.builtin;
const ArgParse = imports.argparse;
const ProcUtil = imports.procutil;
const Task = imports.task;
const LibQA = imports.libqa;
const JSUtil = imports.jsutil;
const JSONUtil = imports.jsonutil;

const TIMEOUT_SECONDS = 10 * 60;
const COMPLETE_IDLE_WAIT_SECONDS = 10;

const TestOneDisk = new Lang.Class({
    Name: 'TestOneDisk',

    _init: function(parentTask, testRequiredMessageIds, testFailedMessageIds, testStatusMessageId) {
        this._parentTask = parentTask;
        this._testRequiredMessageIds = testRequiredMessageIds;
        this._testFailedMessageIds = testFailedMessageIds;
        this._statusMessageId = testStatusMessageId;
    },

    _fail: function(message) {
        if (this._failed)
            return;
        this._failed = true;
        this._failedMessage = message;
        this._screenshot(true);
    },

    _onQemuExited: function(proc, result) {
        let [success, status] = ProcUtil.asyncWaitCheckFinish(proc, result);
        this._qemu = null;
        this._loop.quit();
        if (!success) {
            this._fail("Qemu exited with status " + status);
        }
    },

    _onTimeout: function() {
        print("Timeout reached");
        for (let msgid in this._pendingRequiredMessageIds) {
            print("Did not see MESSAGE_ID=" + msgid);
        }
        this._fail("Timed out");
        this._loop.quit();
    },

    _onJournalOpen: function(file, result) {
        try {
            this._journalStream = file.read_finish(result);
            this._journalDataStream = Gio.DataInputStream.new(this._journalStream);
            this._openedJournal = true;
            this._readingJournal = true;
            this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
                                                    Lang.bind(this, this._onJournalReadLine));
        } catch (e) {
            this._fail("Journal open failed: " + e);
            this._loop.quit();
        }
    },

    _onJournalReadLine: function(stream, result) {
        this._readingJournal = false;
        let line, len;
        try {
            [line, len] = stream.read_line_finish_utf8(result);
        } catch (e) {
            this._fail(e.toString());
            this._loop.quit();
            throw e;
        }
        if (this._foundAllMessageIds || this._failed)
            return;
        if (line) {
            let data = JSON.parse(line);
            let message = data['MESSAGE'];
            let messageId = data['MESSAGE_ID'];
            if (messageId) {
                if (this._pendingRequiredMessageIds[messageId]) {
                    print("Found required message ID " + messageId);
                    print(message);
                    delete this._pendingRequiredMessageIds[messageId];
                    this._countPendingRequiredMessageIds--;
                } else if (this._failMessageIds[messageId]) {
                    this._fail("Found failure message ID " + messageId);
                    print(message);
                    this._loop.quit();
                }
                if (messageId === this._statusMessageId) {
                    print(message);
	                  let statusTxtPath = Gio.File.new_for_path('status.txt');
	                  statusTxtPath.replace_contents(message + '\n', null, false,
				                                           Gio.FileCreateFlags.REPLACE_DESTINATION, this._cancellable);
                }
                this._parentTask._handleMessage(data, this._cancellable);
            }
            if (this._countPendingRequiredMessageIds == 0 && !this._foundAllMessageIds) {
                print("Found all required message IDs");
                this._foundAllMessageIds = true;
                GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, COMPLETE_IDLE_WAIT_SECONDS,
                                         Lang.bind(this, this._onFinalWait));
            } else {
                this._readingJournal = true;
                this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
                                                        Lang.bind(this, this._onJournalReadLine));
            }
        }
    },

    _onJournalChanged: function(monitor, file, otherFile, eventType) {
        if (this._foundAllMessageIds || this._failed)
            return;
        if (!this._openedJournal) {
            this._openedJournal = true;
            file.read_async(GLib.PRIORITY_DEFAULT,
                            this._cancellable,
                            Lang.bind(this, this._onJournalOpen));
        } else if (!this._readingJournal) {
            this._readingJournal = true;
            this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
                                                    Lang.bind(this, this._onJournalReadLine));
        }
    },

    _ensureQemuConnection: function() {
        if (!this._qemuSocketConn) {
            let path = Gio.File.new_for_path('.').get_relative_path(this._subworkdir.get_child("qemu.monitor"));
            let address = Gio.UnixSocketAddress.new_with_type(path, Gio.UnixSocketAddressType.PATH);
            let socketClient = new Gio.SocketClient();
            this._qemuSocketConn = socketClient.connect(address, this._cancellable);
            this._qemuOut = Gio.DataOutputStream.new(this._qemuSocketConn.get_output_stream());
            this._qemuIn = Gio.DataInputStream.new(this._qemuSocketConn.get_input_stream());
            let [response, len] = this._qemuIn.read_line_utf8(this._cancellable);
            this._qemuCommand({ "execute": "qmp_capabilities" });
        }
    },

    _qemuCommand: function(cmd) {
        this._ensureQemuConnection();
        let cmdStr = JSON.stringify(cmd);
        this._qemuOut.put_string(cmdStr, this._cancellable);
        let [response, len] = this._qemuIn.read_line_utf8(this._cancellable);
    },

    _screenshot: function(isFinal) {
        let filename;
        let modified = true;
        if (isFinal)
            filename = "screenshot-final.ppm";
        else
            filename = "screenshot-" + this._screenshotSerial + ".ppm";

        this._qemuCommand({"execute": "screendump", "arguments": { "filename": filename }});

        let filePath = this._subworkdir.get_child(filename);

        if (!isFinal) {
	          let contentsBytes = GSystem.file_map_readonly(filePath, this._cancellable);
	          let csum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256,
						                                           contentsBytes);

            modified = this._lastScreenshotChecksum != csum;
            if (!modified) {
                GSystem.file_unlink(filePath, this._cancellable);
            } else {
                this._lastScreenshotChecksum = csum;
            }
            this._screenshotSerial++;
        }

        // Convert to PNG if possible
        if (modified && imports.gi.GdkPixbuf) {
            let GdkPixbuf = imports.gi.GdkPixbuf;
            let pixbuf = null;
            try {
                pixbuf = GdkPixbuf.Pixbuf.new_from_file(filePath.get_path());

            } catch (e) {
                if (e.domain != GdkPixbuf.PixbufError)
                    throw e;
                print(e);
            }
            if (pixbuf != null) {
                let outFilename = this._subworkdir.get_child(filename.replace(/ppm$/, 'png'));
                pixbuf.savev(outFilename.get_path(), "png", [], []);
            }
            GSystem.file_unlink(filePath, this._cancellable);
        }
    },

    _idleScreenshot: function() {
        this._screenshot(false);
        return true;
    },

    _onFinalWait: function() {
        print("Final wait complete");

        this._screenshot(true);

        this._loop.quit();
    },

    execute: function(subworkdir, buildData, repo, diskPath, cancellable) {
        print("Testing disk " + diskPath.get_path());
        this._buildData = buildData;
        this._repo = repo;
        this._subworkdir = subworkdir;
        this._loop = GLib.MainLoop.new(null, true);
        this._foundAllMessageIds = false;
        this._failed = false;
        this._journalStream = null;
        this._journalDataStream = null;
        this._openedJournal = false;
        this._readingJournal = false;
        this._pendingRequiredMessageIds = {};
        this._failMessageIds = {};
        this._countPendingRequiredMessageIds = 0;
        this._screenshotSerial = 0;
        this._lastScreenshotChecksum = null;
        this._qemuSocket = null;
        print("Will wait for message IDs: " + JSON.stringify(this._testRequiredMessageIds));
        for (let i = 0; i < this._testRequiredMessageIds.length; i++) {
            this._pendingRequiredMessageIds[this._testRequiredMessageIds[i]] = true;
            this._countPendingRequiredMessageIds += 1;
        }
        for (let i = 0; i < this._testFailedMessageIds.length; i++) {
            this._failMessageIds[this._testFailedMessageIds[i]] = true;
        }
        this._cancellable = cancellable;

        let osname = this._buildData['snapshot']['osname'];

        // HACK
        if (diskPath.get_basename().indexOf('x86_64') >= 0)
            this._diskArch = 'x86_64';
        else
            this._diskArch = 'i686';

        let qemuArgs = LibQA.getDefaultQemuOptions({ parallel: true });

        let diskClone = subworkdir.get_child('testoverlay-' + diskPath.get_basename());
        GSystem.shutil_rm_rf(diskClone, cancellable);

        LibQA.createDiskSnapshot(diskPath, diskClone, cancellable);
        let [gfmnt, mntdir] = LibQA.newReadWriteMount(diskClone, cancellable);
        try {
            LibQA.modifyBootloaderAppendKernelArgs(mntdir, ["console=ttyS0"], cancellable);

            let [currentDir, currentEtcDir] = LibQA.getDeployDirs(mntdir, osname);

            LibQA.injectExportJournal(currentDir, currentEtcDir, cancellable);
            LibQA.injectTestUserCreation(currentDir, currentEtcDir, 'testuser', {}, cancellable);
            LibQA.enableAutologin(currentDir, currentEtcDir, 'testuser', cancellable);

            this._parentTask._prepareDisk(mntdir, this._diskArch, cancellable);
        } finally {
            gfmnt.umount(cancellable);
        }

        let consoleOutput = subworkdir.get_child('console.out');
        let journalOutput = subworkdir.get_child('journal-json.txt');

        qemuArgs.push.apply(qemuArgs, ['-drive', 'file=' + diskClone.get_path() + ',if=virtio',
                                       '-vnc', 'none',
                                       '-serial', 'file:' + consoleOutput.get_path(),
                                       '-chardev', 'socket,id=charmonitor,path=qemu.monitor,server,nowait',
                                       '-mon', 'chardev=charmonitor,id=monitor,mode=control',
                                       '-device', 'virtio-serial',
                                       '-chardev', 'file,id=journaljson,path=' + journalOutput.get_path(),
                                       '-device', 'virtserialport,chardev=journaljson,name=org.gnome.journaljson']);

        let qemuContext = new GSystem.SubprocessContext({ argv: qemuArgs });
        qemuContext.set_cwd(subworkdir.get_path());
        let qemu = new GSystem.Subprocess({context: qemuContext});
        this._qemu = qemu;
        print("starting qemu : " + qemuArgs.join(' '));
        qemu.init(cancellable);

        qemu.wait(cancellable, Lang.bind(this, this._onQemuExited));

        let journalMonitor = journalOutput.monitor_file(0, cancellable);
        journalMonitor.connect('changed', Lang.bind(this, this._onJournalChanged));

        let timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, TIMEOUT_SECONDS,
                                                 Lang.bind(this, this._onTimeout));

        // Let's only do a screenshot every 3 seconds, I think it's slowing things down...
        let screenshotTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3,
                                                 Lang.bind(this, this._idleScreenshot));

        this._loop.run();

        if (this._qemu)
            this._qemu.force_exit();

        GLib.source_remove(timeoutId);

        if (this._failed) {
            throw new Error(this._failedMessage);
        }

        this._parentTask._postQemu(cancellable);

        print("Completed testing of " + diskPath.get_basename());
    }
});

const TestBase = new Lang.Class({
    Name: 'TestBase',
    Extends: Task.Task,

    TaskDef: {
        TaskName: "testbase",
        TaskAfter: ['builddisks'],
    },

    BaseRequiredMessageIDs: ["39f53479d3a045ac8e11786248231fbf", // graphical.target
                             "f77379a8490b408bbe5f6940505a777b",  // systemd-journald
                            ],

    BaseFailedMessageIDs: [],

    RequiredMessageIDs: [],
    FailedMessageIDs: [],

    StatusMessageID: [],

    CompletedTag: null,

    _prepareDisk: function(mntdir, cancellable) {
        // Nothing, intended for subclasses
    },

    _handleMessage: function(message, cancellable) {
        return false;
    },

    _postQemu: function(cancellable) {
    },

    execute: function(cancellable) {
	      let imageDir = this.workdir.get_child('images');
	      let currentImages = imageDir.get_child('current');

        let e = currentImages.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
                                                 cancellable);
        let info;
        let buildJson;
        let disksToTest = [];

        while ((info = e.next_file(cancellable)) != null) {
            let name = info.get_name();
            if (name.indexOf('build-') == 0 && JSUtil.stringEndswith(name, '.json')) {
                buildJson = e.get_child(info);
                continue;
            }
            if (!JSUtil.stringEndswith(name, '.qcow2'))
                continue;
            disksToTest.push(name);
        }
        e.close(null);
        this._buildData = null;
        if (buildJson != null)
            this._buildData = JSONUtil.loadJson(buildJson, cancellable);
        for (let i = 0; i < disksToTest.length; i++) {
            let name = disksToTest[i];
            let workdirName = 'work-' + name.replace(/\.qcow2$/, '');
            let subworkdir = Gio.File.new_for_path(workdirName);
            GSystem.file_ensure_directory(subworkdir, true, cancellable);
            let test = new TestOneDisk(this,
                                       this.BaseRequiredMessageIDs.concat(this.RequiredMessageIDs),
                                       this.BaseFailedMessageIDs.concat(this.FailedMessageIDs),
                                       this.StatusMessageID);
            test.execute(subworkdir, this._buildData, this.repo, currentImages.get_child(name), cancellable);
        }

        let buildData = this._buildData;
        if (buildJson != null && this.CompletedTag !== null) {
            let snapshot = buildData['snapshot'];
            this.ostreeRepo.prepare_transaction(cancellable);
            for (let targetName in buildData['targets']) {
                let targetRev = buildData['targets'][targetName];
                let lastSlash = targetName.lastIndexOf('/');
                let testedRefName = snapshot['osname'] + '/' + this.CompletedTag + targetName.substr(lastSlash);
                this.ostreeRepo.transaction_set_ref(null, testedRefName, targetRev);
                print(Format.vprintf("Wrote ref: %s => %s", [testedRefName, targetRev]));
            }
            this.ostreeRepo.commit_transaction(cancellable);
        } else {
            print("No build json found, not tagging");
        }
    }
});
<|endoftext|>


<commit_before>import { send as sendMessage } from './messaging';
import { $ } from './util';

let nameFormat;

sendMessage({ action: 'get', key: 'nm_disp' }, (response) => {
  nameFormat = response.val;
});

function removeElMatchingSel(sel, node) {
  if (!$(sel, node) || !sel) {
    return;
  }

  $(sel, node)[0].remove();
}

export function rewriteElMatchingSel(sel, node, html) {
  if (!$(sel, node) || !sel) {
    return;
  }

  $(sel, node)[0].innerHTML = html;
}

export function format({ node, user, fSel, uSel }) {
  switch (nameFormat) {
    default:
      break;

    case 'fullname':
      removeElMatchingSel(uSel, node);
      rewriteElMatchingSel(fSel, node, user.name);
      break;

    case 'username':
      removeElMatchingSel(uSel, node);
      rewriteElMatchingSel(fSel, node, user.screenName);
      break;

    case 'inverted':
      rewriteElMatchingSel(fSel, node, user.screenName);
      rewriteElMatchingSel(uSel, node, user.name);
      break;
  }
}

export function formatGroupDM({ node, participants, fSel }) {
  switch (nameFormat) {
    default:
      break;

    case 'inverted':
    case 'username':
      ($(fSel, node) || []).forEach((el, i) => {
        el.innerHTML = participants[i].screenName;
      });
      break;
  }
}
<commit_msg>:wrench: use the emojifiedName to substitute names<commit_after>import { send as sendMessage } from './messaging';
import { $ } from './util';

let nameFormat;

sendMessage({ action: 'get', key: 'nm_disp' }, (response) => {
  nameFormat = response.val;
});

function removeElMatchingSel(sel, node) {
  if (!$(sel, node) || !sel) {
    return;
  }

  $(sel, node)[0].remove();
}

export function rewriteElMatchingSel(sel, node, html) {
  if (!$(sel, node) || !sel) {
    return;
  }

  $(sel, node)[0].innerHTML = html;
}

export function format({ node, user, fSel, uSel }) {
  switch (nameFormat) {
    default:
      break;

    case 'fullname':
      removeElMatchingSel(uSel, node);
      rewriteElMatchingSel(fSel, node, user.emojifiedName || user.name);
      break;

    case 'username':
      removeElMatchingSel(uSel, node);
      rewriteElMatchingSel(fSel, node, user.screenName);
      break;

    case 'inverted':
      rewriteElMatchingSel(fSel, node, user.screenName);
      rewriteElMatchingSel(uSel, node, user.emojifiedName || user.name);
      break;
  }
}

export function formatGroupDM({ node, participants, fSel }) {
  switch (nameFormat) {
    default:
      break;

    case 'inverted':
    case 'username':
      ($(fSel, node) || []).forEach((el, i) => {
        el.innerHTML = participants[i].screenName;
      });
      break;
  }
}
<|endoftext|>


<commit_before>import geojsonUtil from 'utils/arcgis-to-geojson';
import webmercatorUtils from 'esri/geometry/webMercatorUtils';
import {toQuerystring} from 'utils/params';

const utils = {
  /**
  * Retrieve the object from a given array based on id and value
  * @param {array} - items - Array to search
  * @param {string} - field - Property of unique identifier in object
  * @param {string} - value - value of the unique id
  * @return {Any} - Return whatever object is matched
  */
  getObject: (items, field, value) => {
    let obj;
    items.some((item) => {
      if (item[field] === value) {
        obj = item;
        return true;
      }
    });
    return obj;
  },

    /**
  * Retrieve the object from a given array based on id and value
  * Should be used for webmap layers
  * @param {array} - items - Array to search
  * @param {string} - field - Property of unique identifier in object
  * @param {string} - field2 - Property of unique identifier in object
  * @param {string} - value - value of the unique id
  * @return {Any} - Return whatever object is matched
  */
  getWebMapObject: (items, field, field2, value) => {
    let obj;
    items.some((item) => {
      if (item[field][field2] === value) {
        obj = item;
        return true;
      }
    });
    return obj.layer;
  },

  /**
  * Retrieve the object from a given array based on id and value
  * @param {array} - items - Array to search
  * @param {string} - field - Property of unique identifier in object
  * @param {string} - value - value of the unique id
  * @return {boolean} - Return true if object is matched
  */
  containsObject: (items, field, value) => {
    return items.some(item => item[field] === value);
  },

  /**
  * Checks to make sure lat and lng are within global bounds
  * @param {number} lat - Latitude
  * @param {number} lon - Longitude
  * @return {boolean}
  */
  validLatLng: (lat, lon) => {
    return (lat > -90 && lat < 90) && (lon > -180 && lon < 180);
  },

  /**
  * Round down to the nearest 100s
  */
  roundToHundred: (value) => (Math.floor(value / 100) * 100),

  /**
  * Return true if the document.execCommand exists
  * @return {boolean}
  */
  supportsExecCommand: () => typeof document !== 'undefined' && !!document.execCommand,

  /**
  * Return true if we can succesfully copy item to clipboard, this will query element
  * and try to put the focus on it so we can copy it correctly
  * @param {HTML element} el - Input element
  * @return {boolean}
  */
  copySelectionFrom: el => {
    let status = false;
    if (!utils.supportsExecCommand()) {
      return status;
    }
    // Highlight the input
    el.select();
    // This may not work in all scenarios, wrap in a try catch to prevent any errors
    // and handle accordingly
    try {
      status = document.execCommand('copy');
    } catch (err) {
      console.error(err);
    }
    return status;
  },

  clone: (sourceObject) => {
    return JSON.parse(JSON.stringify(sourceObject));
  },

  registerGeom: (feature, success, failure, options) => {
    const geographic = webmercatorUtils.webMercatorToGeographic(feature.geometry);
    const geojson = geojsonUtil.arcgisToGeoJSON(geographic);
    const geoStore = {
      'geojson': {
        'type': 'FeatureCollection',
        'features': [{
          'type': 'Feature',
          'properties': {},
          'geometry': geojson
        }]
      }
    };
    const content = JSON.stringify(geoStore);

    const http = new XMLHttpRequest();
    const url = 'https://production-api.globalforestwatch.org/v1/geostore';
    const params = content;
    http.open('POST', url, true);

    http.setRequestHeader('Content-type', 'application/json');

    http.onreadystatechange = () => {
      if(http.readyState === 4 && http.status === 200) {
        success(JSON.parse(http.responseText), options);
      } else if (http.readyState === 4) {
        failure(http);
      }
    };
    http.send(params);
  },

  geometrySuccess: (response, options) => {
    const {
      settings,
      lang,
      canopyDensity,
      appid,
      layerId,
      OBJECTID,
      OBJECTID_Field,
      activeSlopeClass,
      activeLayers,
      activeFilters,
      activeVersions,
      dynamicLayers,
      tcLossFrom,
      tcLossTo,
      gladFrom,
      gladTo,
      terraIFrom,
      terraITo,
      viirsStartDate,
      viirsEndDate,
      modisStartDate,
      modisEndDate,
      selectedFeatureTitles
    } = options;
    const labels = settings.labels[lang];
    const query = {
      title: labels.title,
      subtitle: labels.subtitle,
      logoUrl: settings.logoUrl,
      logoLinkUrl: settings.logoLinkUrl,
      activeSlopeClass: activeSlopeClass,
      webmap: settings.webmap,
      idvalue: response.data.id,
      tcd: canopyDensity,
      lang: lang,
      activeLayers: activeLayers,
      tcLossFrom: tcLossFrom,
      tcLossTo: tcLossTo,
      gladFrom: gladFrom,
      gladTo: gladTo,
      terraIFrom: terraIFrom,
      terraITo: terraITo,
      viirsStartDate: viirsStartDate,
      viirsEndDate: viirsEndDate,
      modisStartDate: modisStartDate,
      modisEndDate: modisEndDate,
      customFeatureTitle: options.selectedFeature.attributes.title || 'Feature Analysis',
      selectedFeatureTitles: selectedFeatureTitles || options.selectedFeature.attributes.title || 'Feature Analysis',
      sharinghost: settings.sharinghost,
      activeFilters: activeFilters,
      activeVersions: activeVersions,
      ...(layerId ? {layerId} : {}),
      ...(OBJECTID ? {OBJECTID} : {}),
      ...(OBJECTID_Field ? {OBJECTID_Field} : {})
    };

    // We need the dynamic layers but we cannot encode nested objects, so we will pass them in to query like this:
    // layerId: array of visible layers
    for (const key in dynamicLayers) {
      query[key] = dynamicLayers[key];
    }

    if (appid) {
      query.appid = appid;
    }

    if (window._app.cache) {
      query.cache = window._app.cache;
    }

    query.origin = window.location.origin;

    const path = toQuerystring(query);

    // if (window._app.base === window._app.cache) {
    //   console.log('window._app.base === window._app.cache', window._app.base, window._app.cache)
    //   window.open(`report.html?${path}`);
    // }
    let appBase = window.location.origin + window.location.pathname;

    if (appBase.slice(-1) !== '/') {
      appBase += '/';
    }
    //We are no longer using localStorage as it won't persist across domains!
    window.addEventListener('message', function(e) {
      // We need the report's origin; AKA appBase minus a couple things
      if (appBase.indexOf(e.origin) > -1 && e.data === 'send-info') {
        e.source.postMessage({command: 'info', info: settings.analysisModules}, e.origin);
      }
    }, false);

    window.open(`${appBase}report.html?${path}`);
  },

  geometryFailure: (err) => {
    console.log(err);
  },

  /**
  * @param {object} options
  * @property {object} options.selectedFeature - Selected feature should come from infoWindow.getSelectedFeature()
  * @property {string} options.webmap - webmap id
  * @property {string} options.appid - app id
  * @property {string} options.lang - two digit iso code representing current language, en || es || fr || pt
  * @property {number} options.canopyDensity - Current tree cover density settings
  * @property {number} options.activeSlopeClass - Current Slope setting, only relevant for slope analysis
  */
  generateReport: (options) => {
    const { selectedFeature } = options;
    if (selectedFeature.attributes.geostoreId) {
      utils.geometrySuccess({
        data: {
          id: selectedFeature.attributes.geostoreId
        }
      }, options);
    } else {
      utils.registerGeom(selectedFeature, utils.geometrySuccess, utils.geometryFailure, options);
    }
  },

  /**
  * Universal pad function, takes pad(12, '*', 5) and return '***12'
  */
  pad: (content, padding, length) => {
    let item = content.toString(), i = 0;
    while (i < length) {
      item = padding + item;
      i++;
    }
    return item.slice(item.length - length);
  },

  getJulianDate: (timestamp) => {
    const day = 1000 * 60 * 60 * 24;
    const newDate = new Date(timestamp);
    const year = new Date(newDate.getFullYear(), 0, 0);
    const currentDay = Math.ceil((newDate - year) / day);
    //- Year should be 15000 or 16000
    const julianYear = (newDate.getFullYear() - 2000) * 1000;
    return julianYear + currentDay;
  },

  /**
  * Used for calculating the date of a grid code in the Terra-I Layer
  */
  getDateFromGridCode: (gridCode) => {
    const year = Math.floor((gridCode - 1) / 23) + 2004;
    const day = (((gridCode - 1) % 23) * 16) + 1;
    return { year, day };
  },

  /**
  * Inclusive Array generator
  */
  range: (start, end) => {
    const result = [];
    let min = start;
    while (min <= end) {
      result.push(min);
      ++min;
    }
    return result;
  },

  formatNumber: (number) => {

    const numberString = Math.round(number).toString();

    if (number >= 1000) {
      const stringArray = [];
      for (let i = 0; i < numberString.length; i++) {
        stringArray.push(numberString[i]);
      }
      stringArray.reverse();
      const stringWithExtras = [];

      for (let i = 0; i < stringArray.length; i++) {
        if (i !== 0 && i % 3 === 0) {
          stringWithExtras.push(',');
        }
        stringWithExtras.push(stringArray[i]);
      }
      stringWithExtras.reverse();

      return stringWithExtras.join('');
    } else {
      return numberString;
    }
  }

};

export { utils as default };
<commit_msg>Switching to alternative webmap based on current lang<commit_after>import geojsonUtil from 'utils/arcgis-to-geojson';
import webmercatorUtils from 'esri/geometry/webMercatorUtils';
import {toQuerystring} from 'utils/params';

const utils = {
  /**
  * Retrieve the object from a given array based on id and value
  * @param {array} - items - Array to search
  * @param {string} - field - Property of unique identifier in object
  * @param {string} - value - value of the unique id
  * @return {Any} - Return whatever object is matched
  */
  getObject: (items, field, value) => {
    let obj;
    items.some((item) => {
      if (item[field] === value) {
        obj = item;
        return true;
      }
    });
    return obj;
  },

    /**
  * Retrieve the object from a given array based on id and value
  * Should be used for webmap layers
  * @param {array} - items - Array to search
  * @param {string} - field - Property of unique identifier in object
  * @param {string} - field2 - Property of unique identifier in object
  * @param {string} - value - value of the unique id
  * @return {Any} - Return whatever object is matched
  */
  getWebMapObject: (items, field, field2, value) => {
    let obj;
    items.some((item) => {
      if (item[field][field2] === value) {
        obj = item;
        return true;
      }
    });
    return obj.layer;
  },

  /**
  * Retrieve the object from a given array based on id and value
  * @param {array} - items - Array to search
  * @param {string} - field - Property of unique identifier in object
  * @param {string} - value - value of the unique id
  * @return {boolean} - Return true if object is matched
  */
  containsObject: (items, field, value) => {
    return items.some(item => item[field] === value);
  },

  /**
  * Checks to make sure lat and lng are within global bounds
  * @param {number} lat - Latitude
  * @param {number} lon - Longitude
  * @return {boolean}
  */
  validLatLng: (lat, lon) => {
    return (lat > -90 && lat < 90) && (lon > -180 && lon < 180);
  },

  /**
  * Round down to the nearest 100s
  */
  roundToHundred: (value) => (Math.floor(value / 100) * 100),

  /**
  * Return true if the document.execCommand exists
  * @return {boolean}
  */
  supportsExecCommand: () => typeof document !== 'undefined' && !!document.execCommand,

  /**
  * Return true if we can succesfully copy item to clipboard, this will query element
  * and try to put the focus on it so we can copy it correctly
  * @param {HTML element} el - Input element
  * @return {boolean}
  */
  copySelectionFrom: el => {
    let status = false;
    if (!utils.supportsExecCommand()) {
      return status;
    }
    // Highlight the input
    el.select();
    // This may not work in all scenarios, wrap in a try catch to prevent any errors
    // and handle accordingly
    try {
      status = document.execCommand('copy');
    } catch (err) {
      console.error(err);
    }
    return status;
  },

  clone: (sourceObject) => {
    return JSON.parse(JSON.stringify(sourceObject));
  },

  registerGeom: (feature, success, failure, options) => {
    const geographic = webmercatorUtils.webMercatorToGeographic(feature.geometry);
    const geojson = geojsonUtil.arcgisToGeoJSON(geographic);
    const geoStore = {
      'geojson': {
        'type': 'FeatureCollection',
        'features': [{
          'type': 'Feature',
          'properties': {},
          'geometry': geojson
        }]
      }
    };
    const content = JSON.stringify(geoStore);

    const http = new XMLHttpRequest();
    const url = 'https://production-api.globalforestwatch.org/v1/geostore';
    const params = content;
    http.open('POST', url, true);

    http.setRequestHeader('Content-type', 'application/json');

    http.onreadystatechange = () => {
      if(http.readyState === 4 && http.status === 200) {
        success(JSON.parse(http.responseText), options);
      } else if (http.readyState === 4) {
        failure(http);
      }
    };
    http.send(params);
  },

  geometrySuccess: (response, options) => {
    const {
      settings,
      lang,
      canopyDensity,
      appid,
      layerId,
      OBJECTID,
      OBJECTID_Field,
      activeSlopeClass,
      activeLayers,
      activeFilters,
      activeVersions,
      dynamicLayers,
      tcLossFrom,
      tcLossTo,
      gladFrom,
      gladTo,
      terraIFrom,
      terraITo,
      viirsStartDate,
      viirsEndDate,
      modisStartDate,
      modisEndDate,
      selectedFeatureTitles
    } = options;
    const labels = settings.labels[lang];
    const query = {
      title: labels.title,
      subtitle: labels.subtitle,
      logoUrl: settings.logoUrl,
      logoLinkUrl: settings.logoLinkUrl,
      activeSlopeClass: activeSlopeClass,
      webmap: settings.webmap,
      idvalue: response.data.id,
      tcd: canopyDensity,
      lang: lang,
      activeLayers: activeLayers,
      tcLossFrom: tcLossFrom,
      tcLossTo: tcLossTo,
      gladFrom: gladFrom,
      gladTo: gladTo,
      terraIFrom: terraIFrom,
      terraITo: terraITo,
      viirsStartDate: viirsStartDate,
      viirsEndDate: viirsEndDate,
      modisStartDate: modisStartDate,
      modisEndDate: modisEndDate,
      customFeatureTitle: options.selectedFeature.attributes.title || 'Feature Analysis',
      selectedFeatureTitles: selectedFeatureTitles || options.selectedFeature.attributes.title || 'Feature Analysis',
      sharinghost: settings.sharinghost,
      activeFilters: activeFilters,
      activeVersions: activeVersions,
      ...(layerId ? {layerId} : {}),
      ...(OBJECTID ? {OBJECTID} : {}),
      ...(OBJECTID_Field ? {OBJECTID_Field} : {})
    };

    if (settings.language !== lang) {
      query.webmap = settings.alternativeWebmap;
    }

    // We need the dynamic layers but we cannot encode nested objects, so we will pass them in to query like this:
    // layerId: array of visible layers
    for (const key in dynamicLayers) {
      query[key] = dynamicLayers[key];
    }

    if (appid) {
      query.appid = appid;
    }

    if (window._app.cache) {
      query.cache = window._app.cache;
    }

    query.origin = window.location.origin;

    const path = toQuerystring(query);

    // if (window._app.base === window._app.cache) {
    //   console.log('window._app.base === window._app.cache', window._app.base, window._app.cache)
    //   window.open(`report.html?${path}`);
    // }
    let appBase = window.location.origin + window.location.pathname;

    if (appBase.slice(-1) !== '/') {
      appBase += '/';
    }
    //We are no longer using localStorage as it won't persist across domains!
    window.addEventListener('message', function(e) {
      // We need the report's origin; AKA appBase minus a couple things
      if (appBase.indexOf(e.origin) > -1 && e.data === 'send-info') {
        e.source.postMessage({command: 'info', info: settings.analysisModules}, e.origin);
      }
    }, false);

    window.open(`${appBase}report.html?${path}`);
  },

  geometryFailure: (err) => {
    console.log(err);
  },

  /**
  * @param {object} options
  * @property {object} options.selectedFeature - Selected feature should come from infoWindow.getSelectedFeature()
  * @property {string} options.webmap - webmap id
  * @property {string} options.appid - app id
  * @property {string} options.lang - two digit iso code representing current language, en || es || fr || pt
  * @property {number} options.canopyDensity - Current tree cover density settings
  * @property {number} options.activeSlopeClass - Current Slope setting, only relevant for slope analysis
  */
  generateReport: (options) => {
    const { selectedFeature } = options;
    if (selectedFeature.attributes.geostoreId) {
      utils.geometrySuccess({
        data: {
          id: selectedFeature.attributes.geostoreId
        }
      }, options);
    } else {
      utils.registerGeom(selectedFeature, utils.geometrySuccess, utils.geometryFailure, options);
    }
  },

  /**
  * Universal pad function, takes pad(12, '*', 5) and return '***12'
  */
  pad: (content, padding, length) => {
    let item = content.toString(), i = 0;
    while (i < length) {
      item = padding + item;
      i++;
    }
    return item.slice(item.length - length);
  },

  getJulianDate: (timestamp) => {
    const day = 1000 * 60 * 60 * 24;
    const newDate = new Date(timestamp);
    const year = new Date(newDate.getFullYear(), 0, 0);
    const currentDay = Math.ceil((newDate - year) / day);
    //- Year should be 15000 or 16000
    const julianYear = (newDate.getFullYear() - 2000) * 1000;
    return julianYear + currentDay;
  },

  /**
  * Used for calculating the date of a grid code in the Terra-I Layer
  */
  getDateFromGridCode: (gridCode) => {
    const year = Math.floor((gridCode - 1) / 23) + 2004;
    const day = (((gridCode - 1) % 23) * 16) + 1;
    return { year, day };
  },

  /**
  * Inclusive Array generator
  */
  range: (start, end) => {
    const result = [];
    let min = start;
    while (min <= end) {
      result.push(min);
      ++min;
    }
    return result;
  },

  formatNumber: (number) => {

    const numberString = Math.round(number).toString();

    if (number >= 1000) {
      const stringArray = [];
      for (let i = 0; i < numberString.length; i++) {
        stringArray.push(numberString[i]);
      }
      stringArray.reverse();
      const stringWithExtras = [];

      for (let i = 0; i < stringArray.length; i++) {
        if (i !== 0 && i % 3 === 0) {
          stringWithExtras.push(',');
        }
        stringWithExtras.push(stringArray[i]);
      }
      stringWithExtras.reverse();

      return stringWithExtras.join('');
    } else {
      return numberString;
    }
  }

};

export { utils as default };
<|endoftext|>


<commit_before>/*
 * Namespace: lb.base.i18n.data
 * Base data storage of language properties for Internationalization (i18n)
 *
 * This module provides the basis for the storage and retrieval of language
 * properties for the adaptation of a web application to different languages
 * in an international context.
 *
 * This module provides a generic storage for language properties associated
 * with language codes. Any type of language properties may be defined by an
 * an application:
 * - string properties,
 * - functions for localized behavior, formatting and sorting,
 * - object properties, to group properties within. The groups may be nested.
 *
 * A language code is a string which identifies the language, region and other
 * variations of the language as defined in RFC5646 "Tags for Identifying
 * Languages", for example:
 * - 'en' for English,
 * - 'fr' for French,
 * - 'en-GB' for English/Great Britain,
 * - 'en-US' for English/USA,
 * - 'fr-FR' for French/France,
 * - 'fr-CA' for French/Canada.
 *
 * The lookup of language properties is done from the most specific language
 * to the least specific language, with longer language codes considered more
 * specific than shorter language codes. Only language codes which are
 * substrings of the language selected for the lookup are considered.
 * For example, if the selected language is 'en-GB', 'en-GB' and 'en' are
 * considered in this order, while 'en-US', 'fr-FR', 'fr-CA' and 'fr' are left
 * out. Language codes are compared in a case-insensitive way.
 *
 * The empty string '' is the least specific language code possible, which will
 * always be considered last in the lookup process. Common default properties
 * can be associated with the empty language code '', they will be shared by
 * all languages.
 *
 * Any custom property may be defined in language properties for the needs of
 * your application, and associated with a language code by calling
 * addLanguageProperties(). Calling reset() removes all language properties.
 *
 * The list of language codes associated with language properties is returned
 * by getLanguageCodes(). It is initially empty.
 *
 * Author:
 * Eric Bréchemier <legalbox@eric.brechemier.name>
 *
 * Copyright:
 * Legal Box SAS (c) 2010, All Rights Reserved
 *
 * License:
 * BSD License
 * http://creativecommons.org/licenses/BSD/
 *
 * Version:
 * 2010-12-29
 */
/*requires lb.base.i18n.js */
/*jslint white:false, plusplus:false */
/*global lb */
// preserve the module, if already loaded
lb.base.i18n.data = lb.base.i18n.data || (function() {
  // Builder of
  // Closure for lb.base.i18n.data module

  // Declare aliases
  var i18n = lb.base.i18n,
      equals = i18n.equals,
      languageCompare = i18n.languageCompare,
      contains = i18n.contains,

  // private fields

      // languages - array, the list of language objects, sorted by language
      //             code, in case-insensitive lexical order.
      //             Each language object is in the format:
      //             | {
      //             |    code: 'en-US', // string, language code
      //             |    properties: {...}  // object, properties given
      //             |                       // in addLanguageProperties
      //             | }
      // Note:
      // In current implementation, the same language code may be repeated in
      // several language objects. These duplicates may be merged into a single
      // language object in a future implementation (trading less memory for
      // more computations due to added merging step).
      languages = [];

  function getLanguageCodes(){
    // Function: getLanguageCodes(): array
    // Get the list of language codes associated with language properties.
    //
    // Returns:
    //   array of strings, the list of unique language codes with associated
    //   language properties, sorted in case-insensitive lexical order.
    //
    // Notes:
    // Language codes are returned AS IS, but in case the same language code
    // has been registered several times, comparing in a case-insensitive
    // manner, duplicates are not included in the list. Language codes are not
    // currently normalized to a lower case form in the resulting list; this
    // may be done in a future implementation.

    var i,
        length,
        languageCode,
        previousLanguageCode = null,
        languageCodes = [];
    for (i=0, length=languages.length; i<length; i++){
      languageCode = languages[i].code;
      if ( !equals(languageCode,previousLanguageCode) ){
        languageCodes.push(languageCode);
      }
      previousLanguageCode = languageCode;
    }
    return languageCodes;
  }

  function addLanguageProperties(languageCode,languageProperties){
    // Function: addLanguageProperties(languageCode,languageProperties)
    // Add or replace language properties associated with given language code.
    //
    // Language properties may be specified in multiple calls with the same
    // language code. In case of duplicate properties, the properties defined
    // last are considered more specific and take precedence over properties
    // defined previously.
    //
    // Parameters:
    //   languageCode - string, the language code identifying the language,
    //                  as defined in RFC5646 "Tags for Identifying Languages"
    //   languageProperties - object, a set of language properties
    //
    // Note:
    //   Nothing happens in case the given language code is not a string.
    if ( typeof languageCode!=='string' ){
      return;
    }

    // Note: array.sort does not guarantee that the order of items with the
    // same value is preserved. This is the case in recent versions of Firefox,
    // Opera and Chrome, but not in IE and Safari.
    //
    // Thus I chose to insert the new item at the highest position where
    // the lexical order of previous language is lesser or equal, instead of
    // adding the item to the array and calling sort().

    var insertionPosition = 0,
        length = languages.length,
        j;

    // find the first suitable position for insertion
    for (j=length-1; j>=0; j--){
      if ( languageCompare(languageCode,languages[j].code)>=0 ){
        insertionPosition = j+1; // insert just after
        break;
      }
    }

    // insert new language at found location (possibly 0)
    languages.splice(insertionPosition,0,{
      code: languageCode,
      properties: languageProperties
    });
  }

  function getProperty(languageCode,path){
    // Function: getProperty(languageCode,path): any
    // Get the most specific property for given language code at given path.
    //
    // The path argument may be a string
    // or an array of strings:
    // - the name of a property defined at top level:
    //   e.g. 'propertyName'
    // - the dotted name of a nested property:
    //   e.g. 'section.subsection.propertyName'
    // - the list of sections and subsections:
    //   e.g. ['section','subsection','propertyName']
    //
    // The last two forms are equivalent, both matching a property
    // 'propertyName' nested in a property 'subsection' within a property
    // 'section' at top level of language properties. The array notation allows
    // to look up a property which would contain a dot in its name, without the
    // substitution to a section and subsection: ['no.substitution.done'].
    //
    // Parameters:
    //   name - string, the name of the looked up property, which may be a
    //          dotted string or an array of strings to represent a property
    //          nested within sections and subsections
    //   languageCode - string, the language code to filter relevant languages
    //
    // Returns:
    //   * any, the value of the property found in the most specific language
    //     object whose language code put in lower case is a substring of the
    //     given language code put in lower case
    //   * or null if the property is not found in suitable languages,
    //     if the given path is null or undefined, or if the given language
    //     code is not a string.
    if (path===null || path===undefined || typeof languageCode!=='string'){
      return null;
    }
    if (typeof path === 'string'){
      path = path.split('.');
    }

    var language,
        i,
        properties,
        pathElement,
        j,
        length;

    // for each language, from most specific (last) to least specific (first)
    for (i=languages.length-1; i>=0; i--){
      language = languages[i];
      // does selected language inherit properties from this language ?
      if ( contains(languageCode,language.code) ){
        // start at top of language properties
        properties = language.properties;
        // for each path element in the given property path
        for (j=0, length=path.length; j<length && properties; j++){
          pathElement = path[j];
          // if the final path element is found
          if (pathElement in properties && j===length-1){
            return properties[pathElement];
          }
          // go on with next level (may be undefined)
          properties = properties[pathElement];
        }
      }
    }
    return null;
  }

  function reset(){
    // Function: reset()
    // Remove all language properties.

    languages.length = 0;
  }

  return { // public API
    getLanguageCodes: getLanguageCodes,
    addLanguageProperties: addLanguageProperties,
    getProperty: getProperty,
    reset: reset
  };
}());
<commit_msg>Clarification in documentation of getProperty (base i18n data API)<commit_after>/*
 * Namespace: lb.base.i18n.data
 * Base data storage of language properties for Internationalization (i18n)
 *
 * This module provides the basis for the storage and retrieval of language
 * properties for the adaptation of a web application to different languages
 * in an international context.
 *
 * This module provides a generic storage for language properties associated
 * with language codes. Any type of language properties may be defined by an
 * an application:
 * - string properties,
 * - functions for localized behavior, formatting and sorting,
 * - object properties, to group properties within. The groups may be nested.
 *
 * A language code is a string which identifies the language, region and other
 * variations of the language as defined in RFC5646 "Tags for Identifying
 * Languages", for example:
 * - 'en' for English,
 * - 'fr' for French,
 * - 'en-GB' for English/Great Britain,
 * - 'en-US' for English/USA,
 * - 'fr-FR' for French/France,
 * - 'fr-CA' for French/Canada.
 *
 * The lookup of language properties is done from the most specific language
 * to the least specific language, with longer language codes considered more
 * specific than shorter language codes. Only language codes which are
 * substrings of the language selected for the lookup are considered.
 * For example, if the selected language is 'en-GB', 'en-GB' and 'en' are
 * considered in this order, while 'en-US', 'fr-FR', 'fr-CA' and 'fr' are left
 * out. Language codes are compared in a case-insensitive way.
 *
 * The empty string '' is the least specific language code possible, which will
 * always be considered last in the lookup process. Common default properties
 * can be associated with the empty language code '', they will be shared by
 * all languages.
 *
 * Any custom property may be defined in language properties for the needs of
 * your application, and associated with a language code by calling
 * addLanguageProperties(). Calling reset() removes all language properties.
 *
 * The list of language codes associated with language properties is returned
 * by getLanguageCodes(). It is initially empty.
 *
 * Author:
 * Eric Bréchemier <legalbox@eric.brechemier.name>
 *
 * Copyright:
 * Legal Box SAS (c) 2010-2011, All Rights Reserved
 *
 * License:
 * BSD License
 * http://creativecommons.org/licenses/BSD/
 *
 * Version:
 * 2011-01-03
 */
/*requires lb.base.i18n.js */
/*jslint white:false, plusplus:false */
/*global lb */
// preserve the module, if already loaded
lb.base.i18n.data = lb.base.i18n.data || (function() {
  // Builder of
  // Closure for lb.base.i18n.data module

  // Declare aliases
  var i18n = lb.base.i18n,
      equals = i18n.equals,
      languageCompare = i18n.languageCompare,
      contains = i18n.contains,

  // private fields

      // languages - array, the list of language objects, sorted by language
      //             code, in case-insensitive lexical order.
      //             Each language object is in the format:
      //             | {
      //             |    code: 'en-US', // string, language code
      //             |    properties: {...}  // object, properties given
      //             |                       // in addLanguageProperties
      //             | }
      // Note:
      // In current implementation, the same language code may be repeated in
      // several language objects. These duplicates may be merged into a single
      // language object in a future implementation (trading less memory for
      // more computations due to added merging step).
      languages = [];

  function getLanguageCodes(){
    // Function: getLanguageCodes(): array
    // Get the list of language codes associated with language properties.
    //
    // Returns:
    //   array of strings, the list of unique language codes with associated
    //   language properties, sorted in case-insensitive lexical order.
    //
    // Notes:
    // Language codes are returned AS IS, but in case the same language code
    // has been registered several times, comparing in a case-insensitive
    // manner, duplicates are not included in the list. Language codes are not
    // currently normalized to a lower case form in the resulting list; this
    // may be done in a future implementation.

    var i,
        length,
        languageCode,
        previousLanguageCode = null,
        languageCodes = [];
    for (i=0, length=languages.length; i<length; i++){
      languageCode = languages[i].code;
      if ( !equals(languageCode,previousLanguageCode) ){
        languageCodes.push(languageCode);
      }
      previousLanguageCode = languageCode;
    }
    return languageCodes;
  }

  function addLanguageProperties(languageCode,languageProperties){
    // Function: addLanguageProperties(languageCode,languageProperties)
    // Add or replace language properties associated with given language code.
    //
    // Language properties may be specified in multiple calls with the same
    // language code. In case of duplicate properties, the properties defined
    // last are considered more specific and take precedence over properties
    // defined previously.
    //
    // Parameters:
    //   languageCode - string, the language code identifying the language,
    //                  as defined in RFC5646 "Tags for Identifying Languages"
    //   languageProperties - object, a set of language properties
    //
    // Note:
    //   Nothing happens in case the given language code is not a string.
    if ( typeof languageCode!=='string' ){
      return;
    }

    // Note: array.sort does not guarantee that the order of items with the
    // same value is preserved. This is the case in recent versions of Firefox,
    // Opera and Chrome, but not in IE and Safari.
    //
    // Thus I chose to insert the new item at the highest position where
    // the lexical order of previous language is lesser or equal, instead of
    // adding the item to the array and calling sort().

    var insertionPosition = 0,
        length = languages.length,
        j;

    // find the first suitable position for insertion
    for (j=length-1; j>=0; j--){
      if ( languageCompare(languageCode,languages[j].code)>=0 ){
        insertionPosition = j+1; // insert just after
        break;
      }
    }

    // insert new language at found location (possibly 0)
    languages.splice(insertionPosition,0,{
      code: languageCode,
      properties: languageProperties
    });
  }

  function getProperty(languageCode,path){
    // Function: getProperty(languageCode,path): any
    // Get the most specific property for given language code at given path.
    //
    // The path argument may be a string
    // or an array of strings:
    // - the name of a property defined at top level:
    //   e.g. 'propertyName'
    // - the dotted name of a nested property:
    //   e.g. 'section.subsection.propertyName'
    // - the list of sections and subsections:
    //   e.g. ['section','subsection','propertyName']
    //
    // The last two forms are equivalent, both matching a property
    // 'propertyName' nested in a property 'subsection' within a property
    // 'section' at top level of language properties. The array notation allows
    // to look up a property which would contain a dot in its name, without the
    // substitution to a section and subsection: ['no.substitution.done'].
    //
    // Parameters:
    //   name - string, the name of the looked up property, which may be a
    //          dotted string or an array of strings to represent a property
    //          nested within sections and subsections
    //   languageCode - string, the language code to filter relevant languages
    //
    // Returns:
    //   * any, the value of the property found in the most specific language
    //     object whose language code put in lower case is a hyphenated
    //     substring of the given language code put in lower case
    //   * or null if the property is not found in suitable languages,
    //     if the given path is null or undefined, or if the given language
    //     code is not a string.
    if (path===null || path===undefined || typeof languageCode!=='string'){
      return null;
    }
    if (typeof path === 'string'){
      path = path.split('.');
    }

    var language,
        i,
        properties,
        pathElement,
        j,
        length;

    // for each language, from most specific (last) to least specific (first)
    for (i=languages.length-1; i>=0; i--){
      language = languages[i];
      // does selected language inherit properties from this language ?
      if ( contains(languageCode,language.code) ){
        // start at top of language properties
        properties = language.properties;
        // for each path element in the given property path
        for (j=0, length=path.length; j<length && properties; j++){
          pathElement = path[j];
          // if the final path element is found
          if (pathElement in properties && j===length-1){
            return properties[pathElement];
          }
          // go on with next level (may be undefined)
          properties = properties[pathElement];
        }
      }
    }
    return null;
  }

  function reset(){
    // Function: reset()
    // Remove all language properties.

    languages.length = 0;
  }

  return { // public API
    getLanguageCodes: getLanguageCodes,
    addLanguageProperties: addLanguageProperties,
    getProperty: getProperty,
    reset: reset
  };
}());
<|endoftext|>