<commit_before>import { Observable, BehaviorSubject, ConnectableObservable, Subscription } from 'rxjs';

const PouchDB = require('pouchdb');
PouchDB.plugin(require('pouchdb-find'));

import { remote as ElectronRemote } from 'electron';

import { promiseLoop, pouchdb_observe, pouchdb_store } from './util';

import { Moment } from 'moment';

/*****************************************************************************/

export interface Mailbox {
  name: string;
  path: string;
  children?: Mailbox[];
  flags: string[];
  specialUse?: string[];
};

export interface Mailboxes {
  children: Mailbox[];
};

export interface Address {
  address: string;
  name: string;
};

export interface Envelope {
  _id: string;
  flags?: string[];
  envelope: {
    "date"?: string;
    "subject"?: string;
    "from"?: Address[];
    "sender"?: Address[];
    "reply-to"?: Address[];
    "to"?: Address[];
    "cc"?: Address[];
    "bcc"?: Address[];
    "message-id"?: string;
    "in-reply-to"?: string;
  };
};

export interface ImapModel {

  readonly mailboxes: Observable<Mailboxes>;
//  readonly addresses: Observable<Address[]>;
//  readonly flags: Observable<string[]>;

//  readonly filteredDates: Observable<Moment[]>;           // ignoring Date filter

//  filterStartDate: Moment;
//  filterEndDate: Moment;

  readonly filteredMessages: Observable<Envelope[]>;

//  filterAddresses: {addr:Address,hdr?:string}[];
  filterMailboxes: Mailbox[];
//  filterFlags: {flag:string,invert:boolean}[];

//  countMsgPerAddress(addr:Address,hdr?:string): Observable<Number>;
  countMsgPerMailbox(mbx:Mailbox): Observable<Number>;
//  countMsgPerFlag(flag:string): Observable<Number>;
};


/*****************************************************************************/

export class AccountData {
  host: string = "";
  port: number = 0;
  user: string = "";
  pass: string = "";
};

/*****************************************************************************/

export class ImapCache implements ImapModel {
  private emailjsImapClient: any;
  private _accountData = new AccountData;

  private db: any;
  private _isOpen = false;

  isOpen(): boolean {
    return this._isOpen;
  }

  accountData(): AccountData {
    return this._accountData;
  }

  dbAccess(): any {
    return this.db;
  }

  /*
   * raw db access
   */

  retrieve(id: string): Promise<any> {
    return this.db.get(id);
  }

  retrieve_by_prefix(prefix:string): Promise<any[]> {
    return this.db.find({
      selector: { $and: [
        { _id: { $gte: prefix } },
        { _id: { $lte: prefix + '\uffff' } }
      ]},
    }).then((result:{docs:any[]})=>result.docs);
  }

  observe(id: string): Observable<any> {
    return pouchdb_observe(this.db,{
      selector: { _id: id },
      aggregate: true,
    }).filter(v=>v.length==1).map(v=>v[0]);
  }

  observe_by_prefix(prefix:string): Observable<any[]> {
    return pouchdb_observe(this.db,{
      selector: { $and: [
        { _id: { $gte: prefix } },
        { _id: { $lte: prefix + '\uffff' } }
      ]},
      aggregate: true
    });
  }

  store(newdoc: any): Promise<any> {
    return pouchdb_store(this.db, newdoc);
  }

  store_bulk(newdocs: any[]): Promise<any> {
    return this.db.bulkDocs(newdocs);
  }


  allMessages: Observable<Envelope[]>;
  subscription: Subscription;

  constructor(path: string = undefined) {
    if(!path)
      path = (ElectronRemote ? ElectronRemote.app.getPath('userData') : '.') + "/imapcache"

    this.db = new PouchDB(path);

    this.db.info().catch((err)=>{
      console.error("failed to open PouchDB: " + path);
      console.error(err);
      this.db.close();
      this.db = undefined
    }).then((info)=>{
      console.info("successfully opened PouchDB: " + path);
      console.info(info);
      this._isOpen = true;
    });

    this.retrieve("account").then((doc) => {
      this._accountData = doc as AccountData;
    }).catch(()=>{
    });

    this.allMessages = (this.observe_by_prefix("envelope:") as Observable<Envelope[]>).publishBehavior([]);

    this.subscription = (this.allMessages as ConnectableObservable<Envelope[]>).connect();
  }

  close(): void {
    if(this.db) {
      this.db.close();
      this.db = undefined;
      this._isOpen = false;
      this.subscription.unsubscribe();
    }
  }

  get mailboxes(): Observable<Mailboxes> {
    return this.observe("mailboxes") as Observable<Mailboxes>;
  }

  private _filterMailboxes = new BehaviorSubject<Mailbox[]>([]);

  set filterMailboxes(val: Mailbox[]) {
    this._filterMailboxes.next(val);<commit_msg>simplify code using refCount<commit_after>import { Observable, BehaviorSubject, ConnectableObservable, Subscription } from 'rxjs';

const PouchDB = require('pouchdb');
PouchDB.plugin(require('pouchdb-find'));

import { remote as ElectronRemote } from 'electron';

import { promiseLoop, pouchdb_observe, pouchdb_store } from './util';

import { Moment } from 'moment';

/*****************************************************************************/

export interface Mailbox {
  name: string;
  path: string;
  children?: Mailbox[];
  flags: string[];
  specialUse?: string[];
};

export interface Mailboxes {
  children: Mailbox[];
};

export interface Address {
  address: string;
  name: string;
};

export interface Envelope {
  _id: string;
  flags?: string[];
  envelope: {
    "date"?: string;
    "subject"?: string;
    "from"?: Address[];
    "sender"?: Address[];
    "reply-to"?: Address[];
    "to"?: Address[];
    "cc"?: Address[];
    "bcc"?: Address[];
    "message-id"?: string;
    "in-reply-to"?: string;
  };
};

export interface ImapModel {

  readonly mailboxes: Observable<Mailboxes>;
//  readonly addresses: Observable<Address[]>;
//  readonly flags: Observable<string[]>;

//  readonly filteredDates: Observable<Moment[]>;           // ignoring Date filter

//  filterStartDate: Moment;
//  filterEndDate: Moment;

  readonly filteredMessages: Observable<Envelope[]>;

//  filterAddresses: {addr:Address,hdr?:string}[];
  filterMailboxes: Mailbox[];
//  filterFlags: {flag:string,invert:boolean}[];

//  countMsgPerAddress(addr:Address,hdr?:string): Observable<Number>;
  countMsgPerMailbox(mbx:Mailbox): Observable<Number>;
//  countMsgPerFlag(flag:string): Observable<Number>;
};


/*****************************************************************************/

export class AccountData {
  host: string = "";
  port: number = 0;
  user: string = "";
  pass: string = "";
};

/*****************************************************************************/

export class ImapCache implements ImapModel {
  private emailjsImapClient: any;
  private _accountData = new AccountData;

  private db: any;
  private _isOpen = false;

  isOpen(): boolean {
    return this._isOpen;
  }

  accountData(): AccountData {
    return this._accountData;
  }

  dbAccess(): any {
    return this.db;
  }

  /*
   * raw db access
   */

  retrieve(id: string): Promise<any> {
    return this.db.get(id);
  }

  retrieve_by_prefix(prefix:string): Promise<any[]> {
    return this.db.find({
      selector: { $and: [
        { _id: { $gte: prefix } },
        { _id: { $lte: prefix + '\uffff' } }
      ]},
    }).then((result:{docs:any[]})=>result.docs);
  }

  observe(id: string): Observable<any> {
    return pouchdb_observe(this.db,{
      selector: { _id: id },
      aggregate: true,
    }).filter(v=>v.length==1).map(v=>v[0]);
  }

  observe_by_prefix(prefix:string): Observable<any[]> {
    return pouchdb_observe(this.db,{
      selector: { $and: [
        { _id: { $gte: prefix } },
        { _id: { $lte: prefix + '\uffff' } }
      ]},
      aggregate: true
    });
  }

  store(newdoc: any): Promise<any> {
    return pouchdb_store(this.db, newdoc);
  }

  store_bulk(newdocs: any[]): Promise<any> {
    return this.db.bulkDocs(newdocs);
  }


  allMessages: Observable<Envelope[]>;

  constructor(path: string = undefined) {
    if(!path)
      path = (ElectronRemote ? ElectronRemote.app.getPath('userData') : '.') + "/imapcache"

    this.db = new PouchDB(path);

    this.db.info().catch((err)=>{
      console.error("failed to open PouchDB: " + path);
      console.error(err);
      this.db.close();
      this.db = undefined
    }).then((info)=>{
      console.info("successfully opened PouchDB: " + path);
      console.info(info);
      this._isOpen = true;
    });

    this.retrieve("account").then((doc) => {
      this._accountData = doc as AccountData;
    }).catch(()=>{
    });

    this.allMessages = (this.observe_by_prefix("envelope:") as Observable<Envelope[]>).publishBehavior([]).refCount();
  }

  close(): void {
    if(this.db) {
      this.db.close();
      this.db = undefined;
      this._isOpen = false;
    }
  }

  get mailboxes(): Observable<Mailboxes> {
    return this.observe("mailboxes") as Observable<Mailboxes>;
  }

  private _filterMailboxes = new BehaviorSubject<Mailbox[]>([]);

  set filterMailboxes(val: Mailbox[]) {
    this._filterMailboxes.next(val);
{'fraction_non_alphanumeric': 0.21082525893752088, 'fraction_numerical': 0.0006682258603407952, 'mean_word_length': 3.645628556647698, 'pattern_counts': {'":': 0, '<': 42, '<?xml version=': 0, '>': 56, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5676230', 'n_tokens_mistral': 2885, 'n_tokens_neox': 2812, 'n_words': 573}