class SendStream: def __init__(self, data : bytes = b''): self.byteArray = QByteArray(data) self.buffer = QBuffer(self.byteArray) self.buffer.open(QBuffer.ReadOnly if data else QBuffer.WriteOnly) self.stream = QDataStream(self.buffer) def atEnd(self): return self.stream.atEnd() def data(self): return self.byteArray.data() def clear(self): self.buffer.reset() self.byteArray.resize(0) def send(self, tcpSocket : QTcpSocket): writeInt(tcpSocket, len(self.byteArray)) tcpSocket.write(self.byteArray) def writeBytes(self, data: bytes): self.buffer.write(b'\xFE') self.stream.writeBytes(data) def readBytes(self): controlByte = self.buffer.read(1) if controlByte != b'\xFE': raise Exception('Failed to read bytes from stream') return self.stream.readBytes() def writeString(self, s: str): self.buffer.write(b'\xFD') self.stream.writeBytes(s.encode()) def readString(self): controlByte = self.buffer.read(1) if controlByte != b'\xFD': raise Exception('Failed to read string from stream.') return self.stream.readBytes().decode() def writeInt(self, i: int): self.buffer.write(b'\xFC') self.stream.writeInt(i) def readInt(self): controlByte = self.buffer.read(1) if controlByte != b'\xFC': raise Exception('Failed to read int from stream.') return self.stream.readInt() def writeBool(self, b: bool): self.buffer.write(b'\xFB' if b else b'\xFA') def readBool(self): b = self.buffer.read(1) if b == b'\xFB': return True elif b == b'\xFA': return False else: raise Exception('Failed to read bool from stream')
class HelpSchemeReply(QIODevice): """ Class implementing a reply for a requested qthelp: page. @signal closed emitted to signal that the web engine has read the data see: https://fossies.org/linux/eric6/eric/WebBrowser/Network/QtHelpSchemeHandler.py All credits for this class go to: Detlev Offenbach, the developer of The Eric Python IDE(https://eric-ide.python-projects.org) """ closed = pyqtSignal() def __init__(self, job, engine, parent=None): """ Constructor @param job reference to the URL request @type QWebEngineUrlRequestJob @param engine reference to the help engine @type QHelpEngine @param parent reference to the parent object @type QObject """ super(HelpSchemeReply, self).__init__(parent) url = job.requestUrl() strUrl = url.toString() self.__buffer = QBuffer() # For some reason the url to load maybe wrong (passed from web engine) # though the css file and the references inside should work that way. # One possible problem might be that the css is loaded at the same # level as the html, thus a path inside the css like # (../images/foo.png) might cd out of the virtual folder if not engine.findFile(url).isValid(): if strUrl.startswith(QtHelp_DOCROOT): newUrl = job.requestUrl() if not newUrl.path().startswith("/qdoc/"): newUrl.setPath("/qdoc" + newUrl.path()) url = newUrl strUrl = url.toString() self.__mimeType = mimetypes.guess_type(strUrl)[0] if self.__mimeType is None: # do our own (limited) guessing self.__mimeType = self.__mimeFromUrl(url) if engine.findFile(url).isValid(): data = engine.fileData(url) else: data = QByteArray(self.tr( """<html>""" """<head><title>Error 404...</title></head>""" """<body><div align="center"><br><br>""" """<h1>The page could not be found</h1><br>""" """<h3>'{0}'</h3></div></body>""" """</html>""").format(strUrl) .encode("utf-8")) self.__buffer.setData(data) self.__buffer.open(QIODevice.ReadOnly) self.open(QIODevice.ReadOnly) def bytesAvailable(self): """ Public method to get the number of available bytes. @return number of available bytes @rtype int """ return self.__buffer.bytesAvailable() def readData(self, maxlen): """ Public method to retrieve data from the reply object. @param maxlen maximum number of bytes to read (integer) @return string containing the data (bytes) """ return self.__buffer.read(maxlen) def close(self): """ Public method used to cloase the reply. """ super(HelpSchemeReply, self).close() self.closed.emit() def __mimeFromUrl(self, url): """ Private method to guess the mime type given an URL. @param url URL to guess the mime type from (QUrl) @return mime type for the given URL (string) """ path = url.path() ext = os.path.splitext(path)[1].lower() if ext in ExtensionMap: return ExtensionMap[ext] else: return "application/octet-stream" def mimeType(self): """ Public method to get the reply mime type. @return mime type of the reply @rtype bytes """ return self.__mimeType.encode("utf-8")
class EricSchemeReply(QIODevice): """ Class implementing a reply for a requested eric: page. @signal closed emitted to signal that the web engine has read the data """ closed = pyqtSignal() _speedDialPage = "" def __init__(self, job, parent=None): """ Constructor @param job reference to the URL request @type QWebEngineUrlRequestJob @param parent reference to the parent object @type QObject """ super(EricSchemeReply, self).__init__(parent) self.__loaded = False self.__job = job self.__mutex = QMutex() self.__pageName = self.__job.requestUrl().path() self.__buffer = QBuffer() self.__loadPage() def __loadPage(self): """ Private method to load the requested page. """ if self.__loaded: return lock = QMutexLocker(self.__mutex) if self.__pageName == "adblock": contents = self.__adBlockPage() elif self.__pageName in ["home", "start", "startpage"]: contents = self.__startPage() elif self.__pageName == "speeddial": contents = self.__speedDialPage() else: contents = "" self.__buffer.setData(contents.encode("utf-8")) self.__buffer.open(QIODevice.ReadOnly) self.open(QIODevice.ReadOnly) lock.unlock() self.readyRead.emit() self.__loaded = True def bytesAvailable(self): """ Public method to get the number of available bytes. @return number of available bytes @rtype int """ lock = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ return self.__buffer.bytesAvailable() def readData(self, maxlen): """ Public method to retrieve data from the reply object. @param maxlen maximum number of bytes to read (integer) @return string containing the data (bytes) """ lock = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ return self.__buffer.read(maxlen) def close(self): """ Public method used to cloase the reply. """ super(EricSchemeReply, self).close() self.closed.emit() def __adBlockPage(self): """ Private method to build the AdBlock page. @return built AdBlock page @rtype str """ query = QUrlQuery(self.__job.requestUrl()) rule = query.queryItemValue("rule") subscription = query.queryItemValue("subscription") title = self.tr("Content blocked by AdBlock Plus") message = self.tr("Blocked by rule: <i>{0} ({1})</i>").format( rule, subscription) page = readAllFileContents(":/html/adblockPage.html") page = page.replace("@FAVICON@", "qrc:icons/adBlockPlus16.png") page = page.replace("@IMAGE@", "qrc:icons/adBlockPlus64.png") page = page.replace("@TITLE@", title) page = page.replace("@MESSAGE@", message) return page def __startPage(self): """ Private method to build the Start page. @return built Start page @rtype str """ page = readAllFileContents(":/html/startPage.html") page = page.replace("@FAVICON@", "qrc:icons/ericWeb16.png") page = page.replace("@IMAGE@", "qrc:icons/ericWeb32.png") page = page.replace("@TITLE@", self.tr("Welcome to eric6 Web Browser!")) page = page.replace("@ERIC_LINK@", self.tr("About eric6")) page = page.replace("@HEADER_TITLE@", self.tr("eric6 Web Browser")) page = page.replace("@SUBMIT@", self.tr("Search!")) if qApp.isLeftToRight(): ltr = "LTR" else: ltr = "RTL" page = page.replace("@QT_LAYOUT_DIRECTION@", ltr) return page def __speedDialPage(self): """ Private method to create the Speeddial page. @return prepared speeddial page (QByteArray) """ if not self._speedDialPage: page = readAllFileContents(":/html/speeddialPage.html") page = page.replace("@FAVICON@", "qrc:icons/ericWeb16.png") page = page.replace("@IMG_PLUS@", "qrc:icons/plus.png") page = page.replace("@IMG_CLOSE@", "qrc:icons/close.png") page = page.replace("@IMG_EDIT@", "qrc:icons/edit.png") page = page.replace("@IMG_RELOAD@", "qrc:icons/reload.png") page = page.replace("@IMG_SETTINGS@", "qrc:icons/setting.png") page = page.replace("@LOADING-IMG@", "qrc:icons/loading.gif") page = page.replace("@BOX-BORDER@", "qrc:icons/box-border-small.png") page = page.replace("@JQUERY@", "qrc:javascript/jquery.js") page = page.replace("@JQUERY-UI@", "qrc:javascript/jquery-ui.js") page = page.replace("@SITE-TITLE@", self.tr("Speed Dial")) page = page.replace("@URL@", self.tr("URL")) page = page.replace("@TITLE@", self.tr("Title")) page = page.replace("@APPLY@", self.tr("Apply")) page = page.replace("@CLOSE@", self.tr("Close")) page = page.replace("@NEW-PAGE@", self.tr("New Page")) page = page.replace("@TITLE-EDIT@", self.tr("Edit")) page = page.replace("@TITLE-REMOVE@", self.tr("Remove")) page = page.replace("@TITLE-RELOAD@", self.tr("Reload")) page = page.replace( "@TITLE-WARN@", self.tr("Are you sure to remove this" " speed dial?")) page = page.replace( "@TITLE-WARN-REL@", self.tr("Are you sure you want to reload" " all speed dials?")) page = page.replace("@TITLE-FETCHTITLE@", self.tr("Load title from page")) page = page.replace("@SETTINGS-TITLE@", self.tr("Speed Dial Settings")) page = page.replace("@ADD-TITLE@", self.tr("Add New Page")) page = page.replace("@TXT_NRROWS@", self.tr("Maximum pages in a row:")) page = page.replace("@TXT_SDSIZE@", self.tr("Change size of pages:")) page = page.replace( "@JAVASCRIPT_DISABLED@", self.tr("SpeedDial requires enabled" " JavaScript.")) self._speedDialPage = page from WebBrowser.WebBrowserWindow import WebBrowserWindow dial = WebBrowserWindow.speedDial() page = (self._speedDialPage.replace( "@INITIAL-SCRIPT@", dial.initialScript()).replace( "@ROW-PAGES@", str(dial.pagesInRow())).replace("@SD-SIZE@", str(dial.sdSize()))) return page
class HexEditChunks(object): """ Class implementing the storage backend for the hex editor. When HexEditWidget loads data, HexEditChunks access them using a QIODevice interface. When the app uses a QByteArray or Python bytearray interface, QBuffer is used to provide again a QIODevice like interface. No data will be changed, therefore HexEditChunks opens the QIODevice in QIODevice.ReadOnly mode. After every access HexEditChunks closes the QIODevice. That's why external applications can overwrite files while HexEditWidget shows them. When the the user starts to edit the data, HexEditChunks creates a local copy of a chunk of data (4 kilobytes) and notes all changes there. Parallel to that chunk, there is a second chunk, which keeps track of which bytes are changed and which are not. """ BUFFER_SIZE = 0x10000 CHUNK_SIZE = 0x1000 READ_CHUNK_MASK = 0xfffffffffffff000 def __init__(self, ioDevice=None): """ Constructor @param ioDevice io device to get the data from @type QIODevice """ self.__ioDevice = None self.__pos = 0 self.__size = 0 self.__chunks = [] if ioDevice is None: buf = QBuffer() self.setIODevice(buf) else: self.setIODevice(ioDevice) def setIODevice(self, ioDevice): """ Public method to set an io device to read the binary data from. @param ioDevice io device to get the data from @type QIODevice @return flag indicating successful operation @rtype bool """ self.__ioDevice = ioDevice ok = self.__ioDevice.open(QIODevice.ReadOnly) if ok: # open successfully self.__size = self.__ioDevice.size() self.__ioDevice.close() else: # fallback is an empty buffer self.__ioDevice = QBuffer() self.__size = 0 self.__chunks = [] self.__pos = 0 return ok def data(self, pos=0, maxSize=-1, highlighted=None): """ Public method to get data out of the chunks. @param pos position to get bytes from @type int @param maxSize maximum amount of bytes to get @type int @param highlighted reference to a byte array storing highlighting info @type bytearray @return retrieved data @rtype bytearray """ ioDelta = 0 chunkIdx = 0 chunk = HexEditChunk() buffer = bytearray() if highlighted is not None: del highlighted[:] if pos >= self.__size: return buffer if maxSize < 0: maxSize = self.__size elif (pos + maxSize) > self.__size: maxSize = self.__size - pos self.__ioDevice.open(QIODevice.ReadOnly) while maxSize > 0: chunk.absPos = sys.maxsize chunksLoopOngoing = True while chunkIdx < len(self.__chunks) and chunksLoopOngoing: # In this section, we track changes before our required data # and we take the edited data, if availible. ioDelta is a # difference counter to justify the read pointer to the # original data, if data in between was deleted or inserted. chunk = self.__chunks[chunkIdx] if chunk.absPos > pos: chunksLoopOngoing = False else: chunkIdx += 1 chunkOfs = pos - chunk.absPos if maxSize > (len(chunk.data) - chunkOfs): count = len(chunk.data) - chunkOfs ioDelta += self.CHUNK_SIZE - len(chunk.data) else: count = maxSize if count > 0: buffer += chunk.data[chunkOfs:chunkOfs + count] maxSize -= count pos += count if highlighted is not None: highlighted += chunk.dataChanged[ chunkOfs:chunkOfs + count] if maxSize > 0 and pos < chunk.absPos: # In this section, we read data from the original source. This # will only happen, when no copied data is available. if chunk.absPos - pos > maxSize: byteCount = maxSize else: byteCount = chunk.absPos - pos maxSize -= byteCount self.__ioDevice.seek(pos + ioDelta) readBuffer = bytearray(self.__ioDevice.read(byteCount)) buffer += readBuffer if highlighted is not None: highlighted += bytearray(len(readBuffer)) pos += len(readBuffer) self.__ioDevice.close() return buffer def write(self, ioDevice, pos=0, count=-1): """ Public method to write data to an io device. @param ioDevice io device to write the data to @type QIODevice @param pos position to write bytes from @type int @param count amount of bytes to write @type int @return flag indicating success @rtype bool """ if count == -1: # write all data count = self.__size ok = ioDevice.open(QIODevice.WriteOnly) if ok: idx = pos while idx < count: data = self.data(idx, self.BUFFER_SIZE) ioDevice.write(QByteArray(data)) # increment loop variable idx += self.BUFFER_SIZE ioDevice.close() return ok def setDataChanged(self, pos, dataChanged): """ Public method to set highlighting info. @param pos position to set highlighting info for @type int @param dataChanged flag indicating changed data @type bool """ if pos < 0 or pos >= self.__size: # position is out of range, do nothing return chunkIdx = self.__getChunkIndex(pos) posInChunk = pos - self.__chunks[chunkIdx].absPos self.__chunks[chunkIdx].dataChanged[posInChunk] = int(dataChanged) def dataChanged(self, pos): """ Public method to test, if some data was changed. @param pos byte position to check @type int @return flag indicating the changed state @rtype bool """ highlighted = bytearray() self.data(pos, 1, highlighted) return highlighted and bool(highlighted[0]) def indexOf(self, byteArray, start): """ Public method to search the first occurrence of some data. @param byteArray data to search for @type bytearray @param start position to start the search at @type int @return position the data was found at or -1 if nothing could be found @rtype int """ ba = bytearray(byteArray) result = -1 pos = start while pos < self.__size: buffer = self.data(pos, self.BUFFER_SIZE + len(ba) - 1) findPos = buffer.find(ba) if findPos >= 0: result = pos + findPos break # increment loop variable pos += self.BUFFER_SIZE return result def lastIndexOf(self, byteArray, start): """ Public method to search the last occurrence of some data. @param byteArray data to search for @type bytearray @param start position to start the search at @type int @return position the data was found at or -1 if nothing could be found @rtype int """ ba = bytearray(byteArray) result = -1 pos = start while pos > 0 and result < 0: sPos = pos - self.BUFFER_SIZE - len(ba) + 1 if sPos < 0: sPos = 0 buffer = self.data(sPos, pos - sPos) findPos = buffer.rfind(ba) if findPos >= 0: result = sPos + findPos break # increment loop variable pos -= self.BUFFER_SIZE return result def insert(self, pos, data): """ Public method to insert a byte. @param pos position to insert at @type int @param data byte to insert @type int (range 0 to 255) @return flag indicating success @rtype bool """ if pos < 0 or pos > self.__size: # position is out of range, do nothing return False if pos == self.__size: chunkIdx = self.__getChunkIndex(pos - 1) else: chunkIdx = self.__getChunkIndex(pos) chunk = self.__chunks[chunkIdx] posInChunk = pos - chunk.absPos chunk.data.insert(posInChunk, data) chunk.dataChanged.insert(posInChunk, 1) for idx in range(chunkIdx + 1, len(self.__chunks)): self.__chunks[idx].absPos += 1 self.__size += 1 self.__pos = pos return True def overwrite(self, pos, data): """ Public method to overwrite a byte. @param pos position to overwrite @type int @param data byte to overwrite with @type int (range 0 to 255) @return flag indicating success @rtype bool """ if pos < 0 or pos >= self.__size: # position is out of range, do nothing return False chunkIdx = self.__getChunkIndex(pos) chunk = self.__chunks[chunkIdx] posInChunk = pos - chunk.absPos chunk.data[posInChunk] = data chunk.dataChanged[posInChunk] = 1 self.__pos = pos return True def removeAt(self, pos): """ Public method to remove a byte. @param pos position to remove @type int @return flag indicating success @rtype bool """ if pos < 0 or pos >= self.__size: # position is out of range, do nothing return False chunkIdx = self.__getChunkIndex(pos) chunk = self.__chunks[chunkIdx] posInChunk = pos - chunk.absPos chunk.data.pop(posInChunk) chunk.dataChanged.pop(posInChunk) for idx in range(chunkIdx + 1, len(self.__chunks)): self.__chunks[idx].absPos -= 1 self.__size -= 1 self.__pos = pos return True def __getitem__(self, pos): """ Special method to get a byte at a position. Note: This realizes the [] get operator. @param pos position of byte to get @type int @return requested byte @rtype int (range 0 to 255) """ if pos >= self.__size: return 0 ## raise IndexError return self.data(pos, 1)[0] def pos(self): """ Public method to get the current position. @return current position @rtype int """ return self.__pos def size(self): """ Public method to get the current data size. @return current data size @rtype int """ return self.__size def __getChunkIndex(self, absPos): """ Private method to get the chunk index for a position. This method checks, if there is already a copied chunk available. If there is one, it returns its index. If there is no copied chunk available, original data will be copied into a new chunk. @param absPos absolute position of the data. @type int @return index of the chunk containing the position @rtype int """ foundIdx = -1 insertIdx = 0 ioDelta = 0 for idx in range(len(self.__chunks)): chunk = self.__chunks[idx] if (absPos >= chunk.absPos and absPos < (chunk.absPos + len(chunk.data))): foundIdx = idx break if absPos < chunk.absPos: insertIdx = idx break ioDelta += len(chunk.data) - self.CHUNK_SIZE insertIdx = idx + 1 if foundIdx == -1: newChunk = HexEditChunk() readAbsPos = absPos - ioDelta readPos = readAbsPos & self.READ_CHUNK_MASK self.__ioDevice.open(QIODevice.ReadOnly) self.__ioDevice.seek(readPos) newChunk.data = bytearray(self.__ioDevice.read(self.CHUNK_SIZE)) self.__ioDevice.close() newChunk.absPos = absPos - (readAbsPos - readPos) newChunk.dataChanged = bytearray(len(newChunk.data)) self.__chunks.insert(insertIdx, newChunk) foundIdx = insertIdx return foundIdx
class HexEditChunks(object): """ Class implementing the storage backend for the hex editor. When HexEditWidget loads data, HexEditChunks access them using a QIODevice interface. When the app uses a QByteArray or Python bytearray interface, QBuffer is used to provide again a QIODevice like interface. No data will be changed, therefore HexEditChunks opens the QIODevice in QIODevice.ReadOnly mode. After every access HexEditChunks closes the QIODevice. That's why external applications can overwrite files while HexEditWidget shows them. When the the user starts to edit the data, HexEditChunks creates a local copy of a chunk of data (4 kilobytes) and notes all changes there. Parallel to that chunk, there is a second chunk, which keeps track of which bytes are changed and which are not. """ BUFFER_SIZE = 0x10000 CHUNK_SIZE = 0x1000 READ_CHUNK_MASK = 0xfffffffffffff000 def __init__(self, ioDevice=None): """ Constructor @param ioDevice io device to get the data from @type QIODevice """ self.__ioDevice = None self.__pos = 0 self.__size = 0 self.__chunks = [] if ioDevice is None: buf = QBuffer() self.setIODevice(buf) else: self.setIODevice(ioDevice) def setIODevice(self, ioDevice): """ Public method to set an io device to read the binary data from. @param ioDevice io device to get the data from @type QIODevice @return flag indicating successful operation @rtype bool """ self.__ioDevice = ioDevice ok = self.__ioDevice.open(QIODevice.ReadOnly) if ok: # open successfully self.__size = self.__ioDevice.size() self.__ioDevice.close() else: # fallback is an empty buffer self.__ioDevice = QBuffer() self.__size = 0 self.__chunks = [] self.__pos = 0 return ok def data(self, pos=0, maxSize=-1, highlighted=None): """ Public method to get data out of the chunks. @param pos position to get bytes from @type int @param maxSize maximum amount of bytes to get @type int @param highlighted reference to a byte array storing highlighting info @type bytearray @return retrieved data @rtype bytearray """ ioDelta = 0 chunkIdx = 0 chunk = HexEditChunk() buffer = bytearray() if highlighted is not None: del highlighted[:] if pos >= self.__size: return buffer if maxSize < 0: maxSize = self.__size elif (pos + maxSize) > self.__size: maxSize = self.__size - pos self.__ioDevice.open(QIODevice.ReadOnly) while maxSize > 0: chunk.absPos = sys.maxsize chunksLoopOngoing = True while chunkIdx < len(self.__chunks) and chunksLoopOngoing: # In this section, we track changes before our required data # and we take the edited data, if availible. ioDelta is a # difference counter to justify the read pointer to the # original data, if data in between was deleted or inserted. chunk = self.__chunks[chunkIdx] if chunk.absPos > pos: chunksLoopOngoing = False else: chunkIdx += 1 chunkOfs = pos - chunk.absPos if maxSize > (len(chunk.data) - chunkOfs): count = len(chunk.data) - chunkOfs ioDelta += self.CHUNK_SIZE - len(chunk.data) else: count = maxSize if count > 0: buffer += chunk.data[chunkOfs:chunkOfs + count] maxSize -= count pos += count if highlighted is not None: highlighted += \ chunk.dataChanged[chunkOfs:chunkOfs + count] if maxSize > 0 and pos < chunk.absPos: # In this section, we read data from the original source. This # will only happen, when no copied data is available. if chunk.absPos - pos > maxSize: byteCount = maxSize else: byteCount = chunk.absPos - pos maxSize -= byteCount self.__ioDevice.seek(pos + ioDelta) readBuffer = bytearray(self.__ioDevice.read(byteCount)) buffer += readBuffer if highlighted is not None: highlighted += bytearray(len(readBuffer)) pos += len(readBuffer) self.__ioDevice.close() return buffer def write(self, ioDevice, pos=0, count=-1): """ Public method to write data to an io device. @param ioDevice io device to write the data to @type QIODevice @param pos position to write bytes from @type int @param count amount of bytes to write @type int @return flag indicating success @rtype bool """ if count == -1: # write all data count = self.__size ok = ioDevice.open(QIODevice.WriteOnly) if ok: idx = pos while idx < count: data = self.data(idx, self.BUFFER_SIZE) ioDevice.write(QByteArray(data)) # increment loop variable idx += self.BUFFER_SIZE ioDevice.close() return ok def setDataChanged(self, pos, dataChanged): """ Public method to set highlighting info. @param pos position to set highlighting info for @type int @param dataChanged flag indicating changed data @type bool """ if pos < 0 or pos >= self.__size: # position is out of range, do nothing return chunkIdx = self.__getChunkIndex(pos) posInChunk = pos - self.__chunks[chunkIdx].absPos self.__chunks[chunkIdx].dataChanged[posInChunk] = int(dataChanged) def dataChanged(self, pos): """ Public method to test, if some data was changed. @param pos byte position to check @type int @return flag indicating the changed state @rtype bool """ highlighted = bytearray() self.data(pos, 1, highlighted) return highlighted and bool(highlighted[0]) def indexOf(self, byteArray, start): """ Public method to search the first occurrence of some data. @param byteArray data to search for @type bytearray @param start position to start the search at @type int @return position the data was found at or -1 if nothing could be found @rtype int """ ba = bytearray(byteArray) result = -1 pos = start while pos < self.__size: buffer = self.data(pos, self.BUFFER_SIZE + len(ba) - 1) findPos = buffer.find(ba) if findPos >= 0: result = pos + findPos break # increment loop variable pos += self.BUFFER_SIZE return result def lastIndexOf(self, byteArray, start): """ Public method to search the last occurrence of some data. @param byteArray data to search for @type bytearray @param start position to start the search at @type int @return position the data was found at or -1 if nothing could be found @rtype int """ ba = bytearray(byteArray) result = -1 pos = start while pos > 0 and result < 0: sPos = pos - self.BUFFER_SIZE - len(ba) + 1 if sPos < 0: sPos = 0 buffer = self.data(sPos, pos - sPos) findPos = buffer.rfind(ba) if findPos >= 0: result = sPos + findPos break # increment loop variable pos -= self.BUFFER_SIZE return result def insert(self, pos, data): """ Public method to insert a byte. @param pos position to insert at @type int @param data byte to insert @type int (range 0 to 255) @return flag indicating success @rtype bool """ if pos < 0 or pos > self.__size: # position is out of range, do nothing return False if pos == self.__size: chunkIdx = self.__getChunkIndex(pos - 1) else: chunkIdx = self.__getChunkIndex(pos) chunk = self.__chunks[chunkIdx] posInChunk = pos - chunk.absPos chunk.data.insert(posInChunk, data) chunk.dataChanged.insert(posInChunk, 1) for idx in range(chunkIdx + 1, len(self.__chunks)): self.__chunks[idx].absPos += 1 self.__size += 1 self.__pos = pos return True def overwrite(self, pos, data): """ Public method to overwrite a byte. @param pos position to overwrite @type int @param data byte to overwrite with @type int (range 0 to 255) @return flag indicating success @rtype bool """ if pos < 0 or pos >= self.__size: # position is out of range, do nothing return False chunkIdx = self.__getChunkIndex(pos) chunk = self.__chunks[chunkIdx] posInChunk = pos - chunk.absPos chunk.data[posInChunk] = data chunk.dataChanged[posInChunk] = 1 self.__pos = pos return True def removeAt(self, pos): """ Public method to remove a byte. @param pos position to remove @type int @return flag indicating success @rtype bool """ if pos < 0 or pos >= self.__size: # position is out of range, do nothing return chunkIdx = self.__getChunkIndex(pos) chunk = self.__chunks[chunkIdx] posInChunk = pos - chunk.absPos chunk.data.pop(posInChunk) chunk.dataChanged.pop(posInChunk) for idx in range(chunkIdx + 1, len(self.__chunks)): self.__chunks[idx].absPos -= 1 self.__size -= 1 self.__pos = pos return True def __getitem__(self, pos): """ Special method to get a byte at a position. Note: This realizes the [] get operator. @param pos position of byte to get @type int @return requested byte @rtype int (range 0 to 255) """ if pos >= self.__size: return 0 ## raise IndexError return self.data(pos, 1)[0] def pos(self): """ Public method to get the current position. @return current position @rtype int """ return self.__pos def size(self): """ Public method to get the current data size. @return current data size @rtype int """ return self.__size def __getChunkIndex(self, absPos): """ Private method to get the chunk index for a position. This method checks, if there is already a copied chunk available. If there is one, it returns its index. If there is no copied chunk available, original data will be copied into a new chunk. @param absPos absolute position of the data. @type int @return index of the chunk containing the position @rtype int """ foundIdx = -1 insertIdx = 0 ioDelta = 0 for idx in range(len(self.__chunks)): chunk = self.__chunks[idx] if absPos >= chunk.absPos and \ absPos < (chunk.absPos + len(chunk.data)): foundIdx = idx break if absPos < chunk.absPos: insertIdx = idx break ioDelta += len(chunk.data) - self.CHUNK_SIZE insertIdx = idx + 1 if foundIdx == -1: newChunk = HexEditChunk() readAbsPos = absPos - ioDelta readPos = readAbsPos & self.READ_CHUNK_MASK self.__ioDevice.open(QIODevice.ReadOnly) self.__ioDevice.seek(readPos) newChunk.data = bytearray(self.__ioDevice.read(self.CHUNK_SIZE)) self.__ioDevice.close() newChunk.absPos = absPos - (readAbsPos - readPos) newChunk.dataChanged = bytearray(len(newChunk.data)) self.__chunks.insert(insertIdx, newChunk) foundIdx = insertIdx return foundIdx
class HelpSchemeReply(QIODevice): """ Class implementing a reply for a requested qthelp: page. The Qt signal *closed* is emitted to signal that the web engine has read the data. see: https://fossies.org/linux/eric6/eric/WebBrowser/Network/QtHelpSchemeHandler.py All credits for this class go to: Detlev Offenbach, the developer of The Eric Python IDE(https://eric-ide.python-projects.org) """ closed = pyqtSignal() def __init__(self, job, engine, parent=None): """ Constructor :param job: reference to the URL request :type job: QWebEngineUrlRequestJob :param engine: reference to the help engine :type engine: QHelpEngine :param parent: reference to the parent object :type parent: QObject """ super(HelpSchemeReply, self).__init__(parent) url = job.requestUrl() strUrl = url.toString() self.__buffer = QBuffer() # For some reason the url to load maybe wrong (passed from web engine) # though the css file and the references inside should work that way. # One possible problem might be that the css is loaded at the same # level as the html, thus a path inside the css like # (../images/foo.png) might cd out of the virtual folder if not engine.findFile(url).isValid(): if strUrl.startswith(QtHelp_DOCROOT): newUrl = job.requestUrl() if not newUrl.path().startswith("/qdoc/"): newUrl.setPath("/qdoc" + newUrl.path()) url = newUrl strUrl = url.toString() self.__mimeType = mimetypes.guess_type(strUrl)[0] if self.__mimeType is None: # do our own (limited) guessing self.__mimeType = self.__mimeFromUrl(url) if engine.findFile(url).isValid(): data = engine.fileData(url) else: data = QByteArray(self.tr( """<html>""" """<head><title>Error 404...</title></head>""" """<body><div align="center"><br><br>""" """<h1>The page could not be found</h1><br>""" """<h3>'{0}'</h3></div></body>""" """</html>""").format(strUrl) .encode("utf-8")) self.__buffer.setData(data) self.__buffer.open(QIODevice.ReadOnly) self.open(QIODevice.ReadOnly) def bytesAvailable(self): """ Public method to get the number of available bytes. :returns: number of available bytes :rtype: int """ return self.__buffer.bytesAvailable() def readData(self, maxlen): """ Public method to retrieve data from the reply object. :param maxlen: maximum number of bytes to read (integer) :returns: string containing the data (bytes) """ return self.__buffer.read(maxlen) def close(self): """ Public method used to close the reply. """ super(HelpSchemeReply, self).close() self.closed.emit() def __mimeFromUrl(self, url): """ Private method to guess the mime type given an URL. :param url: URL to guess the mime type from (QUrl) :returns: mime type for the given URL (string) """ path = url.path() ext = os.path.splitext(path)[1].lower() if ext in ExtensionMap: return ExtensionMap[ext] else: return "application/octet-stream" def mimeType(self): """ Public method to get the reply mime type. :returns: mime type of the reply :rtype: bytes """ return self.__mimeType.encode("utf-8")
class Chunks(QObject): def __init__(self, parent: QObject = None, device: QIODevice = None): super().__init__(parent) self.device = QBuffer(self) if device is None else device self.chunks: [Chunk] = [] self.position = 0 self.size = 0 self.setIODevice(self.device) def setIODevice(self, device: QIODevice) -> bool: self.device = device status = self.device.open(QIODevice.ReadOnly) if status: self.size = self.device.size() self.device.close() else: # Fallback is an empty buffer self.size = 0 self.device = QBuffer(self) self.chunks.clear() self.position = 0 return status def data(self, position: int, maxSize: int = -1, highlighted: QByteArray = None) -> QByteArray: delta = 0 chunkIdx = 0 chunk = Chunk() buffer = QByteArray() if highlighted: highlighted.clear() if position >= self.size: return buffer if maxSize < 0: maxSize = self.size elif (position + maxSize) > self.size: maxSize = self.size - position self.device.open(QIODevice.ReadOnly) while maxSize > 0: chunk.absPos = sys.maxsize chunksLoopOngoing = True while chunkIdx < len(self.chunks) and chunksLoopOngoing: # In this section, we track changes before our required data and # we take the editdet data, if availible. ioDelta is a difference # counter to justify the read pointer to the original data, if # data in between was deleted or inserted. chunk = self.chunks[chunkIdx] if chunk.absPos > position: chunksLoopOngoing = False else: count = 0 chunkIdx += 1 chunkOfs = position - chunk.absPos if maxSize > chunk.data.size() - chunkOfs: count = chunk.data.size() - chunkOfs delta += CHUNK_SIZE - chunk.data.size() else: count = maxSize if count > 0: buffer += chunk.data.mid(chunkOfs, count) maxSize -= count position += count if highlighted: highlighted += chunk.dataChanged.mid( chunkOfs, count) if maxSize > 0 and position < chunk.absPos: byteCount = 0 if (chunk.absPos - position) > maxSize: byteCount = maxSize else: byteCount = chunk.absPos - position maxSize -= byteCount self.device.seek(position + delta) readBuffer = self.device.read(byteCount) buffer += readBuffer if highlighted: highlighted += QByteArray(readBuffer.size(), NORMAL) position += len(readBuffer) self.device.close() return buffer def write(self, device: QIODevice, position: int = 0, count: int = -1) -> bool: if count == -1: count = self.size status = device.open(QIODevice.WriteOnly) if status: for idx in range(position, count, BUFFER_SIZE): array = self.data(idx, BUFFER_SIZE) device.write(array) device.close() return status def setDataChanged(self, position: int, dataChanged: bool) -> None: pass def dataChanged(self, position: int) -> bool: pass def indexOf(self, array: QByteArray, _from: int) -> int: pass def lastIndexOf(self, array: QByteArray, _from: int) -> int: pass def insert(self, position: int, character: str) -> bool: pass def overwrite(self, position: int, character: str) -> bool: pass def removeAt(self, position: int) -> bool: pass def getPosition(self) -> int: pass def getSize(self) -> int: pass def __getitem__(self, item): pass