<commit_before>/*
 Copyright (c) 2016 Dmitry Brant.
 http://dmitrybrant.com

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

function parseFormat(reader)
{
	var results = new ResultNode("ZIP structure");
	try {
		var stream = new DataStream(reader);

        var contentResults = new ResultNode("ZIP contents");
        var fileList = [];
        var mimeType = "";

        try {
            var zipInfo = zipReadContents(stream, contentResults, fileList);
            if (zipInfo.mimeType !== undefined) {
                mimeType = zipInfo.mimeType;
            }
        } catch (e) {
            console.log("Error while reading ZIP contents: " + e);
        }

        var fileExt = "ZIP";
        var fileType = "Standard ZIP file";

        if (fileList.indexOf("word/document.xml") >= 0) {
            fileExt = "DOCX";
            fileType = "Microsoft Word (2010 and above) document";
        } else if (fileList.indexOf("ppt/presentation.xml") >= 0) {
            fileExt = "PPTX";
            fileType = "Microsoft PowerPoint (2010 and above) presentation";
        } else if (fileList.indexOf("xl/workbook.xml") >= 0) {
            fileExt = "XLSX";
            fileType = "Microsoft Excel (2010 and above) spreadsheet";
        } else if (fileList.indexOf("FixedDocSeq.fdseq") >= 0) {
            fileExt = "XPS";
            fileType = "Microsoft XPS document";
        } else if (fileList.indexOf("AndroidManifest.xml") >= 0) {
            fileExt = "APK";
            fileType = "Android application package";
        } else if (fileList.indexOf("snote/snote.xml") >= 0) {
            fileExt = "SNB";
            fileType = "Exported Samsung S-Note file";
        } else if (fileList.indexOf("content/riffData.cdr") >= 0) {
            fileExt = "CDR";
            fileType = "CorelDraw image";
        } else if (fileList.indexOf("Root.xml") >= 0 && fileList.indexOf("summary.xml") >= 0) {
            if (fileList.indexOf("preview.png") >= 0) {
                fileExt = "DPP";
                fileType = "Serif DrawPlus document";
            } else if (fileList.indexOf("preview.jpg") >= 0) {
                fileExt = "PPP";
                fileType = "Serif PagePlus document";
            }
        } else if (fileList.indexOf("doc.kml") >= 0) {
            fileExt = "KMZ";
            fileType = "Google Earth location data";
        } else if (mimeType.indexOf("opendocument.text") >= 0) {
            fileExt = "ODT";
            fileType = "OpenDocument text file";
        } else if (mimeType.indexOf("opendocument.spreadsheet") >= 0) {
            fileExt = "ODS";
            fileType = "OpenDocument spreadsheet";
        } else if (mimeType.indexOf("opendocument.presentation") >= 0) {
            fileExt = "ODP";
            fileType = "OpenDocument presentation";
        } else if (mimeType.indexOf("opendocument.graphics") >= 0) {
            fileExt = "ODG";
            fileType = "OpenDocument graphics file";
        } else if (mimeType.indexOf("application/epub") >= 0) {
            fileExt = "EPUB";
            fileType = "Electronic publication or e-book";
        }

        results.add("File type", fileType);
        results.add("File extension", fileExt);
        results.addResult(contentResults);

    } catch(e) {
		console.log("Error while reading ZIP: " + e);
	}
	return results;
}

function zipReadContents(stream, results, fileList) {
    var zipInfo = {};
    var chunkType;
    var fileName;
    var versionNeeded, flags, compressionMethod;
    var lastModTime, lastModDate, crc32;
    var compressedSize, uncompressedSize, fileNameLength;
    var extraFieldLength;
    var node;

    while (!stream.eof()) {
        //read chunk type...
        if (stream.readAsciiString(2) != "PK") {
            break;
        }
        chunkType = stream.readUShortLe();

        node = results.add("0x" + chunkType.toString(16), zipChunkName(chunkType));

        if (chunkType == 0x0403) {
            //compressed file data!
            versionNeeded = stream.readUShortLe();
            flags = stream.readUShortLe();
            compressionMethod = stream.readUShortLe();
            lastModTime = stream.readUShortLe();
            lastModDate = stream.readUShortLe();

            crc32 = stream.readUIntLe();
            compressedSize = stream.readUIntLe();
            uncompressedSize = stream.readUIntLe();
            fileNameLength = stream.readUShortLe();
            extraFieldLength = stream.readUShortLe();

            //sanity
            if (fileNameLength > 10000) {
                break;
            }

            //read file name...
            fileName = stream.readAsciiString(fileNameLength);

            node.add("Name", fileName);
            node.add("Size (compressed)", compressedSize);
            node.add("Size (uncompressed)", uncompressedSize);

            if (fileList !== null) {
                fileList.push(fileName);
            }

            stream.seek(extraFieldLength, 1);

            if (fileName == "mimetype" && compressedSize == uncompressedSize && compressedSize < 256) {
                // it's trying to tell us something!
                zipInfo.mimeType = stream.readAsciiString(compressedSize);
            } else {
                stream.seek(compressedSize, 1);
            }

            //read data descriptor?
            if ((flags & 0x8) != 0) {
                //int s = SearchForSig(disk, new byte[]{0x50, 0x4B, 0x7, 0x8}, 10000);
                //if (s >= 0) {
                //    fileSize += s;
                //} else {
                //    haveHeader = false;
                //    break;
                //}
            }
        } else if (chunkType == 0x0807) {

            var dataCrc32 = stream.readUIntLe();
            var dataCompSize = stream.readUIntLe();
            var dataUncompSize = stream.readUIntLe();

            node.add("Size (compressed)", dataCompSize);
            node.add("Size (uncompressed)", dataUncompSize);

        } else if (chunkType == 0x0806) {

            //archive extra data record!
            extraFieldLength = stream.readUIntLe();
            stream.seek(extraFieldLength, 1);

            node.add("Size", extraFieldLength);

        } else if (chunkType == 0x0201) {

            //directory file header!
            var versionMadeBy = stream.readUShortLe();
            versionNeeded = stream.readUShortLe();
            flags = stream.readUShortLe();
            compressionMethod = stream.readUShortLe();
            lastModTime = stream.readUShortLe();
            lastModDate = stream.readUShortLe();

            crc32 = stream.readUIntLe();
            compressedSize = stream.readUIntLe();
            uncompressedSize = stream.readUIntLe();
            fileNameLength = stream.readUShortLe();
            extraFieldLength = stream.readUShortLe();
            var fileCommentLength = stream.readUShortLe();
            var diskNumberStart = stream.readUShortLe();
            var internalFileAttr = stream.readUShortLe();
            var externalFileAttr = stream.readUIntLe();
            var relHeaderOffset = stream.readUIntLe();

            fileName = stream.readAsciiString(fileNameLength);

            node.add("Name", fileName);
            node.add("Size (compressed)", compressedSize);
            node.add("Size (uncompressed)", uncompressedSize);

            //read extra field...
            stream.seek(extraFieldLength, 1);

            //read file comment...
            if (fileCommentLength > 0 && fileCommentLength < 4096) {
                var fileComment = stream.readAsciiString(fileCommentLength);
                node.add("Comment", fileComment);
            } else {
                stream.seek(fileCommentLength, 1);
            }

        } else if (chunkType == 0x0505) {

            //digital signature!
            var dataLength = stream.readUShortLe();
            stream.seek(dataLength, 1);

            node.add("Size", dataLength);

        } else if (chunkType == 0x0606) {

            //zip64 end of central directory!
            var sizeOfRecord = stream.readLongLe();
            if (sizeOfRecord > 0) {
                stream.seek(sizeOfRecord, 1);
            }

            node.add("Size", sizeOfRecord);

        } else if (chunkType == 0x0706) {

            //zip64 end of central dir locator!
            var numberofDisk = stream.readUIntLe();
            var offsetToCentralDirRec = stream.readLongLe();
            var numTotalDisks = stream.readUIntLe();

        } else if (chunkType == 0x0605) {

            //directory file header!
            var diskNumber = stream.readUShortLe();
            var numDiskWithCentralDir = stream.readUShortLe();
            var localCentralDirEntries = stream.readUShortLe();
            var totalCentralDirEntries = stream.readUShortLe();
            var sizeOfCentralDir = stream.readUIntLe();
            var offsetToCentralDir = stream.readUIntLe();
            var zipCommentLength = stream.readUShortLe();

            if (zipCommentLength > 0 && zipCommentLength < 4096) {
                var zipComment = stream.readAsciiString(zipCommentLength);
                node.add("Comment", zipComment);
            } else {
                stream.seek(zipCommentLength, 1);
            }

        } else {
            break;
        }
    }
    return zipInfo;
}

function zipChunkName(id) {
    switch (id)
    {
        case 0x0403: return "Local file header";
        case 0x0806: return "Archive extra data";
        case 0x0201: return "Central directory file header";
        case 0x0505: return "Digital signature";
        case 0x0606: return "Zip64 end of central directory record";
        case 0x0706: return "Zip64 end of central directory locator";
        case 0x0605: return "End of central directory record";
    }
    return "Unknown";
}<commit_msg>Always show MIME type, if found.<commit_after>/*
 Copyright (c) 2016 Dmitry Brant.
 http://dmitrybrant.com

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

function parseFormat(reader)
{
	var results = new ResultNode("ZIP structure");
	try {
		var stream = new DataStream(reader);

        var contentResults = new ResultNode("ZIP contents");
        var fileList = [];
        var mimeType = "";

        try {
            var zipInfo = zipReadContents(stream, contentResults, fileList);
            if (zipInfo.mimeType !== undefined) {
                mimeType = zipInfo.mimeType;
            }
        } catch (e) {
            console.log("Error while reading ZIP contents: " + e);
        }

        var fileExt = "ZIP";
        var fileType = "Standard ZIP file";

        if (fileList.indexOf("word/document.xml") >= 0) {
            fileExt = "DOCX";
            fileType = "Microsoft Word (2010 and above) document";
        } else if (fileList.indexOf("ppt/presentation.xml") >= 0) {
            fileExt = "PPTX";
            fileType = "Microsoft PowerPoint (2010 and above) presentation";
        } else if (fileList.indexOf("xl/workbook.xml") >= 0) {
            fileExt = "XLSX";
            fileType = "Microsoft Excel (2010 and above) spreadsheet";
        } else if (fileList.indexOf("FixedDocSeq.fdseq") >= 0) {
            fileExt = "XPS";
            fileType = "Microsoft XPS document";
        } else if (fileList.indexOf("AndroidManifest.xml") >= 0) {
            fileExt = "APK";
            fileType = "Android application package";
        } else if (fileList.indexOf("snote/snote.xml") >= 0) {
            fileExt = "SNB";
            fileType = "Exported Samsung S-Note file";
        } else if (fileList.indexOf("content/riffData.cdr") >= 0) {
            fileExt = "CDR";
            fileType = "CorelDraw image";
        } else if (fileList.indexOf("Root.xml") >= 0 && fileList.indexOf("summary.xml") >= 0) {
            if (fileList.indexOf("preview.png") >= 0) {
                fileExt = "DPP";
                fileType = "Serif DrawPlus document";
            } else if (fileList.indexOf("preview.jpg") >= 0) {
                fileExt = "PPP";
                fileType = "Serif PagePlus document";
            }
        } else if (fileList.indexOf("doc.kml") >= 0) {
            fileExt = "KMZ";
            fileType = "Google Earth location data";
        } else if (mimeType.indexOf("opendocument.text") >= 0) {
            fileExt = "ODT";
            fileType = "OpenDocument text file";
        } else if (mimeType.indexOf("opendocument.spreadsheet") >= 0) {
            fileExt = "ODS";
            fileType = "OpenDocument spreadsheet";
        } else if (mimeType.indexOf("opendocument.presentation") >= 0) {
            fileExt = "ODP";
            fileType = "OpenDocument presentation";
        } else if (mimeType.indexOf("opendocument.graphics") >= 0) {
            fileExt = "ODG";
            fileType = "OpenDocument graphics file";
        } else if (mimeType.indexOf("application/epub") >= 0) {
            fileExt = "EPUB";
            fileType = "Electronic publication or e-book";
        }

        results.add("File type", fileType);
        results.add("File extension", fileExt);
        results.addResult(contentResults);

    } catch(e) {
		console.log("Error while reading ZIP: " + e);
	}
	return results;
}

function zipReadContents(stream, results, fileList) {
    var zipInfo = {};
    var chunkType;
    var fileName;
    var versionNeeded, flags, compressionMethod;
    var lastModTime, lastModDate, crc32;
    var compressedSize, uncompressedSize, fileNameLength;
    var extraFieldLength;
    var node;

    while (!stream.eof()) {
        //read chunk type...
        if (stream.readAsciiString(2) != "PK") {
            break;
        }
        chunkType = stream.readUShortLe();

        node = results.add("0x" + chunkType.toString(16), zipChunkName(chunkType));

        if (chunkType == 0x0403) {
            //compressed file data!
            versionNeeded = stream.readUShortLe();
            flags = stream.readUShortLe();
            compressionMethod = stream.readUShortLe();
            lastModTime = stream.readUShortLe();
            lastModDate = stream.readUShortLe();

            crc32 = stream.readUIntLe();
            compressedSize = stream.readUIntLe();
            uncompressedSize = stream.readUIntLe();
            fileNameLength = stream.readUShortLe();
            extraFieldLength = stream.readUShortLe();

            //sanity
            if (fileNameLength > 10000) {
                break;
            }

            //read file name...
            fileName = stream.readAsciiString(fileNameLength);

            node.add("Name", fileName);
            node.add("Size (compressed)", compressedSize);
            node.add("Size (uncompressed)", uncompressedSize);

            if (fileList !== null) {
                fileList.push(fileName);
            }

            stream.seek(extraFieldLength, 1);

            if (fileName == "mimetype" && compressedSize == uncompressedSize && compressedSize < 256) {
                // it's trying to tell us something!
                zipInfo.mimeType = stream.readAsciiString(compressedSize);
                node.add("MIME type", zipInfo.mimeType);
            } else {
                stream.seek(compressedSize, 1);
            }

            //read data descriptor?
            if ((flags & 0x8) != 0) {
                //int s = SearchForSig(disk, new byte[]{0x50, 0x4B, 0x7, 0x8}, 10000);
                //if (s >= 0) {
                //    fileSize += s;
                //} else {
                //    haveHeader = false;
                //    break;
                //}
            }
        } else if (chunkType == 0x0807) {

            var dataCrc32 = stream.readUIntLe();
            var dataCompSize = stream.readUIntLe();
            var dataUncompSize = stream.readUIntLe();

            node.add("Size (compressed)", dataCompSize);
            node.add("Size (uncompressed)", dataUncompSize);

        } else if (chunkType == 0x0806) {

            //archive extra data record!
            extraFieldLength = stream.readUIntLe();
            stream.seek(extraFieldLength, 1);

            node.add("Size", extraFieldLength);

        } else if (chunkType == 0x0201) {

            //directory file header!
            var versionMadeBy = stream.readUShortLe();
            versionNeeded = stream.readUShortLe();
            flags = stream.readUShortLe();
            compressionMethod = stream.readUShortLe();
            lastModTime = stream.readUShortLe();
            lastModDate = stream.readUShortLe();

            crc32 = stream.readUIntLe();
            compressedSize = stream.readUIntLe();
            uncompressedSize = stream.readUIntLe();
            fileNameLength = stream.readUShortLe();
            extraFieldLength = stream.readUShortLe();
            var fileCommentLength = stream.readUShortLe();
            var diskNumberStart = stream.readUShortLe();
            var internalFileAttr = stream.readUShortLe();
            var externalFileAttr = stream.readUIntLe();
            var relHeaderOffset = stream.readUIntLe();

            fileName = stream.readAsciiString(fileNameLength);

            node.add("Name", fileName);
            node.add("Size (compressed)", compressedSize);
            node.add("Size (uncompressed)", uncompressedSize);

            //read extra field...
            stream.seek(extraFieldLength, 1);

            //read file comment...
            if (fileCommentLength > 0 && fileCommentLength < 4096) {
                var fileComment = stream.readAsciiString(fileCommentLength);
                node.add("Comment", fileComment);
            } else {
                stream.seek(fileCommentLength, 1);
            }

        } else if (chunkType == 0x0505) {

            //digital signature!
            var dataLength = stream.readUShortLe();
            stream.seek(dataLength, 1);

            node.add("Size", dataLength);

        } else if (chunkType == 0x0606) {

            //zip64 end of central directory!
            var sizeOfRecord = stream.readLongLe();
            if (sizeOfRecord > 0) {
                stream.seek(sizeOfRecord, 1);
            }

            node.add("Size", sizeOfRecord);

        } else if (chunkType == 0x0706) {

            //zip64 end of central dir locator!
            var numberofDisk = stream.readUIntLe();
            var offsetToCentralDirRec = stream.readLongLe();
            var numTotalDisks = stream.readUIntLe();

        } else if (chunkType == 0x0605) {

            //directory file header!
            var diskNumber = stream.readUShortLe();
            var numDiskWithCentralDir = stream.readUShortLe();
            var localCentralDirEntries = stream.readUShortLe();
            var totalCentralDirEntries = stream.readUShortLe();
            var sizeOfCentralDir = stream.readUIntLe();
            var offsetToCentralDir = stream.readUIntLe();
            var zipCommentLength = stream.readUShortLe();

            if (zipCommentLength > 0 && zipCommentLength < 4096) {
                var zipComment = stream.readAsciiString(zipCommentLength);
                node.add("Comment", zipComment);
            } else {
                stream.seek(zipCommentLength, 1);
            }

        } else {
            break;
        }
    }
    return zipInfo;
}

function zipChunkName(id) {
    switch (id)
    {
        case 0x0403: return "Local file header";
        case 0x0806: return "Archive extra data";
        case 0x0201: return "Central directory file header";
        case 0x0505: return "Digital signature";
        case 0x0606: return "Zip64 end of central directory record";
        case 0x0706: return "Zip64 end of central directory locator";
        case 0x0605: return "End of central directory record";
    }
    return "Unknown";
}<|endoftext|>


<commit_before>/**
 * Armenian translation for bootstrap-datepicker
 * Hayk Chamyan <hamshen@gmail.com>
 */
;(function($){
	$.fn.datepicker.dates['hy'] = {
		days: ["Կիրակի", "Երկուշաբթի", "Երեքշաբթի", "Չորեքշաբթի", "Հինգշաբթի", "Ուրբաթ", "Շաբաթ", "Կիրակի"],
		daysShort: ["Կիր", "Երկ", "Երք", "Չոր", "Հնգ", "Ուր", "Շաբ", "Կիր"],
		daysMin: ["Կի", "Եկ", "Եք", "Չո", "Հի", "Ու", "Շա", "Կի"],
		months: ["Հունվար", "Փետրվար", "Մարտ", "Ապրիլ", "Մայիս", "Հունիս", "Հուլիս", "Օգոստոս", "Սեպտեմբեր", "Հոկտեմբեր", "Նոյեմբեր", "Դեկտեմբեր"],
		monthsShort: ["Հնվ", "Փետ", "Մար", "Ապր", "Մայ", "Հուն", "Հուլ", "Օգս", "Սեպ", "Հոկ", "Նոյ", "Դեկ"],
		today: "Այսօր",
		weekStart: 1
	};
}(jQuery));
<commit_msg>Update translation<commit_after>/**
 * Armenian translation for bootstrap-datepicker
 * Hayk Chamyan <hamshen@gmail.com>
 */
;(function($){
	$.fn.datepicker.dates['hy'] = {
		days: ["Կիրակի", "Երկուշաբթի", "Երեքշաբթի", "Չորեքշաբթի", "Հինգշաբթի", "Ուրբաթ", "Շաբաթ", "Կիրակի"],
		daysShort: ["Կիր", "Երկ", "Երք", "Չոր", "Հնգ", "Ուր", "Շաբ", "Կիր"],
		daysMin: ["Կի", "Եկ", "Եք", "Չո", "Հի", "Ու", "Շա", "Կի"],
		months: ["Հունվար", "Փետրվար", "Մարտ", "Ապրիլ", "Մայիս", "Հունիս", "Հուլիս", "Օգոստոս", "Սեպտեմբեր", "Հոկտեմբեր", "Նոյեմբեր", "Դեկտեմբեր"],
		monthsShort: ["Հնվ", "Փետ", "Մար", "Ապր", "Մայ", "Հուն", "Հուլ", "Օգս", "Սեպ", "Հոկ", "Նոյ", "Դեկ"],
		today: "Այսօր",
		clear: "Ջնջել",
		format: "dd.mm.yyyy",
		weekStart: 1
	};
}(jQuery));
<|endoftext|>


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

//import { MozillaBrowserBot } from "./selenium-browserbot";
import { bot, core } from './closure-polyfill'
import { parse_locator } from './utils'
import finder from '@medv/finder'

export default function LocatorBuilders(window) {
  this.window = window
}

window.LocatorBuilders = LocatorBuilders

LocatorBuilders.prototype.detach = function() {}

LocatorBuilders.prototype.buildWith = function(name, e, opt_contextNode) {
  return LocatorBuilders.builderMap[name].call(this, e, opt_contextNode)
}

LocatorBuilders.prototype.elementEquals = function(name, e, locator) {
  let fe = this.findElement(locator)
  //TODO: add match function to the ui locator builder, note the inverted parameters
  return (
    e == fe ||
    (LocatorBuilders.builderMap[name] &&
      LocatorBuilders.builderMap[name].match &&
      LocatorBuilders.builderMap[name].match(e, fe))
  )
}

LocatorBuilders.prototype.build = function(e) {
  let locators = this.buildAll(e)
  if (locators.length > 0) {
    return locators[0][0]
  } else {
    return 'LOCATOR_DETECTION_FAILED'
  }
}

LocatorBuilders.prototype.buildAll = function(el) {
  let e = core.firefox.unwrap(el) //Samit: Fix: Do the magic to get it to work in Firefox 4
  let locator
  let locators = []
  for (let i = 0; i < LocatorBuilders.order.length; i++) {
    let finderName = LocatorBuilders.order[i]
    try {
      locator = this.buildWith(finderName, e)
      if (locator) {
        locator = String(locator)
        //Samit: The following is a quickfix for above commented code to stop exceptions on almost every locator builder
        //TODO: the builderName should NOT be used as a strategy name, create a feature to allow locatorBuilders to specify this kind of behaviour
        //TODO: Useful if a builder wants to capture a different element like a parent. Use the this.elementEquals
        let fe = this.findElement(locator)
        if (e == fe) {
          locators.push([locator, finderName])
        }
      }
    } catch (e) {
      // TODO ignore the buggy locator builder for now
      //this.log.debug("locator exception: " + e);
    }
  }
  return locators
}

LocatorBuilders.prototype.findElement = function(loc) {
  try {
    const locator = parse_locator(loc, true)
    return bot.locators.findElement(
      { [locator.type]: locator.string },
      this.window.document
    )
  } catch (error) {
    //this.log.debug("findElement failed: " + error + ", locator=" + locator);
    return null
  }
}

/*
 * Class methods
 */

LocatorBuilders.order = []
LocatorBuilders.builderMap = {}
LocatorBuilders._preferredOrder = []
// NOTE: for some reasons we does not use this part
// classObservable(LocatorBuilders);

LocatorBuilders.add = function(name, finder) {
  this.order.push(name)
  this.builderMap[name] = finder
  this._orderChanged()
}

/**
 * Call when the order or preferred order changes
 */
LocatorBuilders._orderChanged = function() {
  let changed = this._ensureAllPresent(this.order, this._preferredOrder)
  this._sortByRefOrder(this.order, this._preferredOrder)
  if (changed) {
    // NOTE: for some reasons we does not use this part
    // this.notify('preferredOrderChanged', this._preferredOrder);
  }
}

/**
 * Set the preferred order of the locator builders
 *
 * @param preferredOrder can be an array or a comma separated string of names
 */
LocatorBuilders.setPreferredOrder = function(preferredOrder) {
  if (typeof preferredOrder === 'string') {
    this._preferredOrder = preferredOrder.split(',')
  } else {
    this._preferredOrder = preferredOrder
  }
  this._orderChanged()
}

/**
 * Returns the locator builders preferred order as an array
 */
LocatorBuilders.getPreferredOrder = function() {
  return this._preferredOrder
}

/**
 * Sorts arrayToSort in the order of elements in sortOrderReference
 * @param arrayToSort
 * @param sortOrderReference
 */
LocatorBuilders._sortByRefOrder = function(arrayToSort, sortOrderReference) {
  let raLen = sortOrderReference.length
  arrayToSort.sort(function(a, b) {
    let ai = sortOrderReference.indexOf(a)
    let bi = sortOrderReference.indexOf(b)
    return (ai > -1 ? ai : raLen) - (bi > -1 ? bi : raLen)
  })
}

/**
 * Function to add to the bottom of destArray elements from source array that do not exist in destArray
 * @param sourceArray
 * @param destArray
 */
LocatorBuilders._ensureAllPresent = function(sourceArray, destArray) {
  let changed = false
  sourceArray.forEach(function(e) {
    if (destArray.indexOf(e) == -1) {
      destArray.push(e)
      changed = true
    }
  })
  return changed
}

/*
 * Utility function: Encode XPath attribute value.
 */
LocatorBuilders.prototype.attributeValue = function(value) {
  if (value.indexOf("'") < 0) {
    return "'" + value + "'"
  } else if (value.indexOf('"') < 0) {
    return '"' + value + '"'
  } else {
    let result = 'concat('
    let part = ''
    let didReachEndOfValue = false
    while (!didReachEndOfValue) {
      let apos = value.indexOf("'")
      let quot = value.indexOf('"')
      if (apos < 0) {
        result += "'" + value + "'"
        didReachEndOfValue = true
        break
      } else if (quot < 0) {
        result += '"' + value + '"'
        didReachEndOfValue = true
        break
      } else if (quot < apos) {
        part = value.substring(0, apos)
        result += "'" + part + "'"
        value = value.substring(part.length)
      } else {
        part = value.substring(0, quot)
        result += '"' + part + '"'
        value = value.substring(part.length)
      }
      result += ','
    }
    result += ')'
    return result
  }
}

LocatorBuilders.prototype.xpathHtmlElement = function(name) {
  if (this.window.document.contentType == 'application/xhtml+xml') {
    // "x:" prefix is required when testing XHTML pages
    return 'x:' + name
  } else {
    return name
  }
}

LocatorBuilders.prototype.relativeXPathFromParent = function(current) {
  let index = this.getNodeNbr(current)
  let currentPath = '/' + this.xpathHtmlElement(current.nodeName.toLowerCase())
  if (index > 0) {
    currentPath += '[' + (index + 1) + ']'
  }
  return currentPath
}

LocatorBuilders.prototype.getNodeNbr = function(current) {
  let childNodes = current.parentNode.childNodes
  let total = 0
  let index = -1
  for (let i = 0; i < childNodes.length; i++) {
    let child = childNodes[i]
    if (child.nodeName == current.nodeName) {
      if (child == current) {
        index = total
      }
      total++
    }
  }
  return index
}

LocatorBuilders.prototype.preciseXPath = function(xpath, e) {
  //only create more precise xpath if needed
  if (this.findElement(xpath) != e) {
    let result = e.ownerDocument.evaluate(
      xpath,
      e.ownerDocument,
      null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
      null
    )
    //skip first element (result:0 xpath index:1)
    for (let i = 0, len = result.snapshotLength; i < len; i++) {
      let newPath = 'xpath=(' + xpath + ')[' + (i + 1) + ']'
      if (this.findElement(newPath) == e) {
        return newPath
      }
    }
  }
  return 'xpath=' + xpath
}

/*
 * ===== builders =====
 */

// order listed dictates priority
// e.g., 1st listed is top priority

LocatorBuilders.add('css:data-attr', function(e) {
  const dataAttributes = ['data-test', 'data-test-id']
  for (let i = 0; i < dataAttributes.length; i++) {
    const attr = dataAttributes[i]
    const value = e.getAttribute(attr)
    if (attr) {
      return `css=*[${attr}="${value}"]`
    }
  }
  return null
})

LocatorBuilders.add('id', function(e) {
  if (e.id) {
    return 'id=' + e.id
  }
  return null
})

LocatorBuilders.add('linkText', function(e) {
  if (e.nodeName == 'A') {
    let text = e.textContent
    if (!text.match(/^\s*$/)) {
      return (
        'linkText=' + text.replace(/\xA0/g, ' ').replace(/^\s*(.*?)\s*$/, '$1')
      )
    }
  }
  return null
})

LocatorBuilders.add('name', function(e) {
  if (e.name) {
    return 'name=' + e.name
  }
  return null
})

LocatorBuilders.add('css:finder', function(e) {
  return 'css=' + finder(e)
})

LocatorBuilders.add('xpath:link', function(e) {
  if (e.nodeName == 'A') {
    let text = e.textContent
    if (!text.match(/^\s*$/)) {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('a') +
          "[contains(text(),'" +
          text.replace(/^\s+/, '').replace(/\s+$/, '') +
          "')]",
        e
      )
    }
  }
  return null
})

LocatorBuilders.add('xpath:img', function(e) {
  if (e.nodeName == 'IMG') {
    if (e.alt != '') {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('img') +
          '[@alt=' +
          this.attributeValue(e.alt) +
          ']',
        e
      )
    } else if (e.title != '') {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('img') +
          '[@title=' +
          this.attributeValue(e.title) +
          ']',
        e
      )
    } else if (e.src != '') {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('img') +
          '[contains(@src,' +
          this.attributeValue(e.src) +
          ')]',
        e
      )
    }
  }
  return null
})

LocatorBuilders.add('xpath:attributes', function(e) {
  const PREFERRED_ATTRIBUTES = [
    'id',
    'name',
    'value',
    'type',
    'action',
    'onclick',
  ]
  let i = 0

  function attributesXPath(name, attNames, attributes) {
    let locator = '//' + this.xpathHtmlElement(name) + '['
    for (i = 0; i < attNames.length; i++) {
      if (i > 0) {
        locator += ' and '
      }
      let attName = attNames[i]
      locator += '@' + attName + '=' + this.attributeValue(attributes[attName])
    }
    locator += ']'
    return this.preciseXPath(locator, e)
  }

  if (e.attributes) {
    let atts = e.attributes
    let attsMap = {}
    for (i = 0; i < atts.length; i++) {
      let att = atts[i]
      attsMap[att.name] = att.value
    }
    let names = []
    // try preferred attributes
    for (i = 0; i < PREFERRED_ATTRIBUTES.length; i++) {
      let name = PREFERRED_ATTRIBUTES[i]
      if (attsMap[name] != null) {
        names.push(name)
        let locator = attributesXPath.call(
          this,
          e.nodeName.toLowerCase(),
          names,
          attsMap
        )
        if (e == this.findElement(locator)) {
          return locator
        }
      }
    }
  }
  return null
})

LocatorBuilders.add('xpath:idRelative', function(e) {
  let path = ''
  let current = e
  while (current != null) {
    if (current.parentNode != null) {
      path = this.relativeXPathFromParent(current) + path
      if (
        1 == current.parentNode.nodeType && // ELEMENT_NODE
        current.parentNode.getAttribute('id')
      ) {
        return this.preciseXPath(
          '//' +
            this.xpathHtmlElement(current.parentNode.nodeName.toLowerCase()) +
            '[@id=' +
            this.attributeValue(current.parentNode.getAttribute('id')) +
            ']' +
            path,
          e
        )
      }
    } else {
      return null
    }
    current = current.parentNode
  }
  return null
})

LocatorBuilders.add('xpath:href', function(e) {
  if (e.attributes && e.hasAttribute('href')) {
    let href = e.getAttribute('href')
    if (href.search(/^http?:\/\//) >= 0) {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('a') +
          '[@href=' +
          this.attributeValue(href) +
          ']',
        e
      )
    } else {
      // use contains(), because in IE getAttribute("href") will return absolute path
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('a') +
          '[contains(@href, ' +
          this.attributeValue(href) +
          ')]',
        e
      )
    }
  }
  return null
})

LocatorBuilders.add('xpath:position', function(e, opt_contextNode) {
  let path = ''
  let current = e
  while (current != null && current != opt_contextNode) {
    let currentPath
    if (current.parentNode != null) {
      currentPath = this.relativeXPathFromParent(current)
    } else {
      currentPath = '/' + this.xpathHtmlElement(current.nodeName.toLowerCase())
    }
    path = currentPath + path
    let locator = '/' + path
    if (e == this.findElement(locator)) {
      return 'xpath=' + locator
    }
    current = current.parentNode
  }
  return null
})

LocatorBuilders.add('xpath:innerText', function(el) {
  if (el.innerText) {
    return `xpath=//${el.nodeName.toLowerCase()}[contains(.,'${el.innerText}')]`
  } else {
    return null
  }
})
<commit_msg>name all locator functions<commit_after>/*
 * Copyright 2005 Shinya Kasatani
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//import { MozillaBrowserBot } from "./selenium-browserbot";
import { bot, core } from './closure-polyfill'
import { parse_locator } from './utils'
import finder from '@medv/finder'

export default function LocatorBuilders(window) {
  this.window = window
}

window.LocatorBuilders = LocatorBuilders

LocatorBuilders.prototype.detach = function() {}

LocatorBuilders.prototype.buildWith = function(name, e, opt_contextNode) {
  return LocatorBuilders.builderMap[name].call(this, e, opt_contextNode)
}

LocatorBuilders.prototype.elementEquals = function(name, e, locator) {
  let fe = this.findElement(locator)
  //TODO: add match function to the ui locator builder, note the inverted parameters
  return (
    e == fe ||
    (LocatorBuilders.builderMap[name] &&
      LocatorBuilders.builderMap[name].match &&
      LocatorBuilders.builderMap[name].match(e, fe))
  )
}

LocatorBuilders.prototype.build = function(e) {
  let locators = this.buildAll(e)
  if (locators.length > 0) {
    return locators[0][0]
  } else {
    return 'LOCATOR_DETECTION_FAILED'
  }
}

LocatorBuilders.prototype.buildAll = function(el) {
  let e = core.firefox.unwrap(el) //Samit: Fix: Do the magic to get it to work in Firefox 4
  let locator
  let locators = []
  for (let i = 0; i < LocatorBuilders.order.length; i++) {
    let finderName = LocatorBuilders.order[i]
    try {
      locator = this.buildWith(finderName, e)
      if (locator) {
        locator = String(locator)
        //Samit: The following is a quickfix for above commented code to stop exceptions on almost every locator builder
        //TODO: the builderName should NOT be used as a strategy name, create a feature to allow locatorBuilders to specify this kind of behaviour
        //TODO: Useful if a builder wants to capture a different element like a parent. Use the this.elementEquals
        let fe = this.findElement(locator)
        if (e == fe) {
          locators.push([locator, finderName])
        }
      }
    } catch (e) {
      // TODO ignore the buggy locator builder for now
      //this.log.debug("locator exception: " + e);
    }
  }
  return locators
}

LocatorBuilders.prototype.findElement = function(loc) {
  try {
    const locator = parse_locator(loc, true)
    return bot.locators.findElement(
      { [locator.type]: locator.string },
      this.window.document
    )
  } catch (error) {
    //this.log.debug("findElement failed: " + error + ", locator=" + locator);
    return null
  }
}

/*
 * Class methods
 */

LocatorBuilders.order = []
LocatorBuilders.builderMap = {}
LocatorBuilders._preferredOrder = []
// NOTE: for some reasons we does not use this part
// classObservable(LocatorBuilders);

LocatorBuilders.add = function(name, finder) {
  this.order.push(name)
  this.builderMap[name] = finder
  this._orderChanged()
}

/**
 * Call when the order or preferred order changes
 */
LocatorBuilders._orderChanged = function() {
  let changed = this._ensureAllPresent(this.order, this._preferredOrder)
  this._sortByRefOrder(this.order, this._preferredOrder)
  if (changed) {
    // NOTE: for some reasons we does not use this part
    // this.notify('preferredOrderChanged', this._preferredOrder);
  }
}

/**
 * Set the preferred order of the locator builders
 *
 * @param preferredOrder can be an array or a comma separated string of names
 */
LocatorBuilders.setPreferredOrder = function(preferredOrder) {
  if (typeof preferredOrder === 'string') {
    this._preferredOrder = preferredOrder.split(',')
  } else {
    this._preferredOrder = preferredOrder
  }
  this._orderChanged()
}

/**
 * Returns the locator builders preferred order as an array
 */
LocatorBuilders.getPreferredOrder = function() {
  return this._preferredOrder
}

/**
 * Sorts arrayToSort in the order of elements in sortOrderReference
 * @param arrayToSort
 * @param sortOrderReference
 */
LocatorBuilders._sortByRefOrder = function(arrayToSort, sortOrderReference) {
  let raLen = sortOrderReference.length
  arrayToSort.sort(function(a, b) {
    let ai = sortOrderReference.indexOf(a)
    let bi = sortOrderReference.indexOf(b)
    return (ai > -1 ? ai : raLen) - (bi > -1 ? bi : raLen)
  })
}

/**
 * Function to add to the bottom of destArray elements from source array that do not exist in destArray
 * @param sourceArray
 * @param destArray
 */
LocatorBuilders._ensureAllPresent = function(sourceArray, destArray) {
  let changed = false
  sourceArray.forEach(function(e) {
    if (destArray.indexOf(e) == -1) {
      destArray.push(e)
      changed = true
    }
  })
  return changed
}

/*
 * Utility function: Encode XPath attribute value.
 */
LocatorBuilders.prototype.attributeValue = function(value) {
  if (value.indexOf("'") < 0) {
    return "'" + value + "'"
  } else if (value.indexOf('"') < 0) {
    return '"' + value + '"'
  } else {
    let result = 'concat('
    let part = ''
    let didReachEndOfValue = false
    while (!didReachEndOfValue) {
      let apos = value.indexOf("'")
      let quot = value.indexOf('"')
      if (apos < 0) {
        result += "'" + value + "'"
        didReachEndOfValue = true
        break
      } else if (quot < 0) {
        result += '"' + value + '"'
        didReachEndOfValue = true
        break
      } else if (quot < apos) {
        part = value.substring(0, apos)
        result += "'" + part + "'"
        value = value.substring(part.length)
      } else {
        part = value.substring(0, quot)
        result += '"' + part + '"'
        value = value.substring(part.length)
      }
      result += ','
    }
    result += ')'
    return result
  }
}

LocatorBuilders.prototype.xpathHtmlElement = function(name) {
  if (this.window.document.contentType == 'application/xhtml+xml') {
    // "x:" prefix is required when testing XHTML pages
    return 'x:' + name
  } else {
    return name
  }
}

LocatorBuilders.prototype.relativeXPathFromParent = function(current) {
  let index = this.getNodeNbr(current)
  let currentPath = '/' + this.xpathHtmlElement(current.nodeName.toLowerCase())
  if (index > 0) {
    currentPath += '[' + (index + 1) + ']'
  }
  return currentPath
}

LocatorBuilders.prototype.getNodeNbr = function(current) {
  let childNodes = current.parentNode.childNodes
  let total = 0
  let index = -1
  for (let i = 0; i < childNodes.length; i++) {
    let child = childNodes[i]
    if (child.nodeName == current.nodeName) {
      if (child == current) {
        index = total
      }
      total++
    }
  }
  return index
}

LocatorBuilders.prototype.preciseXPath = function(xpath, e) {
  //only create more precise xpath if needed
  if (this.findElement(xpath) != e) {
    let result = e.ownerDocument.evaluate(
      xpath,
      e.ownerDocument,
      null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
      null
    )
    //skip first element (result:0 xpath index:1)
    for (let i = 0, len = result.snapshotLength; i < len; i++) {
      let newPath = 'xpath=(' + xpath + ')[' + (i + 1) + ']'
      if (this.findElement(newPath) == e) {
        return newPath
      }
    }
  }
  return 'xpath=' + xpath
}

/*
 * ===== builders =====
 */

// order listed dictates priority
// e.g., 1st listed is top priority

LocatorBuilders.add('css:data-attr', function cssDataAttr(e) {
  const dataAttributes = ['data-test', 'data-test-id']
  for (let i = 0; i < dataAttributes.length; i++) {
    const attr = dataAttributes[i]
    const value = e.getAttribute(attr)
    if (attr) {
      return `css=*[${attr}="${value}"]`
    }
  }
  return null
})

LocatorBuilders.add('id', function id(e) {
  if (e.id) {
    return 'id=' + e.id
  }
  return null
})

LocatorBuilders.add('linkText', function linkText(e) {
  if (e.nodeName == 'A') {
    let text = e.textContent
    if (!text.match(/^\s*$/)) {
      return (
        'linkText=' + text.replace(/\xA0/g, ' ').replace(/^\s*(.*?)\s*$/, '$1')
      )
    }
  }
  return null
})

LocatorBuilders.add('name', function name(e) {
  if (e.name) {
    return 'name=' + e.name
  }
  return null
})

LocatorBuilders.add('css:finder', function cssFinder(e) {
  return 'css=' + finder(e)
})

LocatorBuilders.add('xpath:link', function xpathLink(e) {
  if (e.nodeName == 'A') {
    let text = e.textContent
    if (!text.match(/^\s*$/)) {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('a') +
          "[contains(text(),'" +
          text.replace(/^\s+/, '').replace(/\s+$/, '') +
          "')]",
        e
      )
    }
  }
  return null
})

LocatorBuilders.add('xpath:img', function xpathImg(e) {
  if (e.nodeName == 'IMG') {
    if (e.alt != '') {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('img') +
          '[@alt=' +
          this.attributeValue(e.alt) +
          ']',
        e
      )
    } else if (e.title != '') {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('img') +
          '[@title=' +
          this.attributeValue(e.title) +
          ']',
        e
      )
    } else if (e.src != '') {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('img') +
          '[contains(@src,' +
          this.attributeValue(e.src) +
          ')]',
        e
      )
    }
  }
  return null
})

LocatorBuilders.add('xpath:attributes', function xpathAttr(e) {
  const PREFERRED_ATTRIBUTES = [
    'id',
    'name',
    'value',
    'type',
    'action',
    'onclick',
  ]
  let i = 0

  function attributesXPath(name, attNames, attributes) {
    let locator = '//' + this.xpathHtmlElement(name) + '['
    for (i = 0; i < attNames.length; i++) {
      if (i > 0) {
        locator += ' and '
      }
      let attName = attNames[i]
      locator += '@' + attName + '=' + this.attributeValue(attributes[attName])
    }
    locator += ']'
    return this.preciseXPath(locator, e)
  }

  if (e.attributes) {
    let atts = e.attributes
    let attsMap = {}
    for (i = 0; i < atts.length; i++) {
      let att = atts[i]
      attsMap[att.name] = att.value
    }
    let names = []
    // try preferred attributes
    for (i = 0; i < PREFERRED_ATTRIBUTES.length; i++) {
      let name = PREFERRED_ATTRIBUTES[i]
      if (attsMap[name] != null) {
        names.push(name)
        let locator = attributesXPath.call(
          this,
          e.nodeName.toLowerCase(),
          names,
          attsMap
        )
        if (e == this.findElement(locator)) {
          return locator
        }
      }
    }
  }
  return null
})

LocatorBuilders.add('xpath:idRelative', function xpathIdRelative(e) {
  let path = ''
  let current = e
  while (current != null) {
    if (current.parentNode != null) {
      path = this.relativeXPathFromParent(current) + path
      if (
        1 == current.parentNode.nodeType && // ELEMENT_NODE
        current.parentNode.getAttribute('id')
      ) {
        return this.preciseXPath(
          '//' +
            this.xpathHtmlElement(current.parentNode.nodeName.toLowerCase()) +
            '[@id=' +
            this.attributeValue(current.parentNode.getAttribute('id')) +
            ']' +
            path,
          e
        )
      }
    } else {
      return null
    }
    current = current.parentNode
  }
  return null
})

LocatorBuilders.add('xpath:href', function xpathHref(e) {
  if (e.attributes && e.hasAttribute('href')) {
    let href = e.getAttribute('href')
    if (href.search(/^http?:\/\//) >= 0) {
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('a') +
          '[@href=' +
          this.attributeValue(href) +
          ']',
        e
      )
    } else {
      // use contains(), because in IE getAttribute("href") will return absolute path
      return this.preciseXPath(
        '//' +
          this.xpathHtmlElement('a') +
          '[contains(@href, ' +
          this.attributeValue(href) +
          ')]',
        e
      )
    }
  }
  return null
})

LocatorBuilders.add('xpath:position', function xpathPosition(
  e,
  opt_contextNode
) {
  let path = ''
  let current = e
  while (current != null && current != opt_contextNode) {
    let currentPath
    if (current.parentNode != null) {
      currentPath = this.relativeXPathFromParent(current)
    } else {
      currentPath = '/' + this.xpathHtmlElement(current.nodeName.toLowerCase())
    }
    path = currentPath + path
    let locator = '/' + path
    if (e == this.findElement(locator)) {
      return 'xpath=' + locator
    }
    current = current.parentNode
  }
  return null
})

LocatorBuilders.add('xpath:innerText', function xpathInnerText(el) {
  if (el.innerText) {
    return `xpath=//${el.nodeName.toLowerCase()}[contains(.,'${el.innerText}')]`
  } else {
    return null
  }
})
<|endoftext|>


<commit_before>$(function(){

  // Follow tab's URL instead of loading its content via ajax
  $("#tabs").tabs();

  // Restore previously selected tab
  var selected_tab = $.cookie("ganglia-selected-tab");
  if ((selected_tab != null) && (selected_tab.length > 0)) {
    try {
      var tab_index = parseInt(selected_tab, 10);
      if (!isNaN(tab_index)) {
        //alert("ganglia-selected-tab: " + tab_index);
        $("#tabs").tabs("select", tab_index);
        switch (tab_index) {
          case 2:
            getViewsContent();
            break;
          case 4:
            autoRotationChooser();
            break;
        }
      }
    } catch (err) {
      alert("Error(ganglia.js): Unable to select tab: " +
            tab_index + ". " + err.getDescription());
    }
  }

  $("#tabs").bind("tabsselect", function(event, ui) {
    // Store selected tab in a session cookie
    $.cookie("ganglia-selected-tab", ui.index);
  });

  $( "#range_menu" ).buttonset();
  $( "#sort_menu" ).buttonset();

  jQuery('#metric-search input[name="q"]').liveSearch({url: 'search.php?q=', typeDelay: 500});

  $( "#datepicker-cs" ).datepicker({
	  showOn: "button",
	  buttonImage: "img/calendar.gif",
	  buttonImageOnly: true
  });
  $( "#datepicker-ce" ).datepicker({
	  showOn: "button",
	  buttonImage: "img/calendar.gif",
	  buttonImageOnly: true
  });

  $( "#create-new-view-dialog" ).dialog({
    autoOpen: false,
    height: 200,
    width: 350,
    modal: true,
    close: function() {
      getViewsContent();
      $("#create-new-view-layer").toggle();
      $("#create-new-view-confirmation-layer").html("");
    }
  });

  $( "#metric-actions-dialog" ).dialog({
    autoOpen: false,
    height: 250,
    width: 450,
    modal: true
  });

});

function selectView(view_name) {
  $.cookie('ganglia-selected-view', view_name);
  var range = $.cookie('ganglia-view-range');
  if (range == null)
    range = '1hour';
  getViewsContentJustGraphs(view_name, range, '', '');
}

function getViewsContent() {
  $.get('views.php', "" , function(data) {
    $("#tabs-views-content").html('');
    $("#tabs-views-content").html(data);
    $("#create_view_button")
      .button()
      .click(function() {
	$( "#create-new-view-dialog" ).dialog( "open" );
      });;
    $( "#view_range_chooser" ).buttonset();

    // Restore previously selected view
    var view_name = document.getElementById('view_name');
    var selected_view = $.cookie("ganglia-selected-view");
    if (selected_view != null) {
        view_name.value = selected_view;
	var range = $.cookie("ganglia-view-range");
	if (range == null)
          range = "hour";
	$("#view-range-"+range).click();
    } else
      view_name.value = "default";
  });
  return false;
}

// This one avoids
function getViewsContentJustGraphs(viewName,range, cs, ce) {
    $("#view_graphs").html('');
    $.get('views.php', "view_name=" + viewName + "&just_graphs=1&r=" + range + "&cs=" + cs + "&ce=" + ce, function(data) {
	$("#view_graphs").html(data);
	document.getElementById('view_name').value = viewName;
     });
    return false;
}

function createView() {
  $("#create-new-view-confirmation-layer").html('');
  $.get('views.php', $("#create_view_form").serialize() , function(data) {
    $("#create-new-view-layer").toggle();
    $("#create-new-view-confirmation-layer").html(data);
  });
  return false;
}

function addMetricToView() {
  $.get('views.php', $("#add_metric_to_view_form").serialize() + "&add_to_view=1" , function(data) {
      $("#metric-actions-dialog-content").html('');
      $("#metric-actions-dialog-content").html(data);
  });
  return false;
}
function metricActions(host_name,metric_name,type) {
    $( "#metric-actions-dialog" ).dialog( "open" );
    $("#metric-actions-dialog-content").html('');
    $.get('actions.php', "action=show_views&host_name=" + host_name + "&metric_name=" + metric_name + "&type=" + type, function(data) {
      $("#metric-actions-dialog-content").html(data);
     });
    return false;
}

function autoRotationChooser() {
  $("#tabs-autorotation-chooser").html('');
  $.get('autorotation.php', "" , function(data) {
      $("#tabs-autorotation-chooser").html(data);
  });
}
function updateViewTimeRange() {
  alert("Not implemented yet");
}

function ganglia_submit(clearonly) {
  document.getElementById("datepicker-cs").value = "";
  document.getElementById("datepicker-ce").value = "";
  if (! clearonly)
    document.ganglia_form.submit();
}
<commit_msg>Ensure that the index of the restored tab is >= 0<commit_after>$(function(){

  // Follow tab's URL instead of loading its content via ajax
  $("#tabs").tabs();
  // Restore previously selected tab
  var selected_tab = $.cookie("ganglia-selected-tab");
  if ((selected_tab != null) && (selected_tab.length > 0)) {
    try {
      var tab_index = parseInt(selected_tab, 10);
      if (!isNaN(tab_index) && (tab_index >= 0)) {
        //alert("ganglia-selected-tab: " + tab_index);
        $("#tabs").tabs("select", tab_index);
        switch (tab_index) {
          case 2:
            getViewsContent();
            break;
          case 4:
            autoRotationChooser();
            break;
        }
      }
    } catch (err) {
      alert("Error(ganglia.js): Unable to select tab: " +
            tab_index + ". " + err.getDescription());
    }
  }

  $("#tabs").bind("tabsselect", function(event, ui) {
    // Store selected tab in a session cookie
    $.cookie("ganglia-selected-tab", ui.index);
  });

  $( "#range_menu" ).buttonset();
  $( "#sort_menu" ).buttonset();

  jQuery('#metric-search input[name="q"]').liveSearch({url: 'search.php?q=', typeDelay: 500});

  $( "#datepicker-cs" ).datepicker({
	  showOn: "button",
	  buttonImage: "img/calendar.gif",
	  buttonImageOnly: true
  });
  $( "#datepicker-ce" ).datepicker({
	  showOn: "button",
	  buttonImage: "img/calendar.gif",
	  buttonImageOnly: true
  });

  $( "#create-new-view-dialog" ).dialog({
    autoOpen: false,
    height: 200,
    width: 350,
    modal: true,
    close: function() {
      getViewsContent();
      $("#create-new-view-layer").toggle();
      $("#create-new-view-confirmation-layer").html("");
    }
  });

  $( "#metric-actions-dialog" ).dialog({
    autoOpen: false,
    height: 250,
    width: 450,
    modal: true
  });

});

function selectView(view_name) {
  $.cookie('ganglia-selected-view', view_name);
  var range = $.cookie('ganglia-view-range');
  if (range == null)
    range = '1hour';
  getViewsContentJustGraphs(view_name, range, '', '');
}

function getViewsContent() {
  $.get('views.php', "" , function(data) {
    $("#tabs-views-content").html('');
    $("#tabs-views-content").html(data);
    $("#create_view_button")
      .button()
      .click(function() {
	$( "#create-new-view-dialog" ).dialog( "open" );
      });;
    $( "#view_range_chooser" ).buttonset();

    // Restore previously selected view
    var view_name = document.getElementById('view_name');
    var selected_view = $.cookie("ganglia-selected-view");
    if (selected_view != null) {
        view_name.value = selected_view;
	var range = $.cookie("ganglia-view-range");
	if (range == null)
          range = "hour";
	$("#view-range-"+range).click();
    } else
      view_name.value = "default";
  });
  return false;
}

// This one avoids
function getViewsContentJustGraphs(viewName,range, cs, ce) {
    $("#view_graphs").html('');
    $.get('views.php', "view_name=" + viewName + "&just_graphs=1&r=" + range + "&cs=" + cs + "&ce=" + ce, function(data) {
	$("#view_graphs").html(data);
	document.getElementById('view_name').value = viewName;
     });
    return false;
}

function createView() {
  $("#create-new-view-confirmation-layer").html('');
  $.get('views.php', $("#create_view_form").serialize() , function(data) {
    $("#create-new-view-layer").toggle();
    $("#create-new-view-confirmation-layer").html(data);
  });
  return false;
}

function addMetricToView() {
  $.get('views.php', $("#add_metric_to_view_form").serialize() + "&add_to_view=1" , function(data) {
      $("#metric-actions-dialog-content").html('');
      $("#metric-actions-dialog-content").html(data);
  });
  return false;
}
function metricActions(host_name,metric_name,type) {
    $( "#metric-actions-dialog" ).dialog( "open" );
    $("#metric-actions-dialog-content").html('');
    $.get('actions.php', "action=show_views&host_name=" + host_name + "&metric_name=" + metric_name + "&type=" + type, function(data) {
      $("#metric-actions-dialog-content").html(data);
     });
    return false;
}

function autoRotationChooser() {
  $("#tabs-autorotation-chooser").html('');
  $.get('autorotation.php', "" , function(data) {
      $("#tabs-autorotation-chooser").html(data);
  });
}
function updateViewTimeRange() {
  alert("Not implemented yet");
}

function ganglia_submit(clearonly) {
  document.getElementById("datepicker-cs").value = "";
  document.getElementById("datepicker-ce").value = "";
  if (! clearonly)
    document.ganglia_form.submit();
}
<|endoftext|>


<commit_before>"use strict";

const crypto = require("crypto");
const fs = require("fs-extra");
const { pick, pickBy, sortBy, zip } = require("lodash");
const path = require("path");
const { format } = require("util");

const { getConfigModel } = require("../models/config");
const S3Bucket = require("../models/bucket");
const S3Object = require("../models/object");
const { concatStreams, walk } = require("../utils");

const S3RVER_SUFFIX = "%s._S3rver_%s";

class FilesystemStore {
  static decodeKeyPath(keyPath) {
    return process.platform === "win32"
      ? keyPath.replace(/&../g, ent =>
          Buffer.from(ent.slice(1), "hex").toString()
        )
      : keyPath;
  }

  static encodeKeyPath(key) {
    return process.platform === "win32"
      ? key.replace(
          /[<>:"\\|?*]/g,
          ch => "&" + Buffer.from(ch, "utf8").toString("hex")
        )
      : key;
  }

  constructor(rootDirectory) {
    this.rootDirectory = rootDirectory;
  }

  // helpers

  getBucketPath(bucketName) {
    return path.join(this.rootDirectory, bucketName);
  }

  getResourcePath(bucket, key = "", resource) {
    const parts = FilesystemStore.encodeKeyPath(key).split("/");
    const suffix = format(S3RVER_SUFFIX, parts.pop(), resource);
    return path.join(this.rootDirectory, bucket, ...parts, suffix);
  }

  async getMetadata(bucket, key) {
    const objectPath = this.getResourcePath(bucket, key, "object");
    const metadataPath = this.getResourcePath(bucket, key, "metadata.json");

    // this is expected to throw if the object doesn't exist
    const stat = await fs.stat(objectPath);
    const [storedMetadata, md5] = await Promise.all([
      fs
        .readFile(metadataPath)
        .then(JSON.parse)
        .catch(err => {
          if (err.code === "ENOENT") return undefined;
          throw err;
        }),
      fs
        .readFile(`${objectPath}.md5`)
        .then(md5 => md5.toString())
        .catch(async err => {
          if (err.code !== "ENOENT") throw err;
          // create the md5 file if it doesn't already exist
          const md5 = await new Promise((resolve, reject) => {
            const stream = fs.createReadStream(objectPath);
            const md5Context = crypto.createHash("md5");
            stream.on("error", reject);
            stream.on("data", chunk => md5Context.update(chunk, "utf8"));
            stream.on("end", () => resolve(md5Context.digest("hex")));
          });
          await fs.writeFile(objectPath, md5);
          return md5;
        })
    ]);

    return {
      ...storedMetadata,
      etag: JSON.stringify(md5),
      "last-modified": stat.mtime.toUTCString(),
      "content-length": stat.size
    };
  }

  async putMetadata(bucket, key, metadata, md5) {
    const metadataPath = this.getResourcePath(bucket, key, "metadata.json");
    const md5Path = this.getResourcePath(bucket, key, "object.md5");

    const json = {
      ...pick(metadata, S3Object.ALLOWED_METADATA),
      ...pickBy(metadata, (value, key) => key.startsWith("x-amz-meta-"))
    };

    if (md5) await fs.writeFile(md5Path, md5);
    await fs.writeFile(metadataPath, JSON.stringify(json, null, 2));
  }

  // store implementation

  reset() {
    const list = fs.readdirSync(this.rootDirectory);
    for (const file of list) {
      fs.removeSync(path.join(this.rootDirectory, file));
    }
  }

  async listBuckets() {
    const list = await fs.readdir(this.rootDirectory);
    const buckets = await Promise.all(
      list.map(filename => this.getBucket(filename))
    );
    return buckets.filter(Boolean);
  }

  async getBucket(bucket) {
    const bucketPath = this.getBucketPath(bucket);
    try {
      const stat = await fs.stat(bucketPath);
      if (!stat.isDirectory()) return null;
      return new S3Bucket(bucket, stat.birthtime);
    } catch (err) {
      if (err.code === "ENOENT") return null;
      throw err;
    }
  }

  async putBucket(bucket) {
    const bucketPath = this.getBucketPath(bucket);
    await fs.mkdirp(bucketPath, 0o0755);
    return this.getBucket(bucket);
  }

  async deleteBucket(bucket) {
    return fs.remove(this.getBucketPath(bucket));
  }

  async listObjects(bucket, options) {
    const {
      delimiter = "",
      startAfter = "",
      prefix = "",
      maxKeys = Infinity
    } = options;
    const bucketPath = this.getBucketPath(bucket);

    const delimiterEnc = FilesystemStore.encodeKeyPath(delimiter);
    const startAfterPath = [
      bucketPath,
      FilesystemStore.encodeKeyPath(startAfter)
    ].join("/");
    const prefixPath = [bucketPath, FilesystemStore.encodeKeyPath(prefix)].join(
      "/"
    );

    const it = walk(bucketPath, dirPath => {
      // avoid directories occurring before the startAfter parameter
      if (dirPath < startAfterPath && startAfterPath.indexOf(dirPath) === -1) {
        return false;
      }
      if (dirPath.startsWith(prefixPath)) {
        if (
          delimiterEnc &&
          dirPath.slice(prefixPath.length).indexOf(delimiterEnc) !== -1
        ) {
          // avoid directories occurring beneath a common prefix
          return false;
        }
      } else if (!prefixPath.startsWith(dirPath)) {
        // avoid directories that do not intersect with any part of the prefix
        return false;
      }
      return true;
    });

    const commonPrefixes = new Set();
    const objectSuffix = format(S3RVER_SUFFIX, "", "object");
    const keys = [];
    let isTruncated = false;
    for (const keyPath of it) {
      if (!keyPath.endsWith(objectSuffix)) {
        continue;
      }

      const key = FilesystemStore.decodeKeyPath(
        keyPath.slice(bucketPath.length + 1, -objectSuffix.length)
      );

      if (key <= startAfter || !key.startsWith(prefix)) {
        continue;
      }

      if (delimiter) {
        const idx = key.slice(prefix.length).indexOf(delimiter);
        if (idx !== -1) {
          // add to common prefixes before filtering this key out
          commonPrefixes.add(key.slice(0, prefix.length + idx + 1));
          continue;
        }
      }

      if (keys.length < maxKeys) {
        keys.push(key);
      } else {
        isTruncated = true;
        break;
      }
    }

    const metadataArr = await Promise.all(
      keys.map(key =>
        this.getMetadata(bucket, key).catch(err => {
          if (err.code === "ENOENT") return undefined;
          throw err;
        })
      )
    );

    return {
      objects: zip(keys, metadataArr)
        .filter(([, metadata]) => metadata !== undefined)
        .map(([key, metadata]) => new S3Object(bucket, key, null, metadata)),
      commonPrefixes: [...commonPrefixes].sort(),
      isTruncated
    };
  }

  async existsObject(bucket, key) {
    const objectPath = this.getResourcePath(bucket, key, "object");
    try {
      await fs.stat(objectPath);
      return true;
    } catch (err) {
      if (err.code === "ENOENT") return false;
      throw err;
    }
  }

  async getObject(bucket, key, options) {
    try {
      const metadata = await this.getMetadata(bucket, key);
      const lastByte = Number(metadata["content-length"]) - 1;
      const range = {
        start: (options && options.start) || 0,
        end: Math.min((options && options.end) || Infinity, lastByte)
      };

      if (range.start < 0 || Math.min(range.end, lastByte) < range.start) {
        // the range is not satisfiable
        const object = new S3Object(bucket, key, null, metadata);
        if (options && (options.start || options.end)) {
          object.range = range;
        }
        return object;
      }

      const content = await new Promise((resolve, reject) => {
        const stream = fs
          .createReadStream(this.getResourcePath(bucket, key, "object"), range)
          .on("error", reject)
          .on("open", () => resolve(stream));
      });
      const object = new S3Object(bucket, key, content, metadata);
      if (options && (options.start || options.end)) {
        object.range = range;
      }
      return object;
    } catch (err) {
      if (err.code === "ENOENT") return null;
      throw err;
    }
  }

  async putObject(object) {
    const objectPath = this.getResourcePath(
      object.bucket,
      object.key,
      "object"
    );

    await fs.mkdirp(path.dirname(objectPath));

    const [size, md5] = await new Promise((resolve, reject) => {
      const writeStream = fs.createWriteStream(objectPath);
      const md5Context = crypto.createHash("md5");
      let totalLength = 0;

      object.content
        .on("data", chunk => {
          writeStream.write(chunk, "binary");
          md5Context.update(chunk, "binary");
          totalLength += chunk.length;
        })
        .on("error", reject)
        .on("end", () => {
          writeStream.end();
          resolve([totalLength, md5Context.digest("hex")]);
        });
    });
    await this.putMetadata(object.bucket, object.key, object.metadata, md5);
    return { size, md5 };
  }

  async copyObject(
    srcBucket,
    srcKey,
    destBucket,
    destKey,
    replacementMetadata
  ) {
    const srcObjectPath = this.getResourcePath(srcBucket, srcKey, "object");
    const destObjectPath = this.getResourcePath(destBucket, destKey, "object");

    if (srcObjectPath !== destObjectPath) {
      await fs.mkdirp(path.dirname(destObjectPath));
      await fs.copy(srcObjectPath, destObjectPath);
    }

    if (replacementMetadata) {
      await this.putMetadata(destBucket, destKey, replacementMetadata);
      return this.getMetadata(destBucket, destKey);
    } else {
      if (srcObjectPath !== destObjectPath) {
        await fs.copy(
          this.getResourcePath(srcBucket, srcKey, "metadata.json"),
          this.getResourcePath(destBucket, destKey, "metadata.json")
        );
      }
      return this.getMetadata(destBucket, destKey);
    }
  }

  async deleteObject(bucket, key) {
    await Promise.all(
      [
        this.getResourcePath(bucket, key, "object"),
        this.getResourcePath(bucket, key, "object.md5"),
        this.getResourcePath(bucket, key, "metadata.json")
      ].map(filePath =>
        fs.unlink(filePath).catch(err => {
          if (err.code !== "ENOENT") throw err;
        })
      )
    );
    // clean up empty directories
    const bucketPath = this.getBucketPath(bucket);
    const parts = key.split("/");
    // the last part isn't a directory (it's embedded into the file name)
    parts.pop();
    while (
      parts.length &&
      !fs.readdirSync(path.join(bucketPath, ...parts)).length
    ) {
      await fs.rmdir(path.join(bucketPath, ...parts));
      parts.pop();
    }
  }

  async initiateUpload(bucket, key, uploadId, metadata) {
    const uploadDir = path.join(
      this.getResourcePath(bucket, undefined, "uploads"),
      uploadId
    );

    await fs.mkdirp(uploadDir);

    await Promise.all([
      fs.writeFile(path.join(uploadDir, "key"), key),
      fs.writeFile(path.join(uploadDir, "metadata"), JSON.stringify(metadata))
    ]);
  }

  async putPart(bucket, uploadId, partNumber, content) {
    const partPath = path.join(
      this.getResourcePath(bucket, undefined, "uploads"),
      uploadId,
      partNumber.toString()
    );

    await fs.mkdirp(path.dirname(partPath));

    const [size, md5] = await new Promise((resolve, reject) => {
      const writeStream = fs.createWriteStream(partPath);
      const md5Context = crypto.createHash("md5");
      let totalLength = 0;

      content
        .on("data", chunk => {
          writeStream.write(chunk, "binary");
          md5Context.update(chunk, "binary");
          totalLength += chunk.length;
        })
        .on("error", reject)
        .on("end", () => {
          writeStream.end();
          resolve([totalLength, md5Context.digest("hex")]);
        });
    });
    await fs.writeFile(`${partPath}.md5`, md5);
    return { size, md5 };
  }

  async putObjectMultipart(bucket, uploadId, parts) {
    const uploadDir = path.join(
      this.getResourcePath(bucket, undefined, "uploads"),
      uploadId
    );
    const [key, metadata] = await Promise.all([
      fs.readFile(path.join(uploadDir, "key")).then(data => data.toString()),
      fs.readFile(path.join(uploadDir, "metadata")).then(JSON.parse)
    ]);
    const partStreams = sortBy(parts, part => part.number).map(part =>
      fs.createReadStream(path.join(uploadDir, part.number.toString()))
    );
    const object = new S3Object(
      bucket,
      key,
      concatStreams(partStreams),
      metadata
    );
    const result = await this.putObject(object);
    await fs.remove(uploadDir);
    return result;
  }

  async getSubresource(bucket, key, resourceType) {
    const resourcePath = this.getResourcePath(
      bucket,
      key,
      `${resourceType}.xml`
    );

    const Model = getConfigModel(resourceType);

    try {
      const data = await fs.readFile(resourcePath);
      return new Model(data.toString());
    } catch (err) {
      if (err.code === "ENOENT") return null;
      throw err;
    }
  }

  async putSubresource(bucket, key, resource) {
    const resourcePath = this.getResourcePath(
      bucket,
      key,
      `${resource.type}.xml`
    );
    await fs.writeFile(resourcePath, resource.toXML(2));
  }

  async deleteSubresource(bucket, key, resourceType) {
    const resourcePath = this.getResourcePath(
      bucket,
      key,
      `${resourceType}.xml`
    );
    try {
      await fs.unlink(resourcePath);
    } catch (err) {
      if (err.code !== "ENOENT") throw err;
    }
  }
}

module.exports = FilesystemStore;
<commit_msg>Add md5 extension to md5 file<commit_after>"use strict";

const crypto = require("crypto");
const fs = require("fs-extra");
const { pick, pickBy, sortBy, zip } = require("lodash");
const path = require("path");
const { format } = require("util");

const { getConfigModel } = require("../models/config");
const S3Bucket = require("../models/bucket");
const S3Object = require("../models/object");
const { concatStreams, walk } = require("../utils");

const S3RVER_SUFFIX = "%s._S3rver_%s";

class FilesystemStore {
  static decodeKeyPath(keyPath) {
    return process.platform === "win32"
      ? keyPath.replace(/&../g, ent =>
          Buffer.from(ent.slice(1), "hex").toString()
        )
      : keyPath;
  }

  static encodeKeyPath(key) {
    return process.platform === "win32"
      ? key.replace(
          /[<>:"\\|?*]/g,
          ch => "&" + Buffer.from(ch, "utf8").toString("hex")
        )
      : key;
  }

  constructor(rootDirectory) {
    this.rootDirectory = rootDirectory;
  }

  // helpers

  getBucketPath(bucketName) {
    return path.join(this.rootDirectory, bucketName);
  }

  getResourcePath(bucket, key = "", resource) {
    const parts = FilesystemStore.encodeKeyPath(key).split("/");
    const suffix = format(S3RVER_SUFFIX, parts.pop(), resource);
    return path.join(this.rootDirectory, bucket, ...parts, suffix);
  }

  async getMetadata(bucket, key) {
    const objectPath = this.getResourcePath(bucket, key, "object");
    const metadataPath = this.getResourcePath(bucket, key, "metadata.json");

    // this is expected to throw if the object doesn't exist
    const stat = await fs.stat(objectPath);
    const [storedMetadata, md5] = await Promise.all([
      fs
        .readFile(metadataPath)
        .then(JSON.parse)
        .catch(err => {
          if (err.code === "ENOENT") return undefined;
          throw err;
        }),
      fs
        .readFile(`${objectPath}.md5`)
        .then(md5 => md5.toString())
        .catch(async err => {
          if (err.code !== "ENOENT") throw err;
          // create the md5 file if it doesn't already exist
          const md5 = await new Promise((resolve, reject) => {
            const stream = fs.createReadStream(objectPath);
            const md5Context = crypto.createHash("md5");
            stream.on("error", reject);
            stream.on("data", chunk => md5Context.update(chunk, "utf8"));
            stream.on("end", () => resolve(md5Context.digest("hex")));
          });
          await fs.writeFile(`${objectPath}.md5`, md5);
          return md5;
        })
    ]);

    return {
      ...storedMetadata,
      etag: JSON.stringify(md5),
      "last-modified": stat.mtime.toUTCString(),
      "content-length": stat.size
    };
  }

  async putMetadata(bucket, key, metadata, md5) {
    const metadataPath = this.getResourcePath(bucket, key, "metadata.json");
    const md5Path = this.getResourcePath(bucket, key, "object.md5");

    const json = {
      ...pick(metadata, S3Object.ALLOWED_METADATA),
      ...pickBy(metadata, (value, key) => key.startsWith("x-amz-meta-"))
    };

    if (md5) await fs.writeFile(md5Path, md5);
    await fs.writeFile(metadataPath, JSON.stringify(json, null, 2));
  }

  // store implementation

  reset() {
    const list = fs.readdirSync(this.rootDirectory);
    for (const file of list) {
      fs.removeSync(path.join(this.rootDirectory, file));
    }
  }

  async listBuckets() {
    const list = await fs.readdir(this.rootDirectory);
    const buckets = await Promise.all(
      list.map(filename => this.getBucket(filename))
    );
    return buckets.filter(Boolean);
  }

  async getBucket(bucket) {
    const bucketPath = this.getBucketPath(bucket);
    try {
      const stat = await fs.stat(bucketPath);
      if (!stat.isDirectory()) return null;
      return new S3Bucket(bucket, stat.birthtime);
    } catch (err) {
      if (err.code === "ENOENT") return null;
      throw err;
    }
  }

  async putBucket(bucket) {
    const bucketPath = this.getBucketPath(bucket);
    await fs.mkdirp(bucketPath, 0o0755);
    return this.getBucket(bucket);
  }

  async deleteBucket(bucket) {
    return fs.remove(this.getBucketPath(bucket));
  }

  async listObjects(bucket, options) {
    const {
      delimiter = "",
      startAfter = "",
      prefix = "",
      maxKeys = Infinity
    } = options;
    const bucketPath = this.getBucketPath(bucket);

    const delimiterEnc = FilesystemStore.encodeKeyPath(delimiter);
    const startAfterPath = [
      bucketPath,
      FilesystemStore.encodeKeyPath(startAfter)
    ].join("/");
    const prefixPath = [bucketPath, FilesystemStore.encodeKeyPath(prefix)].join(
      "/"
    );

    const it = walk(bucketPath, dirPath => {
      // avoid directories occurring before the startAfter parameter
      if (dirPath < startAfterPath && startAfterPath.indexOf(dirPath) === -1) {
        return false;
      }
      if (dirPath.startsWith(prefixPath)) {
        if (
          delimiterEnc &&
          dirPath.slice(prefixPath.length).indexOf(delimiterEnc) !== -1
        ) {
          // avoid directories occurring beneath a common prefix
          return false;
        }
      } else if (!prefixPath.startsWith(dirPath)) {
        // avoid directories that do not intersect with any part of the prefix
        return false;
      }
      return true;
    });

    const commonPrefixes = new Set();
    const objectSuffix = format(S3RVER_SUFFIX, "", "object");
    const keys = [];
    let isTruncated = false;
    for (const keyPath of it) {
      if (!keyPath.endsWith(objectSuffix)) {
        continue;
      }

      const key = FilesystemStore.decodeKeyPath(
        keyPath.slice(bucketPath.length + 1, -objectSuffix.length)
      );

      if (key <= startAfter || !key.startsWith(prefix)) {
        continue;
      }

      if (delimiter) {
        const idx = key.slice(prefix.length).indexOf(delimiter);
        if (idx !== -1) {
          // add to common prefixes before filtering this key out
          commonPrefixes.add(key.slice(0, prefix.length + idx + 1));
          continue;
        }
      }

      if (keys.length < maxKeys) {
        keys.push(key);
      } else {
        isTruncated = true;
        break;
      }
    }

    const metadataArr = await Promise.all(
      keys.map(key =>
        this.getMetadata(bucket, key).catch(err => {
          if (err.code === "ENOENT") return undefined;
          throw err;
        })
      )
    );

    return {
      objects: zip(keys, metadataArr)
        .filter(([, metadata]) => metadata !== undefined)
        .map(([key, metadata]) => new S3Object(bucket, key, null, metadata)),
      commonPrefixes: [...commonPrefixes].sort(),
      isTruncated
    };
  }

  async existsObject(bucket, key) {
    const objectPath = this.getResourcePath(bucket, key, "object");
    try {
      await fs.stat(objectPath);
      return true;
    } catch (err) {
      if (err.code === "ENOENT") return false;
      throw err;
    }
  }

  async getObject(bucket, key, options) {
    try {
      const metadata = await this.getMetadata(bucket, key);
      const lastByte = Number(metadata["content-length"]) - 1;
      const range = {
        start: (options && options.start) || 0,
        end: Math.min((options && options.end) || Infinity, lastByte)
      };

      if (range.start < 0 || Math.min(range.end, lastByte) < range.start) {
        // the range is not satisfiable
        const object = new S3Object(bucket, key, null, metadata);
        if (options && (options.start || options.end)) {
          object.range = range;
        }
        return object;
      }

      const content = await new Promise((resolve, reject) => {
        const stream = fs
          .createReadStream(this.getResourcePath(bucket, key, "object"), range)
          .on("error", reject)
          .on("open", () => resolve(stream));
      });
      const object = new S3Object(bucket, key, content, metadata);
      if (options && (options.start || options.end)) {
        object.range = range;
      }
      return object;
    } catch (err) {
      if (err.code === "ENOENT") return null;
      throw err;
    }
  }

  async putObject(object) {
    const objectPath = this.getResourcePath(
      object.bucket,
      object.key,
      "object"
    );

    await fs.mkdirp(path.dirname(objectPath));

    const [size, md5] = await new Promise((resolve, reject) => {
      const writeStream = fs.createWriteStream(objectPath);
      const md5Context = crypto.createHash("md5");
      let totalLength = 0;

      object.content
        .on("data", chunk => {
          writeStream.write(chunk, "binary");
          md5Context.update(chunk, "binary");
          totalLength += chunk.length;
        })
        .on("error", reject)
        .on("end", () => {
          writeStream.end();
          resolve([totalLength, md5Context.digest("hex")]);
        });
    });
    await this.putMetadata(object.bucket, object.key, object.metadata, md5);
    return { size, md5 };
  }

  async copyObject(
    srcBucket,
    srcKey,
    destBucket,
    destKey,
    replacementMetadata
  ) {
    const srcObjectPath = this.getResourcePath(srcBucket, srcKey, "object");
    const destObjectPath = this.getResourcePath(destBucket, destKey, "object");

    if (srcObjectPath !== destObjectPath) {
      await fs.mkdirp(path.dirname(destObjectPath));
      await fs.copy(srcObjectPath, destObjectPath);
    }

    if (replacementMetadata) {
      await this.putMetadata(destBucket, destKey, replacementMetadata);
      return this.getMetadata(destBucket, destKey);
    } else {
      if (srcObjectPath !== destObjectPath) {
        await fs.copy(
          this.getResourcePath(srcBucket, srcKey, "metadata.json"),
          this.getResourcePath(destBucket, destKey, "metadata.json")
        );
      }
      return this.getMetadata(destBucket, destKey);
    }
  }

  async deleteObject(bucket, key) {
    await Promise.all(
      [
        this.getResourcePath(bucket, key, "object"),
        this.getResourcePath(bucket, key, "object.md5"),
        this.getResourcePath(bucket, key, "metadata.json")
      ].map(filePath =>
        fs.unlink(filePath).catch(err => {
          if (err.code !== "ENOENT") throw err;
        })
      )
    );
    // clean up empty directories
    const bucketPath = this.getBucketPath(bucket);
    const parts = key.split("/");
    // the last part isn't a directory (it's embedded into the file name)
    parts.pop();
    while (
      parts.length &&
      !fs.readdirSync(path.join(bucketPath, ...parts)).length
    ) {
      await fs.rmdir(path.join(bucketPath, ...parts));
      parts.pop();
    }
  }

  async initiateUpload(bucket, key, uploadId, metadata) {
    const uploadDir = path.join(
      this.getResourcePath(bucket, undefined, "uploads"),
      uploadId
    );

    await fs.mkdirp(uploadDir);

    await Promise.all([
      fs.writeFile(path.join(uploadDir, "key"), key),
      fs.writeFile(path.join(uploadDir, "metadata"), JSON.stringify(metadata))
    ]);
  }

  async putPart(bucket, uploadId, partNumber, content) {
    const partPath = path.join(
      this.getResourcePath(bucket, undefined, "uploads"),
      uploadId,
      partNumber.toString()
    );

    await fs.mkdirp(path.dirname(partPath));

    const [size, md5] = await new Promise((resolve, reject) => {
      const writeStream = fs.createWriteStream(partPath);
      const md5Context = crypto.createHash("md5");
      let totalLength = 0;

      content
        .on("data", chunk => {
          writeStream.write(chunk, "binary");
          md5Context.update(chunk, "binary");
          totalLength += chunk.length;
        })
        .on("error", reject)
        .on("end", () => {
          writeStream.end();
          resolve([totalLength, md5Context.digest("hex")]);
        });
    });
    await fs.writeFile(`${partPath}.md5`, md5);
    return { size, md5 };
  }

  async putObjectMultipart(bucket, uploadId, parts) {
    const uploadDir = path.join(
      this.getResourcePath(bucket, undefined, "uploads"),
      uploadId
    );
    const [key, metadata] = await Promise.all([
      fs.readFile(path.join(uploadDir, "key")).then(data => data.toString()),
      fs.readFile(path.join(uploadDir, "metadata")).then(JSON.parse)
    ]);
    const partStreams = sortBy(parts, part => part.number).map(part =>
      fs.createReadStream(path.join(uploadDir, part.number.toString()))
    );
    const object = new S3Object(
      bucket,
      key,
      concatStreams(partStreams),
      metadata
    );
    const result = await this.putObject(object);
    await fs.remove(uploadDir);
    return result;
  }

  async getSubresource(bucket, key, resourceType) {
    const resourcePath = this.getResourcePath(
      bucket,
      key,
      `${resourceType}.xml`
    );

    const Model = getConfigModel(resourceType);

    try {
      const data = await fs.readFile(resourcePath);
      return new Model(data.toString());
    } catch (err) {
      if (err.code === "ENOENT") return null;
      throw err;
    }
  }

  async putSubresource(bucket, key, resource) {
    const resourcePath = this.getResourcePath(
      bucket,
      key,
      `${resource.type}.xml`
    );
    await fs.writeFile(resourcePath, resource.toXML(2));
  }

  async deleteSubresource(bucket, key, resourceType) {
    const resourcePath = this.getResourcePath(
      bucket,
      key,
      `${resourceType}.xml`
    );
    try {
      await fs.unlink(resourcePath);
    } catch (err) {
      if (err.code !== "ENOENT") throw err;
    }
  }
}

module.exports = FilesystemStore;
<|endoftext|>


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

/**
 * External dependencies
 */
import { render, screen } from '@testing-library/react';

/**
 * Internal dependencies
 */
import WebStoriesModal from '../Modal';

jest.mock('@wordpress/data', () => ({
  select: jest.fn(() => ({
    getCurrentView: () => 'grid',
  })),
  createReduxStore: jest.fn(),
  combineReducers: jest.fn(),
  register: jest.fn(),
}));

jest.mock('@wordpress/i18n', () => ({
  // eslint-disable-next-line no-unused-vars
  __: (val, domain) => val,
}));

jest.mock('../../utils/globals', () => ({
  webStoriesData: {
    views: [],
  },
}));

jest.mock('../../utils', () => ({
  updateViewSettings: jest.fn((settings) => settings),
}));

describe('Modal', () => {
  it('renders modal with toggles', () => {
    const props = {
      modalOpen: true,
      settings: {
        archive_link: {
          show: true,
        },
        title: {},
        excerpt: {},
        author: {},
        date: {},
      },
      prepareShortCode: jest.fn(),
    };

    render(<WebStoriesModal {...props} />);
    expect(screen.queryByText(/Archive Link Label/i)).toBeInTheDocument();
  });
});
<commit_msg>TinyMCE Button: Improve unit tests (#8503)<commit_after>/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { render, screen } from '@testing-library/react';

/**
 * Internal dependencies
 */
import WebStoriesModal from '../Modal';

jest.mock('@wordpress/data', () => ({
  select: jest.fn(() => ({
    getCurrentView: () => 'grid',
  })),
  createReduxStore: jest.fn(),
  combineReducers: jest.fn(),
  register: jest.fn(),
}));

jest.mock('@wordpress/i18n', () => ({
  ...jest.requireActual('@wordpress/i18n'),
  // eslint-disable-next-line no-unused-vars
  __: (val, domain) => val,
}));

jest.mock('../../utils/globals', () => ({
  webStoriesData: {
    views: [],
  },
}));

jest.mock('../../utils', () => ({
  updateViewSettings: jest.fn((settings) => settings),
}));

describe('Modal', () => {
  it('renders modal with toggles', () => {
    const props = {
      modalOpen: true,
      settings: {
        archive_link: {
          show: true,
        },
        title: {},
        excerpt: {},
        author: {},
        date: {},
      },
      prepareShortCode: jest.fn(),
    };

    render(<WebStoriesModal {...props} />);
    expect(screen.queryByText(/Archive Link Label/i)).toBeInTheDocument();
  });
});
<|endoftext|>