| Left: | ||
| Right: |
| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
| 3 * Copyright (C) 2006-2017 eyeo GmbH | 3 * Copyright (C) 2006-2017 eyeo GmbH |
| 4 * | 4 * |
| 5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
| 6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
| 7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
| 8 * | 8 * |
| 9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| (...skipping 378 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 389 for (let text in Filter.knownFilters) | 389 for (let text in Filter.knownFilters) |
| 390 filters.push(Filter.knownFilters[text]); | 390 filters.push(Filter.knownFilters[text]); |
| 391 } | 391 } |
| 392 for (let filter of filters) | 392 for (let filter of filters) |
| 393 { | 393 { |
| 394 filter.hitCount = 0; | 394 filter.hitCount = 0; |
| 395 filter.lastHit = 0; | 395 filter.lastHit = 0; |
| 396 } | 396 } |
| 397 }, | 397 }, |
| 398 | 398 |
| 399 _loading: false, | 399 /** |
| 400 * @callback TextSink | |
| 401 * @param {string?} line | |
| 402 */ | |
| 403 | |
| 404 /** | |
| 405 * Allows importing previously serialized filter data. | |
| 406 * @return {TextSink} | |
| 407 * Function to be called for each line of data. Calling it with null as | |
| 408 * parameter finalizes the import and replaces existing data. No changes | |
| 409 * will be applied before finalization, so import can be "aborted" by | |
| 410 * forgetting this callback. | |
| 411 */ | |
| 412 importData() | |
| 413 { | |
| 414 let parser = new INIParser(); | |
| 415 return line => | |
| 416 { | |
| 417 parser.process(line); | |
|
kzar
2017/03/30 12:58:48
I wonder why we parser.process the final null?
Wladimir Palant
2017/03/30 13:02:06
The parser uses it to flush out whatever object wa
kzar
2017/03/30 13:10:54
Acknowledged.
| |
| 418 if (line === null) | |
| 419 { | |
| 420 // Old special groups might have been converted, remove them if | |
| 421 // they are empty | |
| 422 let specialMap = new Set(["~il~", "~wl~", "~fl~", "~eh~"]); | |
| 423 let knownSubscriptions = Object.create(null); | |
| 424 for (let i = 0; i < parser.subscriptions.length; i++) | |
| 425 { | |
| 426 let subscription = parser.subscriptions[i]; | |
| 427 if (subscription instanceof SpecialSubscription && | |
| 428 subscription.filters.length == 0 && | |
| 429 specialMap.has(subscription.url)) | |
| 430 { | |
| 431 parser.subscriptions.splice(i--, 1); | |
| 432 } | |
| 433 else | |
| 434 knownSubscriptions[subscription.url] = subscription; | |
| 435 } | |
| 436 | |
| 437 this.fileProperties = parser.fileProperties; | |
| 438 this.subscriptions = parser.subscriptions; | |
| 439 this.knownSubscriptions = knownSubscriptions; | |
| 440 Filter.knownFilters = parser.knownFilters; | |
| 441 Subscription.knownSubscriptions = parser.knownSubscriptions; | |
| 442 | |
| 443 if (parser.userFilters) | |
| 444 { | |
| 445 for (let filter of parser.userFilters) | |
| 446 this.addFilter(Filter.fromText(filter), null, undefined, true); | |
| 447 } | |
| 448 | |
| 449 FilterNotifier.triggerListeners("load"); | |
| 450 } | |
| 451 }; | |
| 452 }, | |
| 400 | 453 |
| 401 /** | 454 /** |
| 402 * Loads all subscriptions from the disk | 455 * Loads all subscriptions from the disk |
| 403 * @param {nsIFile} [sourceFile] File to read from | 456 */ |
| 404 */ | 457 loadFromDisk() |
| 405 loadFromDisk(sourceFile) | 458 { |
| 406 { | 459 let readFile = () => |
| 407 if (this._loading) | 460 { |
| 408 return; | 461 let parser = { |
| 409 | 462 process: this.importData() |
| 410 this._loading = true; | 463 }; |
| 411 | 464 IO.readFromFile(this.sourceFile, parser, readFromFileException => |
| 412 let readFile = (currentSourceFile, backupIndex) => | 465 { |
| 413 { | 466 if (!readFromFileException && this.subscriptions.length == 0) |
| 414 let parser = new INIParser(); | |
| 415 IO.readFromFile(currentSourceFile, parser, readFromFileException => | |
| 416 { | |
| 417 if (!readFromFileException && parser.subscriptions.length == 0) | |
| 418 { | 467 { |
| 419 // No filter subscriptions in the file, this isn't right. | 468 // No filter subscriptions in the file, this isn't right. |
| 420 readFromFileException = new Error("No data in the file"); | 469 readFromFileException = new Error("No data in the file"); |
| 421 } | 470 } |
| 422 | 471 |
| 423 if (readFromFileException) | 472 if (readFromFileException) |
| 424 Cu.reportError(readFromFileException); | 473 Cu.reportError(readFromFileException); |
| 425 | 474 |
| 426 if (readFromFileException && !explicitFile) | 475 if (readFromFileException) |
| 427 { | 476 tryBackup(1); |
| 428 // Attempt to load a backup | |
| 429 currentSourceFile = this.sourcefile; | |
| 430 if (currentSourceFile) | |
| 431 { | |
| 432 let [, part1, part2] = /^(.*)(\.\w+)$/.exec( | |
| 433 currentSourceFile.leafName | |
| 434 ) || [null, currentSourceFile.leafName, ""]; | |
| 435 | |
| 436 currentSourceFile = currentSourceFile.clone(); | |
| 437 currentSourceFile.leafName = ( | |
| 438 part1 + "-backup" + (++backupIndex) + part2 | |
| 439 ); | |
| 440 | |
| 441 IO.statFile(currentSourceFile, (statFileException, statData) => | |
| 442 { | |
| 443 if (!statFileException && statData.exists) | |
| 444 readFile(currentSourceFile, backupIndex); | |
| 445 else | |
| 446 doneReading(parser); | |
| 447 }); | |
| 448 return; | |
| 449 } | |
| 450 } | |
| 451 doneReading(parser); | |
| 452 }); | 477 }); |
| 453 }; | 478 }; |
| 454 | 479 |
| 455 let doneReading = parser => | 480 let tryBackup = backupIndex => |
| 456 { | 481 { |
| 457 // Old special groups might have been converted, remove them if | 482 this.restoreBackup(backupIndex).then(() => |
| 458 // they are empty | 483 { |
| 459 let specialMap = {"~il~": true, "~wl~": true, "~fl~": true, "~eh~": true}; | 484 if (this.subscriptions.length == 0) |
| 460 let knownSubscriptions = Object.create(null); | 485 tryBackup(backupIndex + 1); |
| 461 for (let i = 0; i < parser.subscriptions.length; i++) | 486 }).catch(error => |
| 462 { | 487 { |
| 463 let subscription = parser.subscriptions[i]; | 488 // Give up |
| 464 if (subscription instanceof SpecialSubscription && | 489 }); |
| 465 subscription.filters.length == 0 && subscription.url in specialMap) | 490 }; |
| 466 { | 491 |
| 467 parser.subscriptions.splice(i--, 1); | 492 IO.statFile(this.sourceFile, (statError, statData) => |
| 468 } | 493 { |
| 494 if (statError || !statData.exists) | |
| 495 { | |
| 496 this.firstRun = true; | |
| 497 FilterNotifier.triggerListeners("load"); | |
| 498 } | |
| 499 else | |
| 500 readFile(); | |
| 501 }); | |
| 502 }, | |
| 503 | |
| 504 /** | |
| 505 * Restores an automatically created backup. | |
| 506 * @param {number} backupIndex | |
| 507 * number of the backup to restore (1 being the most recent) | |
| 508 * @return {Promise} promise resolved or rejected when restoring is complete | |
| 509 */ | |
| 510 restoreBackup(backupIndex) | |
| 511 { | |
| 512 return new Promise((resolve, reject) => | |
| 513 { | |
| 514 // Attempt to load a backup | |
| 515 let [, part1, part2] = /^(.*)(\.\w+)$/.exec( | |
|
kzar
2017/03/30 12:58:48
Well I just learned something, I didn't realise va
| |
| 516 this.sourceFile.leafName | |
| 517 ) || [null, this.sourceFile.leafName, ""]; | |
| 518 | |
| 519 let backupFile = this.sourceFile.clone(); | |
| 520 backupFile.leafName = (part1 + "-backup" + backupIndex + part2); | |
| 521 | |
| 522 let parser = { | |
| 523 process: this.importData() | |
| 524 }; | |
| 525 IO.readFromFile(backupFile, parser, error => | |
| 526 { | |
| 527 if (error) | |
| 528 reject(error); | |
| 469 else | 529 else |
| 470 knownSubscriptions[subscription.url] = subscription; | 530 { |
| 471 } | 531 this.saveToDisk(); |
| 472 | 532 resolve(); |
| 473 this.fileProperties = parser.fileProperties; | 533 } |
| 474 this.subscriptions = parser.subscriptions; | 534 }); |
| 475 this.knownSubscriptions = knownSubscriptions; | 535 }); |
| 476 Filter.knownFilters = parser.knownFilters; | 536 }, |
| 477 Subscription.knownSubscriptions = parser.knownSubscriptions; | 537 |
| 478 | 538 /** |
| 479 if (parser.userFilters) | 539 * Generator serializing filter data and yielding it line by line. |
| 480 { | 540 */ |
| 481 for (let i = 0; i < parser.userFilters.length; i++) | 541 *exportData() |
| 482 { | 542 { |
| 483 let filter = Filter.fromText(parser.userFilters[i]); | 543 // Do not persist external subscriptions |
|
kzar
2017/03/30 12:58:48
Nit: I guess we could avoid doing this filtering u
Wladimir Palant
2017/03/30 13:02:06
Better not. This is creating a copy of the list fo
kzar
2017/03/30 13:10:54
Ah OK, makes sense.
| |
| 484 this.addFilter(filter, null, undefined, true); | 544 let subscriptions = this.subscriptions.filter( |
| 485 } | 545 s => !(s instanceof ExternalSubscription) |
| 486 } | 546 ); |
| 487 | 547 |
| 488 this._loading = false; | |
| 489 FilterNotifier.triggerListeners("load"); | |
| 490 | |
| 491 if (sourceFile != this.sourceFile) | |
| 492 this.saveToDisk(); | |
| 493 }; | |
| 494 | |
| 495 let explicitFile; | |
| 496 if (sourceFile) | |
| 497 { | |
| 498 explicitFile = true; | |
| 499 readFile(sourceFile, 0); | |
| 500 } | |
| 501 else | |
| 502 { | |
| 503 explicitFile = false; | |
| 504 ({sourceFile} = FilterStorage); | |
| 505 | |
| 506 let callback = function(e, statData) | |
| 507 { | |
| 508 if (e || !statData.exists) | |
| 509 { | |
| 510 this.firstRun = true; | |
| 511 this._loading = false; | |
| 512 FilterNotifier.triggerListeners("load"); | |
| 513 } | |
| 514 else | |
| 515 readFile(sourceFile, 0); | |
| 516 }.bind(this); | |
| 517 | |
| 518 if (sourceFile) | |
| 519 IO.statFile(sourceFile, callback); | |
| 520 else | |
| 521 callback(true); | |
| 522 } | |
| 523 }, | |
| 524 | |
| 525 *_generateFilterData(subscriptions) | |
| 526 { | |
| 527 yield "# Adblock Plus preferences"; | 548 yield "# Adblock Plus preferences"; |
| 528 yield "version=" + formatVersion; | 549 yield "version=" + formatVersion; |
| 529 | 550 |
| 530 let saved = Object.create(null); | 551 let saved = new Set(); |
| 531 let buf = []; | 552 let buf = []; |
| 532 | 553 |
| 533 // Save filter data | 554 // Save filter data |
| 534 for (let i = 0; i < subscriptions.length; i++) | 555 for (let subscription of subscriptions) |
| 535 { | 556 { |
| 536 let subscription = subscriptions[i]; | 557 for (let filter of subscription.filters) |
| 537 for (let j = 0; j < subscription.filters.length; j++) | 558 { |
| 538 { | 559 if (!saved.has(filter.text)) |
| 539 let filter = subscription.filters[j]; | |
| 540 if (!(filter.text in saved)) | |
| 541 { | 560 { |
| 542 filter.serialize(buf); | 561 filter.serialize(buf); |
| 543 saved[filter.text] = filter; | 562 saved.add(filter.text); |
| 544 for (let k = 0; k < buf.length; k++) | 563 for (let line of buf) |
| 545 yield buf[k]; | 564 yield line; |
| 546 buf.splice(0); | 565 buf.splice(0); |
| 547 } | 566 } |
| 548 } | 567 } |
| 549 } | 568 } |
| 550 | 569 |
| 551 // Save subscriptions | 570 // Save subscriptions |
| 552 for (let i = 0; i < subscriptions.length; i++) | 571 for (let subscription of subscriptions) |
| 553 { | 572 { |
| 554 let subscription = subscriptions[i]; | |
| 555 | |
| 556 yield ""; | 573 yield ""; |
| 557 | 574 |
| 558 subscription.serialize(buf); | 575 subscription.serialize(buf); |
| 559 if (subscription.filters.length) | 576 if (subscription.filters.length) |
| 560 { | 577 { |
| 561 buf.push("", "[Subscription filters]"); | 578 buf.push("", "[Subscription filters]"); |
| 562 subscription.serializeFilters(buf); | 579 subscription.serializeFilters(buf); |
| 563 } | 580 } |
| 564 for (let k = 0; k < buf.length; k++) | 581 for (let line of buf) |
| 565 yield buf[k]; | 582 yield line; |
| 566 buf.splice(0); | 583 buf.splice(0); |
| 567 } | 584 } |
| 568 }, | 585 }, |
| 569 | 586 |
| 570 /** | 587 /** |
| 571 * Will be set to true if saveToDisk() is running (reentrance protection). | 588 * Will be set to true if saveToDisk() is running (reentrance protection). |
| 572 * @type {boolean} | 589 * @type {boolean} |
| 573 */ | 590 */ |
| 574 _saving: false, | 591 _saving: false, |
| 575 | 592 |
| 576 /** | 593 /** |
| 577 * Will be set to true if a saveToDisk() call arrives while saveToDisk() is | 594 * Will be set to true if a saveToDisk() call arrives while saveToDisk() is |
| 578 * already running (delayed execution). | 595 * already running (delayed execution). |
| 579 * @type {boolean} | 596 * @type {boolean} |
| 580 */ | 597 */ |
| 581 _needsSave: false, | 598 _needsSave: false, |
| 582 | 599 |
| 583 /** | 600 /** |
| 584 * Saves all subscriptions back to disk | 601 * Saves all subscriptions back to disk |
| 585 * @param {nsIFile} [targetFile] File to be written | |
| 586 */ | 602 */ |
| 587 saveToDisk(targetFile) | 603 saveToDisk() |
| 588 { | 604 { |
| 589 let explicitFile = true; | 605 if (this._saving) |
| 590 if (!targetFile) | |
| 591 { | |
| 592 targetFile = FilterStorage.sourceFile; | |
| 593 explicitFile = false; | |
| 594 } | |
| 595 if (!targetFile) | |
| 596 return; | |
| 597 | |
| 598 if (!explicitFile && this._saving) | |
| 599 { | 606 { |
| 600 this._needsSave = true; | 607 this._needsSave = true; |
| 601 return; | 608 return; |
| 602 } | 609 } |
| 603 | 610 |
| 604 // Make sure the file's parent directory exists | 611 // Make sure the file's parent directory exists |
| 612 let targetFile = this.sourceFile; | |
| 605 try | 613 try |
| 606 { | 614 { |
| 607 targetFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, | 615 targetFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, |
| 608 FileUtils.PERMS_DIRECTORY); | 616 FileUtils.PERMS_DIRECTORY); |
| 609 } | 617 } |
| 610 catch (e) {} | 618 catch (e) {} |
| 611 | 619 |
| 612 let writeFilters = () => | 620 let writeFilters = () => |
| 613 { | 621 { |
| 614 IO.writeToFile(targetFile, this._generateFilterData(subscriptions), e => | 622 IO.writeToFile(targetFile, this.exportData(), e => |
| 615 { | 623 { |
| 616 if (!explicitFile) | 624 this._saving = false; |
| 617 this._saving = false; | |
| 618 | 625 |
| 619 if (e) | 626 if (e) |
| 620 Cu.reportError(e); | 627 Cu.reportError(e); |
| 621 | 628 |
| 622 if (!explicitFile && this._needsSave) | 629 if (this._needsSave) |
| 623 { | 630 { |
| 624 this._needsSave = false; | 631 this._needsSave = false; |
| 625 this.saveToDisk(); | 632 this.saveToDisk(); |
| 626 } | 633 } |
| 627 else | 634 else |
| 628 FilterNotifier.triggerListeners("save"); | 635 FilterNotifier.triggerListeners("save"); |
| 629 }); | 636 }); |
| 630 }; | 637 }; |
| 631 | 638 |
| 632 let checkBackupRequired = (callbackNotRequired, callbackRequired) => | 639 let checkBackupRequired = (callbackNotRequired, callbackRequired) => |
| 633 { | 640 { |
| 634 if (explicitFile || Prefs.patternsbackups <= 0) | 641 if (Prefs.patternsbackups <= 0) |
| 635 callbackNotRequired(); | 642 callbackNotRequired(); |
| 636 else | 643 else |
| 637 { | 644 { |
| 638 IO.statFile(targetFile, (statFileException, statData) => | 645 IO.statFile(targetFile, (statFileException, statData) => |
| 639 { | 646 { |
| 640 if (statFileException || !statData.exists) | 647 if (statFileException || !statData.exists) |
| 641 callbackNotRequired(); | 648 callbackNotRequired(); |
| 642 else | 649 else |
| 643 { | 650 { |
| 644 let [, part1, part2] = /^(.*)(\.\w+)$/.exec(targetFile.leafName) || | 651 let [, part1, part2] = /^(.*)(\.\w+)$/.exec(targetFile.leafName) || |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 687 } | 694 } |
| 688 else | 695 else |
| 689 { | 696 { |
| 690 let toFile = targetFile.clone(); | 697 let toFile = targetFile.clone(); |
| 691 toFile.leafName = part1 + "-backup" + (index + 1) + part2; | 698 toFile.leafName = part1 + "-backup" + (index + 1) + part2; |
| 692 | 699 |
| 693 IO.copyFile(targetFile, toFile, writeFilters); | 700 IO.copyFile(targetFile, toFile, writeFilters); |
| 694 } | 701 } |
| 695 }; | 702 }; |
| 696 | 703 |
| 697 // Do not persist external subscriptions | 704 this._saving = true; |
| 698 let subscriptions = this.subscriptions.filter( | |
| 699 s => !(s instanceof ExternalSubscription) | |
| 700 ); | |
| 701 if (!explicitFile) | |
| 702 this._saving = true; | |
| 703 | 705 |
| 704 checkBackupRequired(writeFilters, removeLastBackup); | 706 checkBackupRequired(writeFilters, removeLastBackup); |
| 705 }, | 707 }, |
| 706 | 708 |
| 707 /** | 709 /** |
| 708 * @typedef FileInfo | 710 * @typedef FileInfo |
| 709 * @type {object} | 711 * @type {object} |
| 710 * @property {nsIFile} file | 712 * @property {nsIFile} file |
| 711 * @property {number} lastModified | 713 * @property {number} lastModified |
| 712 */ | 714 */ |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 728 return new Promise((resolve, reject) => | 730 return new Promise((resolve, reject) => |
| 729 { | 731 { |
| 730 let file = FilterStorage.sourceFile.clone(); | 732 let file = FilterStorage.sourceFile.clone(); |
| 731 file.leafName = part1 + "-backup" + index + part2; | 733 file.leafName = part1 + "-backup" + index + part2; |
| 732 | 734 |
| 733 IO.statFile(file, (error, result) => | 735 IO.statFile(file, (error, result) => |
| 734 { | 736 { |
| 735 if (!error && result.exists) | 737 if (!error && result.exists) |
| 736 { | 738 { |
| 737 backups.push({ | 739 backups.push({ |
| 738 file, | 740 index, |
| 739 lastModified: result.lastModified | 741 lastModified: result.lastModified |
| 740 }); | 742 }); |
| 741 resolve(checkBackupFile(index + 1)); | 743 resolve(checkBackupFile(index + 1)); |
| 742 } | 744 } |
| 743 else | 745 else |
| 744 resolve(backups); | 746 resolve(backups); |
| 745 }); | 747 }); |
| 746 }); | 748 }); |
| 747 } | 749 } |
| 748 | 750 |
| (...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 887 Subscription.knownSubscriptions = origKnownSubscriptions; | 889 Subscription.knownSubscriptions = origKnownSubscriptions; |
| 888 } | 890 } |
| 889 | 891 |
| 890 // Allow events to be processed every now and then. | 892 // Allow events to be processed every now and then. |
| 891 // Note: IO.readFromFile() will deal with the potential reentrance here. | 893 // Note: IO.readFromFile() will deal with the potential reentrance here. |
| 892 this.linesProcessed++; | 894 this.linesProcessed++; |
| 893 if (this.linesProcessed % 1000 == 0) | 895 if (this.linesProcessed % 1000 == 0) |
| 894 return Utils.yield(); | 896 return Utils.yield(); |
| 895 } | 897 } |
| 896 }; | 898 }; |
| OLD | NEW |