src/eric7/HexEdit/HexEditChunks.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
14 14
15 class HexEditChunk: 15 class HexEditChunk:
16 """ 16 """
17 Class implementing a container for the data chunks. 17 Class implementing a container for the data chunks.
18 """ 18 """
19
19 def __init__(self): 20 def __init__(self):
20 """ 21 """
21 Constructor 22 Constructor
22 """ 23 """
23 self.data = bytearray() 24 self.data = bytearray()
26 27
27 28
28 class HexEditChunks: 29 class HexEditChunks:
29 """ 30 """
30 Class implementing the storage backend for the hex editor. 31 Class implementing the storage backend for the hex editor.
31 32
32 When HexEditWidget loads data, HexEditChunks access them using a QIODevice 33 When HexEditWidget loads data, HexEditChunks access them using a QIODevice
33 interface. When the app uses a QByteArray or Python bytearray interface, 34 interface. When the app uses a QByteArray or Python bytearray interface,
34 QBuffer is used to provide again a QIODevice like interface. No data will 35 QBuffer is used to provide again a QIODevice like interface. No data will
35 be changed, therefore HexEditChunks opens the QIODevice in 36 be changed, therefore HexEditChunks opens the QIODevice in
36 QIODevice.OpenModeFlag.ReadOnly mode. After every access HexEditChunks 37 QIODevice.OpenModeFlag.ReadOnly mode. After every access HexEditChunks
40 When the the user starts to edit the data, HexEditChunks creates a local 41 When the the user starts to edit the data, HexEditChunks creates a local
41 copy of a chunk of data (4 kilobytes) and notes all changes there. Parallel 42 copy of a chunk of data (4 kilobytes) and notes all changes there. Parallel
42 to that chunk, there is a second chunk, which keeps track of which bytes 43 to that chunk, there is a second chunk, which keeps track of which bytes
43 are changed and which are not. 44 are changed and which are not.
44 """ 45 """
46
45 BUFFER_SIZE = 0x10000 47 BUFFER_SIZE = 0x10000
46 CHUNK_SIZE = 0x1000 48 CHUNK_SIZE = 0x1000
47 READ_CHUNK_MASK = 0xfffffffffffff000 49 READ_CHUNK_MASK = 0xFFFFFFFFFFFFF000
48 50
49 def __init__(self, ioDevice=None): 51 def __init__(self, ioDevice=None):
50 """ 52 """
51 Constructor 53 Constructor
52 54
53 @param ioDevice io device to get the data from 55 @param ioDevice io device to get the data from
54 @type QIODevice 56 @type QIODevice
55 """ 57 """
56 self.__ioDevice = None 58 self.__ioDevice = None
57 self.__pos = 0 59 self.__pos = 0
58 self.__size = 0 60 self.__size = 0
59 self.__chunks = [] 61 self.__chunks = []
60 62
61 if ioDevice is None: 63 if ioDevice is None:
62 buf = QBuffer() 64 buf = QBuffer()
63 self.setIODevice(buf) 65 self.setIODevice(buf)
64 else: 66 else:
65 self.setIODevice(ioDevice) 67 self.setIODevice(ioDevice)
66 68
67 def setIODevice(self, ioDevice): 69 def setIODevice(self, ioDevice):
68 """ 70 """
69 Public method to set an io device to read the binary data from. 71 Public method to set an io device to read the binary data from.
70 72
71 @param ioDevice io device to get the data from 73 @param ioDevice io device to get the data from
72 @type QIODevice 74 @type QIODevice
73 @return flag indicating successful operation 75 @return flag indicating successful operation
74 @rtype bool 76 @rtype bool
75 """ 77 """
81 self.__ioDevice.close() 83 self.__ioDevice.close()
82 else: 84 else:
83 # fallback is an empty buffer 85 # fallback is an empty buffer
84 self.__ioDevice = QBuffer() 86 self.__ioDevice = QBuffer()
85 self.__size = 0 87 self.__size = 0
86 88
87 self.__chunks = [] 89 self.__chunks = []
88 self.__pos = 0 90 self.__pos = 0
89 91
90 return ok 92 return ok
91 93
92 def data(self, pos=0, maxSize=-1, highlighted=None): 94 def data(self, pos=0, maxSize=-1, highlighted=None):
93 """ 95 """
94 Public method to get data out of the chunks. 96 Public method to get data out of the chunks.
95 97
96 @param pos position to get bytes from 98 @param pos position to get bytes from
97 @type int 99 @type int
98 @param maxSize maximum amount of bytes to get 100 @param maxSize maximum amount of bytes to get
99 @type int 101 @type int
100 @param highlighted reference to a byte array storing highlighting info 102 @param highlighted reference to a byte array storing highlighting info
102 @return retrieved data 104 @return retrieved data
103 @rtype bytearray 105 @rtype bytearray
104 """ 106 """
105 ioDelta = 0 107 ioDelta = 0
106 chunkIdx = 0 108 chunkIdx = 0
107 109
108 chunk = HexEditChunk() 110 chunk = HexEditChunk()
109 buffer = bytearray() 111 buffer = bytearray()
110 112
111 if highlighted is not None: 113 if highlighted is not None:
112 del highlighted[:] 114 del highlighted[:]
113 115
114 if pos >= self.__size: 116 if pos >= self.__size:
115 return buffer 117 return buffer
116 118
117 if maxSize < 0: 119 if maxSize < 0:
118 maxSize = self.__size 120 maxSize = self.__size
119 elif (pos + maxSize) > self.__size: 121 elif (pos + maxSize) > self.__size:
120 maxSize = self.__size - pos 122 maxSize = self.__size - pos
121 123
122 self.__ioDevice.open(QIODevice.OpenModeFlag.ReadOnly) 124 self.__ioDevice.open(QIODevice.OpenModeFlag.ReadOnly)
123 125
124 while maxSize > 0: 126 while maxSize > 0:
125 chunk.absPos = sys.maxsize 127 chunk.absPos = sys.maxsize
126 chunksLoopOngoing = True 128 chunksLoopOngoing = True
127 while chunkIdx < len(self.__chunks) and chunksLoopOngoing: 129 while chunkIdx < len(self.__chunks) and chunksLoopOngoing:
128 # In this section, we track changes before our required data 130 # In this section, we track changes before our required data
129 # and we take the edited data, if availible. ioDelta is a 131 # and we take the edited data, if availible. ioDelta is a
130 # difference counter to justify the read pointer to the 132 # difference counter to justify the read pointer to the
131 # original data, if data in between was deleted or inserted. 133 # original data, if data in between was deleted or inserted.
132 134
133 chunk = self.__chunks[chunkIdx] 135 chunk = self.__chunks[chunkIdx]
134 if chunk.absPos > pos: 136 if chunk.absPos > pos:
135 chunksLoopOngoing = False 137 chunksLoopOngoing = False
136 else: 138 else:
137 chunkIdx += 1 139 chunkIdx += 1
140 count = len(chunk.data) - chunkOfs 142 count = len(chunk.data) - chunkOfs
141 ioDelta += self.CHUNK_SIZE - len(chunk.data) 143 ioDelta += self.CHUNK_SIZE - len(chunk.data)
142 else: 144 else:
143 count = maxSize 145 count = maxSize
144 if count > 0: 146 if count > 0:
145 buffer += chunk.data[chunkOfs:chunkOfs + count] 147 buffer += chunk.data[chunkOfs : chunkOfs + count]
146 maxSize -= count 148 maxSize -= count
147 pos += count 149 pos += count
148 if highlighted is not None: 150 if highlighted is not None:
149 highlighted += chunk.dataChanged[ 151 highlighted += chunk.dataChanged[
150 chunkOfs:chunkOfs + count] 152 chunkOfs : chunkOfs + count
151 153 ]
154
152 if maxSize > 0 and pos < chunk.absPos: 155 if maxSize > 0 and pos < chunk.absPos:
153 # In this section, we read data from the original source. This 156 # In this section, we read data from the original source. This
154 # will only happen, when no copied data is available. 157 # will only happen, when no copied data is available.
155 if chunk.absPos - pos > maxSize: 158 if chunk.absPos - pos > maxSize:
156 byteCount = maxSize 159 byteCount = maxSize
157 else: 160 else:
158 byteCount = chunk.absPos - pos 161 byteCount = chunk.absPos - pos
159 162
160 maxSize -= byteCount 163 maxSize -= byteCount
161 self.__ioDevice.seek(pos + ioDelta) 164 self.__ioDevice.seek(pos + ioDelta)
162 readBuffer = bytearray(self.__ioDevice.read(byteCount)) 165 readBuffer = bytearray(self.__ioDevice.read(byteCount))
163 buffer += readBuffer 166 buffer += readBuffer
164 if highlighted is not None: 167 if highlighted is not None:
165 highlighted += bytearray(len(readBuffer)) 168 highlighted += bytearray(len(readBuffer))
166 pos += len(readBuffer) 169 pos += len(readBuffer)
167 170
168 self.__ioDevice.close() 171 self.__ioDevice.close()
169 return buffer 172 return buffer
170 173
171 def write(self, ioDevice, pos=0, count=-1): 174 def write(self, ioDevice, pos=0, count=-1):
172 """ 175 """
173 Public method to write data to an io device. 176 Public method to write data to an io device.
174 177
175 @param ioDevice io device to write the data to 178 @param ioDevice io device to write the data to
176 @type QIODevice 179 @type QIODevice
177 @param pos position to write bytes from 180 @param pos position to write bytes from
178 @type int 181 @type int
179 @param count amount of bytes to write 182 @param count amount of bytes to write
182 @rtype bool 185 @rtype bool
183 """ 186 """
184 if count == -1: 187 if count == -1:
185 # write all data 188 # write all data
186 count = self.__size 189 count = self.__size
187 190
188 ok = ioDevice.open(QIODevice.OpenModeFlag.WriteOnly) 191 ok = ioDevice.open(QIODevice.OpenModeFlag.WriteOnly)
189 if ok: 192 if ok:
190 idx = pos 193 idx = pos
191 while idx < count: 194 while idx < count:
192 data = self.data(idx, self.BUFFER_SIZE) 195 data = self.data(idx, self.BUFFER_SIZE)
193 ioDevice.write(QByteArray(data)) 196 ioDevice.write(QByteArray(data))
194 197
195 # increment loop variable 198 # increment loop variable
196 idx += self.BUFFER_SIZE 199 idx += self.BUFFER_SIZE
197 200
198 ioDevice.close() 201 ioDevice.close()
199 202
200 return ok 203 return ok
201 204
202 def setDataChanged(self, pos, dataChanged): 205 def setDataChanged(self, pos, dataChanged):
203 """ 206 """
204 Public method to set highlighting info. 207 Public method to set highlighting info.
205 208
206 @param pos position to set highlighting info for 209 @param pos position to set highlighting info for
207 @type int 210 @type int
208 @param dataChanged flag indicating changed data 211 @param dataChanged flag indicating changed data
209 @type bool 212 @type bool
210 """ 213 """
212 # position is out of range, do nothing 215 # position is out of range, do nothing
213 return 216 return
214 chunkIdx = self.__getChunkIndex(pos) 217 chunkIdx = self.__getChunkIndex(pos)
215 posInChunk = pos - self.__chunks[chunkIdx].absPos 218 posInChunk = pos - self.__chunks[chunkIdx].absPos
216 self.__chunks[chunkIdx].dataChanged[posInChunk] = int(dataChanged) 219 self.__chunks[chunkIdx].dataChanged[posInChunk] = int(dataChanged)
217 220
218 def dataChanged(self, pos): 221 def dataChanged(self, pos):
219 """ 222 """
220 Public method to test, if some data was changed. 223 Public method to test, if some data was changed.
221 224
222 @param pos byte position to check 225 @param pos byte position to check
223 @type int 226 @type int
224 @return flag indicating the changed state 227 @return flag indicating the changed state
225 @rtype bool 228 @rtype bool
226 """ 229 """
227 highlighted = bytearray() 230 highlighted = bytearray()
228 self.data(pos, 1, highlighted) 231 self.data(pos, 1, highlighted)
229 return highlighted and bool(highlighted[0]) 232 return highlighted and bool(highlighted[0])
230 233
231 def indexOf(self, byteArray, start): 234 def indexOf(self, byteArray, start):
232 """ 235 """
233 Public method to search the first occurrence of some data. 236 Public method to search the first occurrence of some data.
234 237
235 @param byteArray data to search for 238 @param byteArray data to search for
236 @type bytearray 239 @type bytearray
237 @param start position to start the search at 240 @param start position to start the search at
238 @type int 241 @type int
239 @return position the data was found at or -1 if nothing could be found 242 @return position the data was found at or -1 if nothing could be found
240 @rtype int 243 @rtype int
241 """ 244 """
242 ba = bytearray(byteArray) 245 ba = bytearray(byteArray)
243 246
244 result = -1 247 result = -1
245 pos = start 248 pos = start
246 while pos < self.__size: 249 while pos < self.__size:
247 buffer = self.data(pos, self.BUFFER_SIZE + len(ba) - 1) 250 buffer = self.data(pos, self.BUFFER_SIZE + len(ba) - 1)
248 findPos = buffer.find(ba) 251 findPos = buffer.find(ba)
249 if findPos >= 0: 252 if findPos >= 0:
250 result = pos + findPos 253 result = pos + findPos
251 break 254 break
252 255
253 # increment loop variable 256 # increment loop variable
254 pos += self.BUFFER_SIZE 257 pos += self.BUFFER_SIZE
255 258
256 return result 259 return result
257 260
258 def lastIndexOf(self, byteArray, start): 261 def lastIndexOf(self, byteArray, start):
259 """ 262 """
260 Public method to search the last occurrence of some data. 263 Public method to search the last occurrence of some data.
261 264
262 @param byteArray data to search for 265 @param byteArray data to search for
263 @type bytearray 266 @type bytearray
264 @param start position to start the search at 267 @param start position to start the search at
265 @type int 268 @type int
266 @return position the data was found at or -1 if nothing could be found 269 @return position the data was found at or -1 if nothing could be found
267 @rtype int 270 @rtype int
268 """ 271 """
269 ba = bytearray(byteArray) 272 ba = bytearray(byteArray)
270 273
271 result = -1 274 result = -1
272 pos = start 275 pos = start
273 while pos > 0 and result < 0: 276 while pos > 0 and result < 0:
274 sPos = pos - self.BUFFER_SIZE - len(ba) + 1 277 sPos = pos - self.BUFFER_SIZE - len(ba) + 1
275 if sPos < 0: 278 if sPos < 0:
276 sPos = 0 279 sPos = 0
277 280
278 buffer = self.data(sPos, pos - sPos) 281 buffer = self.data(sPos, pos - sPos)
279 findPos = buffer.rfind(ba) 282 findPos = buffer.rfind(ba)
280 if findPos >= 0: 283 if findPos >= 0:
281 result = sPos + findPos 284 result = sPos + findPos
282 break 285 break
283 286
284 # increment loop variable 287 # increment loop variable
285 pos -= self.BUFFER_SIZE 288 pos -= self.BUFFER_SIZE
286 289
287 return result 290 return result
288 291
289 def insert(self, pos, data): 292 def insert(self, pos, data):
290 """ 293 """
291 Public method to insert a byte. 294 Public method to insert a byte.
292 295
293 @param pos position to insert at 296 @param pos position to insert at
294 @type int 297 @type int
295 @param data byte to insert 298 @param data byte to insert
296 @type int (range 0 to 255) 299 @type int (range 0 to 255)
297 @return flag indicating success 300 @return flag indicating success
298 @rtype bool 301 @rtype bool
299 """ 302 """
300 if pos < 0 or pos > self.__size: 303 if pos < 0 or pos > self.__size:
301 # position is out of range, do nothing 304 # position is out of range, do nothing
302 return False 305 return False
303 306
304 chunkIdx = ( 307 chunkIdx = (
305 self.__getChunkIndex(pos - 1) 308 self.__getChunkIndex(pos - 1)
306 if pos == self.__size else 309 if pos == self.__size
307 self.__getChunkIndex(pos) 310 else self.__getChunkIndex(pos)
308 ) 311 )
309 chunk = self.__chunks[chunkIdx] 312 chunk = self.__chunks[chunkIdx]
310 posInChunk = pos - chunk.absPos 313 posInChunk = pos - chunk.absPos
311 chunk.data.insert(posInChunk, data) 314 chunk.data.insert(posInChunk, data)
312 chunk.dataChanged.insert(posInChunk, 1) 315 chunk.dataChanged.insert(posInChunk, 1)
313 for idx in range(chunkIdx + 1, len(self.__chunks)): 316 for idx in range(chunkIdx + 1, len(self.__chunks)):
314 self.__chunks[idx].absPos += 1 317 self.__chunks[idx].absPos += 1
315 self.__size += 1 318 self.__size += 1
316 self.__pos = pos 319 self.__pos = pos
317 return True 320 return True
318 321
319 def overwrite(self, pos, data): 322 def overwrite(self, pos, data):
320 """ 323 """
321 Public method to overwrite a byte. 324 Public method to overwrite a byte.
322 325
323 @param pos position to overwrite 326 @param pos position to overwrite
324 @type int 327 @type int
325 @param data byte to overwrite with 328 @param data byte to overwrite with
326 @type int (range 0 to 255) 329 @type int (range 0 to 255)
327 @return flag indicating success 330 @return flag indicating success
328 @rtype bool 331 @rtype bool
329 """ 332 """
330 if pos < 0 or pos >= self.__size: 333 if pos < 0 or pos >= self.__size:
331 # position is out of range, do nothing 334 # position is out of range, do nothing
332 return False 335 return False
333 336
334 chunkIdx = self.__getChunkIndex(pos) 337 chunkIdx = self.__getChunkIndex(pos)
335 chunk = self.__chunks[chunkIdx] 338 chunk = self.__chunks[chunkIdx]
336 posInChunk = pos - chunk.absPos 339 posInChunk = pos - chunk.absPos
337 chunk.data[posInChunk] = data 340 chunk.data[posInChunk] = data
338 chunk.dataChanged[posInChunk] = 1 341 chunk.dataChanged[posInChunk] = 1
339 self.__pos = pos 342 self.__pos = pos
340 return True 343 return True
341 344
342 def removeAt(self, pos): 345 def removeAt(self, pos):
343 """ 346 """
344 Public method to remove a byte. 347 Public method to remove a byte.
345 348
346 @param pos position to remove 349 @param pos position to remove
347 @type int 350 @type int
348 @return flag indicating success 351 @return flag indicating success
349 @rtype bool 352 @rtype bool
350 """ 353 """
351 if pos < 0 or pos >= self.__size: 354 if pos < 0 or pos >= self.__size:
352 # position is out of range, do nothing 355 # position is out of range, do nothing
353 return False 356 return False
354 357
355 chunkIdx = self.__getChunkIndex(pos) 358 chunkIdx = self.__getChunkIndex(pos)
356 chunk = self.__chunks[chunkIdx] 359 chunk = self.__chunks[chunkIdx]
357 posInChunk = pos - chunk.absPos 360 posInChunk = pos - chunk.absPos
358 chunk.data.pop(posInChunk) 361 chunk.data.pop(posInChunk)
359 chunk.dataChanged.pop(posInChunk) 362 chunk.dataChanged.pop(posInChunk)
360 for idx in range(chunkIdx + 1, len(self.__chunks)): 363 for idx in range(chunkIdx + 1, len(self.__chunks)):
361 self.__chunks[idx].absPos -= 1 364 self.__chunks[idx].absPos -= 1
362 self.__size -= 1 365 self.__size -= 1
363 self.__pos = pos 366 self.__pos = pos
364 return True 367 return True
365 368
366 def __getitem__(self, pos): 369 def __getitem__(self, pos):
367 """ 370 """
368 Special method to get a byte at a position. 371 Special method to get a byte at a position.
369 372
370 Note: This realizes the [] get operator. 373 Note: This realizes the [] get operator.
371 374
372 @param pos position of byte to get 375 @param pos position of byte to get
373 @type int 376 @type int
374 @return requested byte 377 @return requested byte
375 @rtype int (range 0 to 255) 378 @rtype int (range 0 to 255)
376 """ 379 """
377 if pos >= self.__size: 380 if pos >= self.__size:
378 return 0 381 return 0
379 ## raise IndexError 382 ## raise IndexError
380 383
381 return self.data(pos, 1)[0] 384 return self.data(pos, 1)[0]
382 385
383 def pos(self): 386 def pos(self):
384 """ 387 """
385 Public method to get the current position. 388 Public method to get the current position.
386 389
387 @return current position 390 @return current position
388 @rtype int 391 @rtype int
389 """ 392 """
390 return self.__pos 393 return self.__pos
391 394
392 def size(self): 395 def size(self):
393 """ 396 """
394 Public method to get the current data size. 397 Public method to get the current data size.
395 398
396 @return current data size 399 @return current data size
397 @rtype int 400 @rtype int
398 """ 401 """
399 return self.__size 402 return self.__size
400 403
401 def __getChunkIndex(self, absPos): 404 def __getChunkIndex(self, absPos):
402 """ 405 """
403 Private method to get the chunk index for a position. 406 Private method to get the chunk index for a position.
404 407
405 This method checks, if there is already a copied chunk available. If 408 This method checks, if there is already a copied chunk available. If
406 there is one, it returns its index. If there is no copied chunk 409 there is one, it returns its index. If there is no copied chunk
407 available, original data will be copied into a new chunk. 410 available, original data will be copied into a new chunk.
408 411
409 @param absPos absolute position of the data. 412 @param absPos absolute position of the data.
410 @type int 413 @type int
411 @return index of the chunk containing the position 414 @return index of the chunk containing the position
412 @rtype int 415 @rtype int
413 """ 416 """
414 foundIdx = -1 417 foundIdx = -1
415 insertIdx = 0 418 insertIdx = 0
416 ioDelta = 0 419 ioDelta = 0
417 420
418 for idx in range(len(self.__chunks)): 421 for idx in range(len(self.__chunks)):
419 chunk = self.__chunks[idx] 422 chunk = self.__chunks[idx]
420 if ( 423 if absPos >= chunk.absPos and absPos < (chunk.absPos + len(chunk.data)):
421 absPos >= chunk.absPos and
422 absPos < (chunk.absPos + len(chunk.data))
423 ):
424 foundIdx = idx 424 foundIdx = idx
425 break 425 break
426 426
427 if absPos < chunk.absPos: 427 if absPos < chunk.absPos:
428 insertIdx = idx 428 insertIdx = idx
429 break 429 break
430 430
431 ioDelta += len(chunk.data) - self.CHUNK_SIZE 431 ioDelta += len(chunk.data) - self.CHUNK_SIZE
432 insertIdx = idx + 1 432 insertIdx = idx + 1
433 433
434 if foundIdx == -1: 434 if foundIdx == -1:
435 newChunk = HexEditChunk() 435 newChunk = HexEditChunk()
436 readAbsPos = absPos - ioDelta 436 readAbsPos = absPos - ioDelta
437 readPos = readAbsPos & self.READ_CHUNK_MASK 437 readPos = readAbsPos & self.READ_CHUNK_MASK
438 self.__ioDevice.open(QIODevice.OpenModeFlag.ReadOnly) 438 self.__ioDevice.open(QIODevice.OpenModeFlag.ReadOnly)
441 self.__ioDevice.close() 441 self.__ioDevice.close()
442 newChunk.absPos = absPos - (readAbsPos - readPos) 442 newChunk.absPos = absPos - (readAbsPos - readPos)
443 newChunk.dataChanged = bytearray(len(newChunk.data)) 443 newChunk.dataChanged = bytearray(len(newChunk.data))
444 self.__chunks.insert(insertIdx, newChunk) 444 self.__chunks.insert(insertIdx, newChunk)
445 foundIdx = insertIdx 445 foundIdx = insertIdx
446 446
447 return foundIdx 447 return foundIdx

eric ide

mercurial