Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/filterStorage.js

Issue 29398705: Issue 5052 - Allow importing and exporting filterStorage data without file access (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Created March 30, 2017, 12:21 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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 };
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld