forked from mirrors/gecko-dev
		
	 e930b89c34
			
		
	
	
		e930b89c34
		
	
	
	
	
		
			
			***
Bug 1514594: Part 3a - Change ChromeUtils.import to return an exports object; not pollute global. r=mccr8
This changes the behavior of ChromeUtils.import() to return an exports object,
rather than a module global, in all cases except when `null` is passed as a
second argument, and changes the default behavior not to pollute the global
scope with the module's exports. Thus, the following code written for the old
model:
  ChromeUtils.import("resource://gre/modules/Services.jsm");
is approximately the same as the following, in the new model:
  var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
Since the two behaviors are mutually incompatible, this patch will land with a
scripted rewrite to update all existing callers to use the new model rather
than the old.
***
Bug 1514594: Part 3b - Mass rewrite all JS code to use the new ChromeUtils.import API. rs=Gijs
This was done using the followng script:
https://bitbucket.org/kmaglione/m-c-rewrites/src/tip/processors/cu-import-exports.jsm
***
Bug 1514594: Part 3c - Update ESLint plugin for ChromeUtils.import API changes. r=Standard8
Differential Revision: https://phabricator.services.mozilla.com/D16747
***
Bug 1514594: Part 3d - Remove/fix hundreds of duplicate imports from sync tests. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16748
***
Bug 1514594: Part 3e - Remove no-op ChromeUtils.import() calls. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16749
***
Bug 1514594: Part 3f.1 - Cleanup various test corner cases after mass rewrite. r=Gijs
***
Bug 1514594: Part 3f.2 - Cleanup various non-test corner cases after mass rewrite. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16750
--HG--
extra : rebase_source : 359574ee3064c90f33bf36c2ebe3159a24cc8895
extra : histedit_source : b93c8f42808b1599f9122d7842d2c0b3e656a594%2C64a3a4e3359dc889e2ab2b49461bab9e27fc10a7
		
	
			
		
			
				
	
	
		
			608 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			608 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| var EXPORTED_SYMBOLS = ["ESEDBReader"]; /* exported ESEDBReader */
 | |
| 
 | |
| const {ctypes} = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
 | |
| const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| XPCOMUtils.defineLazyGetter(this, "log", () => {
 | |
|   let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
 | |
|   let consoleOptions = {
 | |
|     maxLogLevelPref: "browser.esedbreader.loglevel",
 | |
|     prefix: "ESEDBReader",
 | |
|   };
 | |
|   return new ConsoleAPI(consoleOptions);
 | |
| });
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 | |
| 
 | |
| // We have a globally unique identifier for ESE instances. A new one
 | |
| // is used for each different database opened.
 | |
| let gESEInstanceCounter = 0;
 | |
| 
 | |
| // We limit the length of strings that we read from databases.
 | |
| const MAX_STR_LENGTH = 64 * 1024;
 | |
| 
 | |
| // Kernel-related types:
 | |
| const KERNEL = {};
 | |
| KERNEL.FILETIME = new ctypes.StructType("FILETIME", [
 | |
|   {dwLowDateTime: ctypes.uint32_t},
 | |
|   {dwHighDateTime: ctypes.uint32_t},
 | |
| ]);
 | |
| KERNEL.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [
 | |
|   {wYear: ctypes.uint16_t},
 | |
|   {wMonth: ctypes.uint16_t},
 | |
|   {wDayOfWeek: ctypes.uint16_t},
 | |
|   {wDay: ctypes.uint16_t},
 | |
|   {wHour: ctypes.uint16_t},
 | |
|   {wMinute: ctypes.uint16_t},
 | |
|   {wSecond: ctypes.uint16_t},
 | |
|   {wMilliseconds: ctypes.uint16_t},
 | |
| ]);
 | |
| 
 | |
| // DB column types, cribbed from the ESE header
 | |
| var COLUMN_TYPES = {
 | |
|   JET_coltypBit:           1, /* True, False, or NULL */
 | |
|   JET_coltypUnsignedByte:  2, /* 1-byte integer, unsigned */
 | |
|   JET_coltypShort:         3, /* 2-byte integer, signed */
 | |
|   JET_coltypLong:          4, /* 4-byte integer, signed */
 | |
|   JET_coltypCurrency:      5, /* 8 byte integer, signed */
 | |
|   JET_coltypIEEESingle:    6, /* 4-byte IEEE single precision */
 | |
|   JET_coltypIEEEDouble:    7, /* 8-byte IEEE double precision */
 | |
|   JET_coltypDateTime:      8, /* Integral date, fractional time */
 | |
|   JET_coltypBinary:        9, /* Binary data, < 255 bytes */
 | |
|   JET_coltypText:         10, /* ANSI text, case insensitive, < 255 bytes */
 | |
|   JET_coltypLongBinary:   11, /* Binary data, long value */
 | |
|   JET_coltypLongText:     12, /* ANSI text, long value */
 | |
| 
 | |
|   JET_coltypUnsignedLong: 14, /* 4-byte unsigned integer */
 | |
|   JET_coltypLongLong:     15, /* 8-byte signed integer */
 | |
|   JET_coltypGUID:         16, /* 16-byte globally unique identifier */
 | |
| };
 | |
| 
 | |
| // Not very efficient, but only used for error messages
 | |
| function getColTypeName(numericValue) {
 | |
|   return Object.keys(COLUMN_TYPES).find(t => COLUMN_TYPES[t] == numericValue) || "unknown";
 | |
| }
 | |
| 
 | |
| // All type constants and method wrappers go on this object:
 | |
| const ESE = {};
 | |
| ESE.JET_ERR = ctypes.long;
 | |
| ESE.JET_PCWSTR = ctypes.char16_t.ptr;
 | |
| // The ESE header calls this JET_API_PTR, but because it isn't ever used as a
 | |
| // pointer and because OS.File code implies that the name you give a type
 | |
| // matters, I opted for a different name.
 | |
| // Note that this is defined differently on 32 vs. 64-bit in the header.
 | |
| ESE.JET_API_ITEM = ctypes.voidptr_t.size == 4 ? ctypes.unsigned_long : ctypes.uint64_t;
 | |
| ESE.JET_INSTANCE = ESE.JET_API_ITEM;
 | |
| ESE.JET_SESID = ESE.JET_API_ITEM;
 | |
| ESE.JET_TABLEID = ESE.JET_API_ITEM;
 | |
| ESE.JET_COLUMNID = ctypes.unsigned_long;
 | |
| ESE.JET_GRBIT = ctypes.unsigned_long;
 | |
| ESE.JET_COLTYP = ctypes.unsigned_long;
 | |
| ESE.JET_DBID = ctypes.unsigned_long;
 | |
| 
 | |
| ESE.JET_COLUMNDEF = new ctypes.StructType("JET_COLUMNDEF", [
 | |
|   {"cbStruct": ctypes.unsigned_long },
 | |
|   {"columnid": ESE.JET_COLUMNID },
 | |
|   {"coltyp": ESE.JET_COLTYP },
 | |
|   {"wCountry": ctypes.unsigned_short }, // sepcifies the country/region for the column definition
 | |
|   {"langid": ctypes.unsigned_short },
 | |
|   {"cp": ctypes.unsigned_short },
 | |
|   {"wCollate": ctypes.unsigned_short }, /* Must be 0 */
 | |
|   {"cbMax": ctypes.unsigned_long },
 | |
|   {"grbit": ESE.JET_GRBIT },
 | |
| ]);
 | |
| 
 | |
| // Track open databases
 | |
| let gOpenDBs = new Map();
 | |
| 
 | |
| // Track open libraries
 | |
| let gLibs = {};
 | |
| this.ESE = ESE; // Required for tests.
 | |
| this.KERNEL = KERNEL; // ditto
 | |
| this.gLibs = gLibs; // ditto
 | |
| 
 | |
| function convertESEError(errorCode) {
 | |
|   switch (errorCode) {
 | |
|     case -1213 /* JET_errPageSizeMismatch */:
 | |
|     case -1002 /* JET_errInvalidName*/:
 | |
|     case -1507 /* JET_errColumnNotFound */:
 | |
|       // The DB format has changed and we haven't updated this migration code:
 | |
|       return "The database format has changed, error code: " + errorCode;
 | |
|     case -1032 /* JET_errFileAccessDenied */:
 | |
|     case -1207 /* JET_errDatabaseLocked */:
 | |
|     case -1302 /* JET_errTableLocked */:
 | |
|       return "The database or table is locked, error code: " + errorCode;
 | |
|     case -1305 /* JET_errObjectNotFound */:
 | |
|       return "The table/object was not found.";
 | |
|     case -1809 /* JET_errPermissionDenied*/:
 | |
|     case -1907 /* JET_errAccessDenied */:
 | |
|       return "Access or permission denied, error code: " + errorCode;
 | |
|     case -1044 /* JET_errInvalidFilename */:
 | |
|       return "Invalid file name";
 | |
|     case -1811 /* JET_errFileNotFound */:
 | |
|       return "File not found";
 | |
|     case -550 /* JET_errDatabaseDirtyShutdown */:
 | |
|       return "Database in dirty shutdown state (without the requisite logs?)";
 | |
|     case -514 /* JET_errBadLogVersion */:
 | |
|       return "Database log version does not match the version of ESE in use.";
 | |
|     default:
 | |
|       return "Unknown error: " + errorCode;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function handleESEError(method, methodName, shouldThrow = true, errorLog = true) {
 | |
|   return function() {
 | |
|     let rv;
 | |
|     try {
 | |
|       rv = method.apply(null, arguments);
 | |
|     } catch (ex) {
 | |
|       log.error("Error calling into ctypes method", methodName, ex);
 | |
|       throw ex;
 | |
|     }
 | |
|     let resultCode = parseInt(rv.toString(10), 10);
 | |
|     if (resultCode < 0) {
 | |
|       if (errorLog) {
 | |
|         log.error("Got error " + resultCode + " calling " + methodName);
 | |
|       }
 | |
|       if (shouldThrow) {
 | |
|         throw new Error(convertESEError(rv));
 | |
|       }
 | |
|     } else if (resultCode > 0 && errorLog) {
 | |
|       log.warn("Got warning " + resultCode + " calling " + methodName);
 | |
|     }
 | |
|     return resultCode;
 | |
|   };
 | |
| }
 | |
| 
 | |
| 
 | |
| function declareESEFunction(methodName, ...args) {
 | |
|   let declaration = ["Jet" + methodName, ctypes.winapi_abi, ESE.JET_ERR].concat(args);
 | |
|   let ctypeMethod = gLibs.ese.declare.apply(gLibs.ese, declaration);
 | |
|   ESE[methodName] = handleESEError(ctypeMethod, methodName);
 | |
|   ESE["FailSafe" + methodName] = handleESEError(ctypeMethod, methodName, false);
 | |
|   ESE["Manual" + methodName] = handleESEError(ctypeMethod, methodName, false, false);
 | |
| }
 | |
| 
 | |
| function declareESEFunctions() {
 | |
|   declareESEFunction("GetDatabaseFileInfoW", ESE.JET_PCWSTR, ctypes.voidptr_t,
 | |
|                      ctypes.unsigned_long, ctypes.unsigned_long);
 | |
| 
 | |
|   declareESEFunction("GetSystemParameterW", ESE.JET_INSTANCE, ESE.JET_SESID,
 | |
|                      ctypes.unsigned_long, ESE.JET_API_ITEM.ptr,
 | |
|                      ESE.JET_PCWSTR, ctypes.unsigned_long);
 | |
|   declareESEFunction("SetSystemParameterW", ESE.JET_INSTANCE.ptr,
 | |
|                      ESE.JET_SESID, ctypes.unsigned_long, ESE.JET_API_ITEM,
 | |
|                      ESE.JET_PCWSTR);
 | |
|   declareESEFunction("CreateInstanceW", ESE.JET_INSTANCE.ptr, ESE.JET_PCWSTR);
 | |
|   declareESEFunction("Init", ESE.JET_INSTANCE.ptr);
 | |
| 
 | |
|   declareESEFunction("BeginSessionW", ESE.JET_INSTANCE, ESE.JET_SESID.ptr,
 | |
|                      ESE.JET_PCWSTR, ESE.JET_PCWSTR);
 | |
|   declareESEFunction("AttachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
 | |
|                      ESE.JET_GRBIT);
 | |
|   declareESEFunction("DetachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR);
 | |
|   declareESEFunction("OpenDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
 | |
|                      ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT);
 | |
|   declareESEFunction("OpenTableW", ESE.JET_SESID, ESE.JET_DBID, ESE.JET_PCWSTR,
 | |
|                      ctypes.voidptr_t, ctypes.unsigned_long, ESE.JET_GRBIT,
 | |
|                      ESE.JET_TABLEID.ptr);
 | |
| 
 | |
|   declareESEFunction("GetColumnInfoW", ESE.JET_SESID, ESE.JET_DBID,
 | |
|                      ESE.JET_PCWSTR, ESE.JET_PCWSTR, ctypes.voidptr_t,
 | |
|                      ctypes.unsigned_long, ctypes.unsigned_long);
 | |
| 
 | |
|   declareESEFunction("Move", ESE.JET_SESID, ESE.JET_TABLEID, ctypes.long,
 | |
|                      ESE.JET_GRBIT);
 | |
| 
 | |
|   declareESEFunction("RetrieveColumn", ESE.JET_SESID, ESE.JET_TABLEID,
 | |
|                      ESE.JET_COLUMNID, ctypes.voidptr_t, ctypes.unsigned_long,
 | |
|                      ctypes.unsigned_long.ptr, ESE.JET_GRBIT, ctypes.voidptr_t);
 | |
| 
 | |
|   declareESEFunction("CloseTable", ESE.JET_SESID, ESE.JET_TABLEID);
 | |
|   declareESEFunction("CloseDatabase", ESE.JET_SESID, ESE.JET_DBID,
 | |
|                      ESE.JET_GRBIT);
 | |
| 
 | |
|   declareESEFunction("EndSession", ESE.JET_SESID, ESE.JET_GRBIT);
 | |
| 
 | |
|   declareESEFunction("Term", ESE.JET_INSTANCE);
 | |
| }
 | |
| 
 | |
| function unloadLibraries() {
 | |
|   log.debug("Unloading");
 | |
|   if (gOpenDBs.size) {
 | |
|     log.error("Shouldn't unload libraries before DBs are closed!");
 | |
|     for (let db of gOpenDBs.values()) {
 | |
|       db._close();
 | |
|     }
 | |
|   }
 | |
|   for (let k of Object.keys(ESE)) {
 | |
|     delete ESE[k];
 | |
|   }
 | |
|   gLibs.ese.close();
 | |
|   gLibs.kernel.close();
 | |
|   delete gLibs.ese;
 | |
|   delete gLibs.kernel;
 | |
| }
 | |
| 
 | |
| function loadLibraries() {
 | |
|   Services.obs.addObserver(unloadLibraries, "xpcom-shutdown");
 | |
|   gLibs.ese = ctypes.open("esent.dll");
 | |
|   gLibs.kernel = ctypes.open("kernel32.dll");
 | |
|   KERNEL.FileTimeToSystemTime = gLibs.kernel.declare("FileTimeToSystemTime",
 | |
|     ctypes.winapi_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr);
 | |
| 
 | |
|   declareESEFunctions();
 | |
| }
 | |
| 
 | |
| function ESEDB(rootPath, dbPath, logPath) {
 | |
|   log.info("Created db");
 | |
|   this.rootPath = rootPath;
 | |
|   this.dbPath = dbPath;
 | |
|   this.logPath = logPath;
 | |
|   this._references = 0;
 | |
|   this._init();
 | |
| }
 | |
| 
 | |
| ESEDB.prototype = {
 | |
|   rootPath: null,
 | |
|   dbPath: null,
 | |
|   logPath: null,
 | |
|   _opened: false,
 | |
|   _attached: false,
 | |
|   _sessionCreated: false,
 | |
|   _instanceCreated: false,
 | |
|   _dbId: null,
 | |
|   _sessionId: null,
 | |
|   _instanceId: null,
 | |
| 
 | |
|   _init() {
 | |
|     if (!gLibs.ese) {
 | |
|       loadLibraries();
 | |
|     }
 | |
|     this.incrementReferenceCounter();
 | |
|     this._internalOpen();
 | |
|   },
 | |
| 
 | |
|   _internalOpen() {
 | |
|     try {
 | |
|       let dbinfo = new ctypes.unsigned_long();
 | |
|       ESE.GetDatabaseFileInfoW(this.dbPath, dbinfo.address(),
 | |
|                                ctypes.unsigned_long.size, 17);
 | |
| 
 | |
|       let pageSize = ctypes.UInt64.lo(dbinfo.value);
 | |
|       ESE.SetSystemParameterW(null, 0, 64 /* JET_paramDatabasePageSize*/,
 | |
|                               pageSize, null);
 | |
| 
 | |
|       this._instanceId = new ESE.JET_INSTANCE();
 | |
|       ESE.CreateInstanceW(this._instanceId.address(),
 | |
|                           "firefox-dbreader-" + (gESEInstanceCounter++));
 | |
|       this._instanceCreated = true;
 | |
| 
 | |
|       ESE.SetSystemParameterW(this._instanceId.address(), 0,
 | |
|                               0 /* JET_paramSystemPath*/, 0, this.rootPath);
 | |
|       ESE.SetSystemParameterW(this._instanceId.address(), 0,
 | |
|                               1 /* JET_paramTempPath */, 0, this.rootPath);
 | |
|       ESE.SetSystemParameterW(this._instanceId.address(), 0,
 | |
|                               2 /* JET_paramLogFilePath*/, 0, this.logPath);
 | |
| 
 | |
|       // Shouldn't try to call JetTerm if the following call fails.
 | |
|       this._instanceCreated = false;
 | |
|       ESE.Init(this._instanceId.address());
 | |
|       this._instanceCreated = true;
 | |
|       this._sessionId = new ESE.JET_SESID();
 | |
|       ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null,
 | |
|                         null);
 | |
|       this._sessionCreated = true;
 | |
| 
 | |
|       const JET_bitDbReadOnly = 1;
 | |
|       ESE.AttachDatabaseW(this._sessionId, this.dbPath, JET_bitDbReadOnly);
 | |
|       this._attached = true;
 | |
|       this._dbId = new ESE.JET_DBID();
 | |
|       ESE.OpenDatabaseW(this._sessionId, this.dbPath, null,
 | |
|                         this._dbId.address(), JET_bitDbReadOnly);
 | |
|       this._opened = true;
 | |
|     } catch (ex) {
 | |
|       try {
 | |
|         this._close();
 | |
|       } catch (innerException) {
 | |
|         Cu.reportError(innerException);
 | |
|       }
 | |
|       // Make sure caller knows we failed.
 | |
|       throw ex;
 | |
|     }
 | |
|     gOpenDBs.set(this.dbPath, this);
 | |
|   },
 | |
| 
 | |
|   checkForColumn(tableName, columnName) {
 | |
|     if (!this._opened) {
 | |
|       throw new Error("The database was closed!");
 | |
|     }
 | |
| 
 | |
|     let columnInfo;
 | |
|     try {
 | |
|       columnInfo = this._getColumnInfo(tableName, [{name: columnName}]);
 | |
|     } catch (ex) {
 | |
|       return null;
 | |
|     }
 | |
|     return columnInfo[0];
 | |
|   },
 | |
| 
 | |
|   tableExists(tableName) {
 | |
|     if (!this._opened) {
 | |
|       throw new Error("The database was closed!");
 | |
|     }
 | |
| 
 | |
|     let tableId = new ESE.JET_TABLEID();
 | |
|     let rv = ESE.ManualOpenTableW(this._sessionId, this._dbId, tableName, null,
 | |
|                                   0, 4 /* JET_bitTableReadOnly */,
 | |
|                                   tableId.address());
 | |
|     if (rv == -1305 /* JET_errObjectNotFound */) {
 | |
|       return false;
 | |
|     }
 | |
|     if (rv < 0) {
 | |
|       log.error("Got error " + rv + " calling OpenTableW");
 | |
|       throw new Error(convertESEError(rv));
 | |
|     }
 | |
| 
 | |
|     if (rv > 0) {
 | |
|       log.error("Got warning " + rv + " calling OpenTableW");
 | |
|     }
 | |
|     ESE.FailSafeCloseTable(this._sessionId, tableId);
 | |
|     return true;
 | |
|   },
 | |
| 
 | |
|   * tableItems(tableName, columns) {
 | |
|     if (!this._opened) {
 | |
|       throw new Error("The database was closed!");
 | |
|     }
 | |
| 
 | |
|     let tableOpened = false;
 | |
|     let tableId;
 | |
|     try {
 | |
|       tableId = this._openTable(tableName);
 | |
|       tableOpened = true;
 | |
| 
 | |
|       let columnInfo = this._getColumnInfo(tableName, columns);
 | |
| 
 | |
|       let rv = ESE.ManualMove(this._sessionId, tableId,
 | |
|                               -2147483648 /* JET_MoveFirst */, 0);
 | |
|       if (rv == -1603 /* JET_errNoCurrentRecord */) {
 | |
|         // There are no rows in the table.
 | |
|         this._closeTable(tableId);
 | |
|         return;
 | |
|       }
 | |
|       if (rv != 0) {
 | |
|         throw new Error(convertESEError(rv));
 | |
|       }
 | |
| 
 | |
|       do {
 | |
|         let rowContents = {};
 | |
|         for (let column of columnInfo) {
 | |
|           let [buffer, bufferSize] = this._getBufferForColumn(column);
 | |
|           // We handle errors manually so we accurately deal with NULL values.
 | |
|           let err = ESE.ManualRetrieveColumn(this._sessionId, tableId,
 | |
|                                              column.id, buffer.address(),
 | |
|                                              bufferSize, null, 0, null);
 | |
|           rowContents[column.name] = this._convertResult(column, buffer, err);
 | |
|         }
 | |
|         yield rowContents;
 | |
|       } while (ESE.ManualMove(this._sessionId, tableId, 1 /* JET_MoveNext */, 0) === 0);
 | |
|     } catch (ex) {
 | |
|       if (tableOpened) {
 | |
|         this._closeTable(tableId);
 | |
|       }
 | |
|       throw ex;
 | |
|     }
 | |
|     this._closeTable(tableId);
 | |
|   },
 | |
| 
 | |
|   _openTable(tableName) {
 | |
|     let tableId = new ESE.JET_TABLEID();
 | |
|     ESE.OpenTableW(this._sessionId, this._dbId, tableName, null,
 | |
|                    0, 4 /* JET_bitTableReadOnly */, tableId.address());
 | |
|     return tableId;
 | |
|   },
 | |
| 
 | |
|   _getBufferForColumn(column) {
 | |
|     let buffer;
 | |
|     if (column.type == "string") {
 | |
|       let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
 | |
|       // size on the column is in bytes, 2 bytes to a wchar, so:
 | |
|       let charCount = column.dbSize >> 1;
 | |
|       buffer = new wchar_tArray(charCount);
 | |
|     } else if (column.type == "boolean") {
 | |
|       buffer = new ctypes.uint8_t();
 | |
|     } else if (column.type == "date") {
 | |
|       buffer = new KERNEL.FILETIME();
 | |
|     } else if (column.type == "guid") {
 | |
|       let byteArray = ctypes.ArrayType(ctypes.uint8_t);
 | |
|       buffer = new byteArray(column.dbSize);
 | |
|     } else {
 | |
|       throw new Error("Unknown type " + column.type);
 | |
|     }
 | |
|     return [buffer, buffer.constructor.size];
 | |
|   },
 | |
| 
 | |
|   _convertResult(column, buffer, err) {
 | |
|     if (err != 0) {
 | |
|       if (err == 1004) {
 | |
|         // Deal with null values:
 | |
|         buffer = null;
 | |
|       } else {
 | |
|         Cu.reportError("Unexpected JET error: " + err + "; retrieving value for column " + column.name);
 | |
|         throw new Error(convertESEError(err));
 | |
|       }
 | |
|     }
 | |
|     if (column.type == "string") {
 | |
|       return buffer ? buffer.readString() : "";
 | |
|     }
 | |
|     if (column.type == "boolean") {
 | |
|       return buffer ? (buffer.value == 255) : false;
 | |
|     }
 | |
|     if (column.type == "guid") {
 | |
|       if (buffer.length != 16) {
 | |
|         Cu.reportError("Buffer size for guid field " + column.id + " should have been 16!");
 | |
|         return "";
 | |
|       }
 | |
|       let rv = "{";
 | |
|       for (let i = 0; i < 16; i++) {
 | |
|         if (i == 4 || i == 6 || i == 8 || i == 10) {
 | |
|           rv += "-";
 | |
|         }
 | |
|         let byteValue = buffer.addressOfElement(i).contents;
 | |
|         // Ensure there's a leading 0
 | |
|         rv += ("0" + byteValue.toString(16)).substr(-2);
 | |
|       }
 | |
|       return rv + "}";
 | |
|     }
 | |
|     if (column.type == "date") {
 | |
|       if (!buffer) {
 | |
|         return null;
 | |
|       }
 | |
|       let systemTime = new KERNEL.SYSTEMTIME();
 | |
|       let result = KERNEL.FileTimeToSystemTime(buffer.address(), systemTime.address());
 | |
|       if (result == 0) {
 | |
|         throw new Error(ctypes.winLastError);
 | |
|       }
 | |
| 
 | |
|       // System time is in UTC, so we use Date.UTC to get milliseconds from epoch,
 | |
|       // then divide by 1000 to get seconds, and round down:
 | |
|       return new Date(Date.UTC(systemTime.wYear,
 | |
|                                  systemTime.wMonth - 1,
 | |
|                                  systemTime.wDay,
 | |
|                                  systemTime.wHour,
 | |
|                                  systemTime.wMinute,
 | |
|                                  systemTime.wSecond,
 | |
|                                  systemTime.wMilliseconds));
 | |
|     }
 | |
|     return undefined;
 | |
|   },
 | |
| 
 | |
|   _getColumnInfo(tableName, columns) {
 | |
|     let rv = [];
 | |
|     for (let column of columns) {
 | |
|       let columnInfoFromDB = new ESE.JET_COLUMNDEF();
 | |
|       ESE.GetColumnInfoW(this._sessionId, this._dbId, tableName, column.name,
 | |
|                          columnInfoFromDB.address(), ESE.JET_COLUMNDEF.size, 0 /* JET_ColInfo */);
 | |
|       let dbType = parseInt(columnInfoFromDB.coltyp.toString(10), 10);
 | |
|       let dbSize = parseInt(columnInfoFromDB.cbMax.toString(10), 10);
 | |
|       if (column.type == "string") {
 | |
|         if (dbType != COLUMN_TYPES.JET_coltypLongText &&
 | |
|             dbType != COLUMN_TYPES.JET_coltypText) {
 | |
|           throw new Error("Invalid column type for column " + column.name +
 | |
|                           "; expected text type, got type " + getColTypeName(dbType));
 | |
|         }
 | |
|         if (dbSize > MAX_STR_LENGTH) {
 | |
|           throw new Error("Column " + column.name + " has more than 64k data in it. This API is not designed to handle data that large.");
 | |
|         }
 | |
|       } else if (column.type == "boolean") {
 | |
|         if (dbType != COLUMN_TYPES.JET_coltypBit) {
 | |
|           throw new Error("Invalid column type for column " + column.name +
 | |
|                           "; expected bit type, got type " + getColTypeName(dbType));
 | |
|         }
 | |
|       } else if (column.type == "date") {
 | |
|         if (dbType != COLUMN_TYPES.JET_coltypLongLong) {
 | |
|           throw new Error("Invalid column type for column " + column.name +
 | |
|                           "; expected long long type, got type " + getColTypeName(dbType));
 | |
|         }
 | |
|       } else if (column.type == "guid") {
 | |
|         if (dbType != COLUMN_TYPES.JET_coltypGUID) {
 | |
|           throw new Error("Invalid column type for column " + column.name +
 | |
|                           "; expected guid type, got type " + getColTypeName(dbType));
 | |
|         }
 | |
|       } else if (column.type) {
 | |
|         throw new Error("Unknown column type " + column.type + " requested for column " +
 | |
|                         column.name + ", don't know what to do.");
 | |
|       }
 | |
| 
 | |
|       rv.push({name: column.name, id: columnInfoFromDB.columnid, type: column.type, dbSize, dbType});
 | |
|     }
 | |
|     return rv;
 | |
|   },
 | |
| 
 | |
|   _closeTable(tableId) {
 | |
|     ESE.FailSafeCloseTable(this._sessionId, tableId);
 | |
|   },
 | |
| 
 | |
|   _close() {
 | |
|     this._internalClose();
 | |
|     gOpenDBs.delete(this.dbPath);
 | |
|   },
 | |
| 
 | |
|   _internalClose() {
 | |
|     if (this._opened) {
 | |
|       log.debug("close db");
 | |
|       ESE.FailSafeCloseDatabase(this._sessionId, this._dbId, 0);
 | |
|       log.debug("finished close db");
 | |
|       this._opened = false;
 | |
|     }
 | |
|     if (this._attached) {
 | |
|       log.debug("detach db");
 | |
|       ESE.FailSafeDetachDatabaseW(this._sessionId, this.dbPath);
 | |
|       this._attached = false;
 | |
|     }
 | |
|     if (this._sessionCreated) {
 | |
|       log.debug("end session");
 | |
|       ESE.FailSafeEndSession(this._sessionId, 0);
 | |
|       this._sessionCreated = false;
 | |
|     }
 | |
|     if (this._instanceCreated) {
 | |
|       log.debug("term");
 | |
|       ESE.FailSafeTerm(this._instanceId);
 | |
|       this._instanceCreated = false;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   incrementReferenceCounter() {
 | |
|     this._references++;
 | |
|   },
 | |
| 
 | |
|   decrementReferenceCounter() {
 | |
|     this._references--;
 | |
|     if (this._references <= 0) {
 | |
|       this._close();
 | |
|     }
 | |
|   },
 | |
| };
 | |
| 
 | |
| let ESEDBReader = {
 | |
|   openDB(rootDir, dbFile, logDir) {
 | |
|     let dbFilePath = dbFile.path;
 | |
|     if (gOpenDBs.has(dbFilePath)) {
 | |
|       let db = gOpenDBs.get(dbFilePath);
 | |
|       db.incrementReferenceCounter();
 | |
|       return db;
 | |
|     }
 | |
|     // ESE is really picky about the trailing slashes according to the docs,
 | |
|     // so we do as we're told and ensure those are there:
 | |
|     return new ESEDB(rootDir.path + "\\", dbFilePath, logDir.path + "\\");
 | |
|   },
 | |
| 
 | |
|   async dbLocked(dbFile) {
 | |
|     let options = {winShare: OS.Constants.Win.FILE_SHARE_READ};
 | |
|     let locked = true;
 | |
|     await OS.File.open(dbFile.path, {read: true}, options).then(fileHandle => {
 | |
|       locked = false;
 | |
|       // Return the close promise so we wait for the file to be closed again.
 | |
|       // Otherwise the file might still be kept open by this handle by the time
 | |
|       // that we try to use the ESE APIs to access it.
 | |
|       return fileHandle.close();
 | |
|     }, () => {
 | |
|       Cu.reportError("ESE DB at " + dbFile.path + " is locked.");
 | |
|     });
 | |
|     return locked;
 | |
|   },
 | |
| 
 | |
|   closeDB(db) {
 | |
|     db.decrementReferenceCounter();
 | |
|   },
 | |
| 
 | |
|   COLUMN_TYPES,
 | |
| };
 | |
| 
 |