<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.medusa',
  name: 'MedusaConsensusDAO',
  extends: 'foam.dao.ProxyDAO',

  implements: [
    'foam.core.ContextAgent',
    'foam.nanos.NanoService'
  ],

  documentation: `Receive Entry's from the Nodes. Test for consensus on hash, cleanup, and notify.`,

  javaImports: [
    'foam.core.Agency',
    'foam.core.AgencyTimerTask',
    'foam.core.FObject',
    'foam.dao.ArraySink',
    'foam.dao.DAO',
    'foam.dao.DOP',
    'foam.lib.json.JSONParser',
    'static foam.mlang.MLang.AND',
    'static foam.mlang.MLang.COUNT',
    'static foam.mlang.MLang.EQ',
    'static foam.mlang.MLang.GT',
    'static foam.mlang.MLang.GTE',
    'static foam.mlang.MLang.MIN',
    'static foam.mlang.MLang.NEQ',
    'foam.mlang.sink.Count',
    'foam.mlang.sink.Min',
    'foam.nanos.logger.PrefixLogger',
    'foam.nanos.logger.Logger',
    'foam.nanos.pm.PM',
    'foam.util.SafetyUtil',
    'java.util.Arrays',
    'java.util.ArrayList',
    'java.util.concurrent.atomic.AtomicInteger',
    'java.util.HashMap',
    'java.util.List',
    'java.util.Map',
    'java.util.Timer'
  ],

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(foam.java.Code.create({
          data: `
  protected Object indexLock_ = new Object();
  protected Object promoteLock_ = new Object();
          `
        }));
      }
    }
  ],

  constants: [
    {
      documentation: 'starting at 2 as indexes 1 and 2 are used to prime the system.',
      name: 'INDEX_OFFSET',
      type: 'Long',
      value: 2
    }
  ],

  properties: [
    {
      name: 'index',
      class: 'Long',
      factory: function() { return this.INDEX_OFFSET; },
      javaFactory: 'return INDEX_OFFSET;',
      visibility: 'RO',
    },
    {
      name: 'timerInterval',
      class: 'Long',
      value: 10000
    },
    {
      name: 'initialTimerDelay',
      class: 'Long',
      value: 60000
    },
    {
      name: 'promoterRunning',
      class: 'Boolean',
      value: false
    },
    {
      name: 'logger',
      class: 'FObjectProperty',
      of: 'foam.nanos.logger.Logger',
      visibility: 'HIDDEN',
      javaFactory: `
        return new PrefixLogger(new Object[] {
          this.getClass().getSimpleName()
        }, (Logger) getX().get("logger"));
      `
    }
  ],

  methods: [
    {
      name: 'put_',
      javaCode: `
      PM pm = PM.create(x, this.getClass().getSimpleName(), "put");
      MedusaEntry entry = (MedusaEntry) obj;
      Object id = entry.getProperty("id");
      ReplayingInfo replaying = (ReplayingInfo) x.get("replayingInfo");
      getLogger().debug("put", getIndex(), replaying.getReplayIndex(), entry.toSummary(), entry.getNode());

      try {
        MedusaEntry existing = (MedusaEntry) getDelegate().find_(x, id);
        if ( existing != null &&
             existing.getPromoted() ) {
         // getLogger().debug("put", getIndex(), entry.toSummary(), entry.getNode(), "discarding");
         return existing;
        }

        synchronized ( id.toString().intern() ) {
          existing = (MedusaEntry) getDelegate().find_(x, id);
          if ( existing != null ) {
            if ( existing.getPromoted() ) {
              // getLogger().debug("put", getIndex(), entry.toSummary(), entry.getNode(), "discarding", "in-lock");
              return entry;
            }
          } else {
            existing = entry;
          }

          // NOTE: all this business with the nested Maps to avoid
          // a mulitipart id (index,hash) on MedusaEntry, as presently
          // it is a huge performance impact.
          Map<String, Map> hashes = existing.getConsensusHashes();
          Map<String, Object> nodes = hashes.get(entry.getHash());
          if ( nodes == null ) {
            nodes = new HashMap<String, Object>();
            hashes.put(entry.getHash(), nodes);
          }
          if ( nodes.get(entry.getNode()) == null ) {
            // getLogger().debug("put", "nodes", "put", entry.toSummary(), entry.getNode());
            nodes.put(entry.getNode(), entry);
          }
          ClusterConfigSupport support = (ClusterConfigSupport) x.get("clusterConfigSupport");
          if ( nodes.size() >= support.getNodeQuorum() &&
               entry.getIndex() == getIndex() + 1 ) {
            entry.setConsensusCount(nodes.size());
            entry.setConsensusNodes(nodes.keySet().toArray(new String[0]));
            MedusaEntry.CONSENSUS_HASHES.clear(entry);
            promote(x, entry);
          } else {
            hashes.put(entry.getHash(), nodes);
            existing.setConsensusHashes(hashes);
            if ( nodes.size() > existing.getConsensusCount() ) {
              existing.setConsensusCount(nodes.size());
              existing.setConsensusNodes(nodes.keySet().toArray(new String[0]));
            }
            existing = (MedusaEntry) getDelegate().put_(x, existing);
            // getLogger().debug("put", getIndex(), entry.toSummary(), "existing", Arrays.toString(existing.getConsensusNodes()));
          }
          return entry;
        }
      } finally {
        pm.log(x);
      }
      `
    },
    {
      documentation: 'Make an entry available for Dagger hashing.',
      name: 'promote',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'foam.nanos.medusa.MedusaEntry'
        }
      ],
      type: 'foam.nanos.medusa.MedusaEntry',
      javaCode: `
      PM pm = PM.create(x, this.getOwnClassInfo(), "promote");
      ReplayingInfo replaying = (ReplayingInfo) x.get("replayingInfo");
      // getLogger().debug("promote", entry.getIndex(), getIndex(), replaying.getReplayIndex(), replaying.getReplaying());
      try {

        DaggerService dagger = (DaggerService) x.get("daggerService");
        dagger.verify(x, entry);
        entry.setVerified(true);
        synchronized ( indexLock_ ) {
          if ( entry.getIndex() == getIndex() + 1 ) {
            setIndex(entry.getIndex());
            replaying.setIndex(getIndex());
            if ( replaying.getReplaying() &&
                 getIndex() >= replaying.getReplayIndex() ) {
              getLogger().info("promote", "replayComplete", "index");
              ((DAO) x.get("localMedusaEntryDAO")).cmd(new ReplayCompleteCmd());
            }
          }
        }

        dagger.updateLinks(x, entry);

        entry = mdao(x, entry);

        entry.setPromoted(true);
        entry = (MedusaEntry) getDelegate().put_(x, entry);

        // Notify any blocked Primary puts
        MedusaRegistry registry = (MedusaRegistry) x.get("medusaRegistry");
        registry.notify(x, entry);
      } finally {
        pm.log(x);
      }
      return entry;
      `
    },
    {
      documentation: 'NanoService implementation.',
      name: 'start',
      javaCode: `
      getLogger().debug("start");
      ClusterConfigSupport support = (ClusterConfigSupport) getX().get("clusterConfigSupport");
      Timer timer = new Timer(this.getClass().getSimpleName());
      timer.scheduleAtFixedRate(
        new AgencyTimerTask(getX(), support.getThreadPoolName(), this),
        getInitialTimerDelay(),
        getTimerInterval());
      `
    },
    {
      documentation: 'ContextAgent implementation. Handling out of order consensus updates. Check if next (index + 1) has reach consensus and promote.',
      name: 'execute',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `
      try {
        synchronized ( promoteLock_ ) {
          if ( getPromoterRunning() ) {
            getLogger().debug("promoter already running");
            return;
          }
          setPromoterRunning(true);
        }
        ClusterConfigSupport support = (ClusterConfigSupport) x.get("clusterConfigSupport");
        ClusterConfig config = support.getConfig(x, support.getConfigId());
        while ( true ) {
          MedusaEntry entry = null;
          ReplayingInfo replaying = (ReplayingInfo) x.get("replayingInfo");
          if ( replaying.getReplaying() &&
               getIndex() >= replaying.getReplayIndex() ) {
            ((DAO) x.get("localMedusaEntryDAO")).cmd(new ReplayCompleteCmd());
          }

          Long nextIndex = getIndex() + 1;
          MedusaEntry next = (MedusaEntry) getDelegate().find_(x, nextIndex);
          if ( next != null ) {
            Map<Object, Map> hashes = next.getConsensusHashes();
            for ( Map<String, MedusaEntry> nodes : hashes.values() ) {
              if ( nodes.size() >= support.getNodeQuorum() ) {
                if ( entry == null ) {
                  for ( MedusaEntry e : nodes.values() ) {
                    entry = e;
                    break;
                  }
                  entry.setConsensusCount(nodes.size());
                  entry.setConsensusNodes(nodes.keySet().toArray(new String[0]));
                  MedusaEntry.CONSENSUS_HASHES.clear(entry);
                  promote(x, entry);
                } else {
                  getLogger().error("promoter", next, "multiple found with consensus", next.toSummary(), next.getConsensusCount(), support.getNodeQuorum());
                  // TODO: Halt system
                }
              }
            }
            if ( entry == null ) {
              getLogger().warning("promoter", next, "no consensus", next.toSummary(), next.getConsensusCount(), support.getNodeQuorum());
            }
          }
          if ( entry == null ) {
            break;
          }
        }
      } catch ( Throwable e ) {
        getLogger().error("promoter", "execute", e.getMessage(), e);
        // TODO: Alarm
      } finally {
        // getLogger().debug("promoter", "execute", "exit");
        synchronized ( promoteLock_ ) {
          setPromoterRunning(false);
        }
      }
     `
    },
    {
      documentation: 'Make an entry available for Dagger hashing.',
      name: 'mdao',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'foam.nanos.medusa.MedusaEntry'
        }
      ],
      type: 'foam.nanos.medusa.MedusaEntry',
      javaCode: `
      PM pm = PM.create(x, this.getOwnClassInfo(), "mdao");
      // getLogger().debug("mdao", entry.getIndex());

      try {
        ClusterConfigSupport support = (ClusterConfigSupport) x.get("clusterConfigSupport");
        ClusterConfig config = support.getConfig(x, support.getConfigId());
        ReplayingInfo replaying = (ReplayingInfo) x.get("replayingInfo");
        if ( replaying.getReplaying() ||
             ! config.getIsPrimary() ) {
          String data = entry.getData();
          getLogger().debug("mdao", entry.getIndex(), entry.getDop().getLabel()); //, data);
          if ( ! SafetyUtil.isEmpty(data) ) {
            DAO dao = support.getMdao(x, entry.getNSpecName());
            Class cls = null;
            if ( dao instanceof foam.dao.ProxyDAO ) {
              cls = ((foam.dao.ProxyDAO) dao).getOf().getObjClass();
            } else if ( dao instanceof foam.dao.MDAO ) {
              cls = ((foam.dao.MDAO) dao).getOf().getObjClass();
            } else {
              getLogger().error("mdao", entry.getIndex(), entry.getNSpecName(), dao.getClass().getSimpleName(), "Unable to determine class", data);
              throw new RuntimeException("Error parsing data.");
            }
            FObject nu = x.create(JSONParser.class).parseString(entry.getData(), cls);
            if ( nu == null ) {
              getLogger().error("Failed to parse", entry.getIndex(), entry.getNSpecName(), cls, entry.getData());
              throw new RuntimeException("Error parsing data.");
            }

            FObject old = dao.find_(x, nu.getProperty("id"));
            if (  old != null ) {
              nu = old.fclone().copyFrom(nu);
            }

            if ( DOP.PUT == entry.getDop() ) {
              dao.put_(x, nu);
            } else if ( DOP.REMOVE == entry.getDop() ) {
              dao.remove_(x, nu);
            } else {
              getLogger().warning("Unsupported operation", entry.getDop().getLabel());
              throw new UnsupportedOperationException(entry.getDop().getLabel());
            }
          }
        }

        return entry;
      } catch (Throwable t) {
        getLogger().error(t);
        throw t;
        // TODO: Alarm
      } finally {
        pm.log(x);
      }
      `
    }
  ]
});
<commit_msg>Move constant to configuration property<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.medusa',
  name: 'MedusaConsensusDAO',
  extends: 'foam.dao.ProxyDAO',

  implements: [
    'foam.core.ContextAgent',
    'foam.nanos.NanoService'
  ],

  documentation: `Receive Entry's from the Nodes. Test for consensus on hash, cleanup, and notify.`,

  javaImports: [
    'foam.core.Agency',
    'foam.core.AgencyTimerTask',
    'foam.core.FObject',
    'foam.dao.ArraySink',
    'foam.dao.DAO',
    'foam.dao.DOP',
    'foam.lib.json.JSONParser',
    'static foam.mlang.MLang.AND',
    'static foam.mlang.MLang.COUNT',
    'static foam.mlang.MLang.EQ',
    'static foam.mlang.MLang.GT',
    'static foam.mlang.MLang.GTE',
    'static foam.mlang.MLang.MIN',
    'static foam.mlang.MLang.NEQ',
    'foam.mlang.sink.Count',
    'foam.mlang.sink.Min',
    'foam.nanos.logger.PrefixLogger',
    'foam.nanos.logger.Logger',
    'foam.nanos.pm.PM',
    'foam.util.SafetyUtil',
    'java.util.Arrays',
    'java.util.ArrayList',
    'java.util.concurrent.atomic.AtomicInteger',
    'java.util.HashMap',
    'java.util.List',
    'java.util.Map',
    'java.util.Timer'
  ],

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(foam.java.Code.create({
          data: `
  protected Object indexLock_ = new Object();
  protected Object promoteLock_ = new Object();
          `
        }));
      }
    }
  ],

  properties: [
    {
      name: 'index',
      class: 'Long',
      factory: function() { return this.initialIndexOffset; },
      javaFactory: 'return getInitialIndexOffset();',
      visibility: 'RO',
    },
    {
      documentation: 'Starting at 2 as indexes 1 and 2 are used to prime the system.',
      name: 'initialIndexOffset',
      class: 'Long',
      value: 2
    },
    {
      name: 'timerInterval',
      class: 'Long',
      value: 10000
    },
    {
      name: 'initialTimerDelay',
      class: 'Long',
      value: 60000
    },
    {
      name: 'promoterRunning',
      class: 'Boolean',
      value: false
    },
    {
      name: 'logger',
      class: 'FObjectProperty',
      of: 'foam.nanos.logger.Logger',
      visibility: 'HIDDEN',
      javaFactory: `
        return new PrefixLogger(new Object[] {
          this.getClass().getSimpleName()
        }, (Logger) getX().get("logger"));
      `
    }
  ],

  methods: [
    {
      name: 'put_',
      javaCode: `
      PM pm = PM.create(x, this.getClass().getSimpleName(), "put");
      MedusaEntry entry = (MedusaEntry) obj;
      Object id = entry.getProperty("id");
      ReplayingInfo replaying = (ReplayingInfo) x.get("replayingInfo");
      getLogger().debug("put", getIndex(), replaying.getReplayIndex(), entry.toSummary(), entry.getNode());

      try {
        MedusaEntry existing = (MedusaEntry) getDelegate().find_(x, id);
        if ( existing != null &&
             existing.getPromoted() ) {
         getLogger().debug("put", getIndex(), entry.toSummary(), entry.getNode(), "discarding");
         return existing;
        }

        synchronized ( id.toString().intern() ) {
          existing = (MedusaEntry) getDelegate().find_(x, id);
          if ( existing != null ) {
            if ( existing.getPromoted() ) {
              getLogger().debug("put", getIndex(), entry.toSummary(), entry.getNode(), "discarding", "in-lock");
              return entry;
            }
          } else {
            existing = entry;
          }

          // NOTE: all this business with the nested Maps to avoid
          // a mulitipart id (index,hash) on MedusaEntry, as presently
          // it is a huge performance impact.
          Map<String, Map> hashes = existing.getConsensusHashes();
          Map<String, Object> nodes = hashes.get(entry.getHash());
          if ( nodes == null ) {
            nodes = new HashMap<String, Object>();
            hashes.put(entry.getHash(), nodes);
          }
          if ( nodes.get(entry.getNode()) == null ) {
            // getLogger().debug("put", "nodes", "put", entry.toSummary(), entry.getNode());
            nodes.put(entry.getNode(), entry);
          }
          ClusterConfigSupport support = (ClusterConfigSupport) x.get("clusterConfigSupport");
          if ( nodes.size() >= support.getNodeQuorum() &&
               entry.getIndex() == getIndex() + 1 ) {
            entry.setConsensusCount(nodes.size());
            entry.setConsensusNodes(nodes.keySet().toArray(new String[0]));
            MedusaEntry.CONSENSUS_HASHES.clear(entry);
            promote(x, entry);
          } else {
            hashes.put(entry.getHash(), nodes);
            existing.setConsensusHashes(hashes);
            if ( nodes.size() > existing.getConsensusCount() ) {
              existing.setConsensusCount(nodes.size());
              existing.setConsensusNodes(nodes.keySet().toArray(new String[0]));
            }
            existing = (MedusaEntry) getDelegate().put_(x, existing);
            // getLogger().debug("put", getIndex(), entry.toSummary(), "existing", Arrays.toString(existing.getConsensusNodes()));
          }
          return entry;
        }
      } finally {
        pm.log(x);
      }
      `
    },
    {
      documentation: 'Make an entry available for Dagger hashing.',
      name: 'promote',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'foam.nanos.medusa.MedusaEntry'
        }
      ],
      type: 'foam.nanos.medusa.MedusaEntry',
      javaCode: `
      PM pm = PM.create(x, this.getOwnClassInfo(), "promote");
      ReplayingInfo replaying = (ReplayingInfo) x.get("replayingInfo");
      // getLogger().debug("promote", entry.getIndex(), getIndex(), replaying.getReplayIndex(), replaying.getReplaying());
      try {

        DaggerService dagger = (DaggerService) x.get("daggerService");
        dagger.verify(x, entry);
        entry.setVerified(true);
        synchronized ( indexLock_ ) {
          if ( entry.getIndex() == getIndex() + 1 ) {
            setIndex(entry.getIndex());
            replaying.setIndex(getIndex());
            if ( replaying.getReplaying() &&
                 getIndex() >= replaying.getReplayIndex() ) {
              getLogger().info("promote", "replayComplete", "index");
              ((DAO) x.get("localMedusaEntryDAO")).cmd(new ReplayCompleteCmd());
            }
          }
        }

        dagger.updateLinks(x, entry);

        entry = mdao(x, entry);

        entry.setPromoted(true);
        entry = (MedusaEntry) getDelegate().put_(x, entry);

        // Notify any blocked Primary puts
        MedusaRegistry registry = (MedusaRegistry) x.get("medusaRegistry");
        registry.notify(x, entry);
      } finally {
        pm.log(x);
      }
      return entry;
      `
    },
    {
      documentation: 'NanoService implementation.',
      name: 'start',
      javaCode: `
      getLogger().debug("start");
      ClusterConfigSupport support = (ClusterConfigSupport) getX().get("clusterConfigSupport");
      Timer timer = new Timer(this.getClass().getSimpleName());
      timer.scheduleAtFixedRate(
        new AgencyTimerTask(getX(), support.getThreadPoolName(), this),
        getInitialTimerDelay(),
        getTimerInterval());
      `
    },
    {
      documentation: 'ContextAgent implementation. Handling out of order consensus updates. Check if next (index + 1) has reach consensus and promote.',
      name: 'execute',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `
      try {
        synchronized ( promoteLock_ ) {
          if ( getPromoterRunning() ) {
            getLogger().debug("promoter already running");
            return;
          }
          setPromoterRunning(true);
        }
        ClusterConfigSupport support = (ClusterConfigSupport) x.get("clusterConfigSupport");
        ClusterConfig config = support.getConfig(x, support.getConfigId());
        while ( true ) {
          MedusaEntry entry = null;
          ReplayingInfo replaying = (ReplayingInfo) x.get("replayingInfo");
          if ( replaying.getReplaying() &&
               getIndex() >= replaying.getReplayIndex() ) {
            ((DAO) x.get("localMedusaEntryDAO")).cmd(new ReplayCompleteCmd());
          }

          Long nextIndex = getIndex() + 1;
          MedusaEntry next = (MedusaEntry) getDelegate().find_(x, nextIndex);
          if ( next != null ) {
            Map<Object, Map> hashes = next.getConsensusHashes();
            for ( Map<String, MedusaEntry> nodes : hashes.values() ) {
              if ( nodes.size() >= support.getNodeQuorum() ) {
                if ( entry == null ) {
                  for ( MedusaEntry e : nodes.values() ) {
                    entry = e;
                    break;
                  }
                  entry.setConsensusCount(nodes.size());
                  entry.setConsensusNodes(nodes.keySet().toArray(new String[0]));
                  MedusaEntry.CONSENSUS_HASHES.clear(entry);
                  promote(x, entry);
                } else {
                  getLogger().error("promoter", next, "multiple found with consensus", next.toSummary(), next.getConsensusCount(), support.getNodeQuorum());
                  // TODO: Halt system
                }
              }
            }
            if ( entry == null ) {
              getLogger().warning("promoter", next, "no consensus", next.toSummary(), next.getConsensusCount(), support.getNodeQuorum());
            }
          }
          if ( entry == null ) {
            break;
          }
        }
      } catch ( Throwable e ) {
        getLogger().error("promoter", "execute", e.getMessage(), e);
        // TODO: Alarm
      } finally {
        // getLogger().debug("promoter", "execute", "exit");
        synchronized ( promoteLock_ ) {
          setPromoterRunning(false);
        }
      }
     `
    },
    {
      documentation: 'Make an entry available for Dagger hashing.',
      name: 'mdao',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'foam.nanos.medusa.MedusaEntry'
        }
      ],
      type: 'foam.nanos.medusa.MedusaEntry',
      javaCode: `
      PM pm = PM.create(x, this.getOwnClassInfo(), "mdao");
      // getLogger().debug("mdao", entry.getIndex());

      try {
        ClusterConfigSupport support = (ClusterConfigSupport) x.get("clusterConfigSupport");
        ClusterConfig config = support.getConfig(x, support.getConfigId());
        ReplayingInfo replaying = (ReplayingInfo) x.get("replayingInfo");
        if ( replaying.getReplaying() ||
             ! config.getIsPrimary() ) {
          String data = entry.getData();
          getLogger().debug("mdao", entry.getIndex(), entry.getDop().getLabel()); //, data);
          if ( ! SafetyUtil.isEmpty(data) ) {
            DAO dao = support.getMdao(x, entry.getNSpecName());
            Class cls = null;
            if ( dao instanceof foam.dao.ProxyDAO ) {
              cls = ((foam.dao.ProxyDAO) dao).getOf().getObjClass();
            } else if ( dao instanceof foam.dao.MDAO ) {
              cls = ((foam.dao.MDAO) dao).getOf().getObjClass();
            } else {
              getLogger().error("mdao", entry.getIndex(), entry.getNSpecName(), dao.getClass().getSimpleName(), "Unable to determine class", data);
              throw new RuntimeException("Error parsing data.");
            }
            FObject nu = x.create(JSONParser.class).parseString(entry.getData(), cls);
            if ( nu == null ) {
              getLogger().error("Failed to parse", entry.getIndex(), entry.getNSpecName(), cls, entry.getData());
              throw new RuntimeException("Error parsing data.");
            }

            FObject old = dao.find_(x, nu.getProperty("id"));
            if (  old != null ) {
              nu = old.fclone().copyFrom(nu);
            }

            if ( DOP.PUT == entry.getDop() ) {
              dao.put_(x, nu);
            } else if ( DOP.REMOVE == entry.getDop() ) {
              dao.remove_(x, nu);
            } else {
              getLogger().warning("Unsupported operation", entry.getDop().getLabel());
              throw new UnsupportedOperationException(entry.getDop().getLabel());
            }
          }
        }

        return entry;
      } catch (Throwable t) {
        getLogger().error(t);
        throw t;
        // TODO: Alarm
      } finally {
        pm.log(x);
      }
      `
    }
  ]
});
<|endoftext|>


<commit_before>$(document).ready(function(){
	// add the responsive image class to all images
    $('img').each(function(){
        $(this).addClass('img-responsive');
    });

    // make all links in articles open in new window/tab
    $('.body_text a').attr('target', '_blank');

	// setup mermaid charting
	mermaid.initialize({startOnLoad: true});

	// add the table class to all tables
    $('table').each(function(){
        $(this).addClass('table table-hover');
    });

	// add the token field to the keywords input
	$('#frm_kb_keywords').tokenfield();

    if($('#editor').length){
        // setup editors
        var simplemde = new SimpleMDE({
            element: $('#editor')[0],
            toolbar: ['bold', 'italic', 'heading', '|', 'quote', 'unordered-list', 'ordered-list', '|', 'link', 'image', '|', 'table', 'horizontal-rule', 'guide']
        });

        // setup inline attachments
        inlineAttachment.editors.codemirror4.attach(simplemde.codemirror, {uploadUrl: '/file/upload_file'});

        // do initial convert on load
        convertTextAreaToMarkdown();

        // attach to editor changes and update preview
        simplemde.codemirror.on('change', function(){
            convertTextAreaToMarkdown();
        });
    }

    // if in the editor, trap ctrl+s and cmd+s shortcuts and save the article
    if($('#frm_editor').val() === 'true'){
        $(window).bind('keydown', function(event){
            if(event.ctrlKey || event.metaKey){
                if(String.fromCharCode(event.which).toLowerCase() === 's'){
                    event.preventDefault();
                    $('#frm_edit_kb_save').click();
                }
            }
        });
    }

	// Call to API for a change to the published state of a KB
	$("input[class='published_state']").change(function(){
		$.ajax({
			method: 'POST',
			url: '/published_state',
			data: {id: this.id, state: this.checked}
		})
		.success(function(msg){
            show_notification(msg, 'success');
        })
        .error(function(msg){
            show_notification(msg.responseText, 'danger');
        });
	});

    // convert editor markdown to HTML and display in #preview div
    function convertTextAreaToMarkdown(){
        var classy = window.markdownItClassy;
        var mark_it_down = window.markdownit({html: true, linkify: true, typographer: true, breaks: true});
        mark_it_down.use(classy);
        var html = mark_it_down.render(simplemde.value());

        // add responsive images and tables
        var fixed_html = html.replace(/<img/g, "<img class='img-responsive' ");
        fixed_html = fixed_html.replace(/<table/g, "<table class='table table-hover' ");
        $('#preview').html(fixed_html);
    }

	// Call to API to check if a permalink is available
	$('#validate_permalink').click(function(){
		if($('#frm_kb_permalink').val() !== ''){
			$.ajax({
				method: 'POST',
				url: '/api/validate_permalink',
				data: {'permalink': $('#frm_kb_permalink').val(), 'doc_id': $('#frm_kb_id').val()}
			})
			.success(function(msg){
				show_notification(msg, 'success');
			})
			.error(function(msg){
				show_notification(msg.responseText, 'danger');
			});
		}else{
			show_notification('Please enter a permalink to validate', 'danger');
		}
	});

	// generates a random permalink
	$('#generate_permalink').click(function(){
		var min = 100000;
		var max = 999999;
		var num = Math.floor(Math.random() * (max - min + 1)) + min;
		$('#frm_kb_permalink').val(num);
	});

	// applies an article filter
	$('#btn_articles_filter').click(function(){
		window.location.href = '/articles/' + $('#article_filter').val();
	});

	// resets the article filter
	$('#btn_articles_reset').click(function(){
		window.location.href = '/articles';
	});

	// search button click event
	$('#btn_search').click(function(event){
		if($('#frm_search').val() === ''){
			show_notification('Please enter a search value', 'danger');
			event.preventDefault();
		}
	});

	if($('#input_notify_message').val() !== ''){
		// save values from inputs
		var message_val = $('#input_notify_message').val();
		var message_type_val = $('#input_notify_message_type').val();

		// clear inputs
		$('#input_notify_message').val('');
		$('#input_notify_message_type').val('');

		// alert
		show_notification(message_val, message_type_val, false);
	}
});

// Calls the API to delete a file
function file_delete_confirm(img, id){
	if(window.confirm('Are you sure you want to delete the file?')){
		$.ajax({
			method: 'POST',
			url: '/file/delete',
			data: {img: img}
		})
		.success(function(msg){
			$('#file-' + id).remove();
			show_notification(msg, 'success');
		})
		.error(function(msg){
			show_notification(msg, 'danger');
		});
	}
}

// show notification popup
function show_notification(msg, type, reload_page){
    // defaults to false
    reload_page = reload_page || false;

    $('#notify_message').removeClass();
    $('#notify_message').addClass('notify_message-' + type);
    $('#notify_message').html(msg);
    $('#notify_message').slideDown(600).delay(1200).slideUp(600, function(){
        if(reload_page === true){
            location.reload();
        }
    });
}

function search_form(id){
	$('form#' + id).submit();
}
<commit_msg>Fix #65 - Added auto scroll. Thannks @gallarotti!<commit_after>$(document).ready(function(){
	// add the responsive image class to all images
    $('img').each(function(){
        $(this).addClass('img-responsive');
    });

    // make all links in articles open in new window/tab
    $('.body_text a').attr('target', '_blank');

	// setup mermaid charting
	mermaid.initialize({startOnLoad: true});

	// add the table class to all tables
    $('table').each(function(){
        $(this).addClass('table table-hover');
    });

	// add the token field to the keywords input
	$('#frm_kb_keywords').tokenfield();

    if($('#editor').length){
        // setup editors
        var simplemde = new SimpleMDE({
            element: $('#editor')[0],
            toolbar: ['bold', 'italic', 'heading', '|', 'quote', 'unordered-list', 'ordered-list', '|', 'link', 'image', '|', 'table', 'horizontal-rule', 'guide']
        });

        // setup inline attachments
        inlineAttachment.editors.codemirror4.attach(simplemde.codemirror, {uploadUrl: '/file/upload_file'});

        // do initial convert on load
        convertTextAreaToMarkdown();

        // attach to editor changes and update preview
        simplemde.codemirror.on('change', function(){
            convertTextAreaToMarkdown();
        });

        // auto scrolls the simpleMDE preview pane
        var preview = document.getElementById('preview');
        if(preview !== null){
            // Syncs scroll  editor -> preview
            var cScroll = false;
            var pScroll = false;
            simplemde.codemirror.on('scroll', function(v){
                if(cScroll){
                    cScroll = false;
                    return;
                }
                pScroll = true;
                var height = v.getScrollInfo().height - v.getScrollInfo().clientHeight;
                var ratio = parseFloat(v.getScrollInfo().top) / height;
                var move = (preview.scrollHeight - preview.clientHeight) * ratio;
                preview.scrollTop = move;
            });

            // Syncs scroll  preview -> editor
            preview.onscroll = function(){
                if(pScroll){
                    pScroll = false;
                    return;
                }
                cScroll = true;
                var height = preview.scrollHeight - preview.clientHeight;
                var ratio = parseFloat(preview.scrollTop) / height;
                var move = (simplemde.codemirror.getScrollInfo().height - simplemde.codemirror.getScrollInfo().clientHeight) * ratio;
                simplemde.codemirror.scrollTo(0, move);
            };
        }
    }

    // if in the editor, trap ctrl+s and cmd+s shortcuts and save the article
    if($('#frm_editor').val() === 'true'){
        $(window).bind('keydown', function(event){
            if(event.ctrlKey || event.metaKey){
                if(String.fromCharCode(event.which).toLowerCase() === 's'){
                    event.preventDefault();
                    $('#frm_edit_kb_save').click();
                }
            }
        });
    }

	// Call to API for a change to the published state of a KB
	$("input[class='published_state']").change(function(){
		$.ajax({
			method: 'POST',
			url: '/published_state',
			data: {id: this.id, state: this.checked}
		})
		.success(function(msg){
            show_notification(msg, 'success');
        })
        .error(function(msg){
            show_notification(msg.responseText, 'danger');
        });
	});

    // convert editor markdown to HTML and display in #preview div
    function convertTextAreaToMarkdown(){
        var classy = window.markdownItClassy;
        var mark_it_down = window.markdownit({html: true, linkify: true, typographer: true, breaks: true});
        mark_it_down.use(classy);
        var html = mark_it_down.render(simplemde.value());

        // add responsive images and tables
        var fixed_html = html.replace(/<img/g, "<img class='img-responsive' ");
        fixed_html = fixed_html.replace(/<table/g, "<table class='table table-hover' ");
        $('#preview').html(fixed_html);
    }

	// Call to API to check if a permalink is available
	$('#validate_permalink').click(function(){
		if($('#frm_kb_permalink').val() !== ''){
			$.ajax({
				method: 'POST',
				url: '/api/validate_permalink',
				data: {'permalink': $('#frm_kb_permalink').val(), 'doc_id': $('#frm_kb_id').val()}
			})
			.success(function(msg){
				show_notification(msg, 'success');
			})
			.error(function(msg){
				show_notification(msg.responseText, 'danger');
			});
		}else{
			show_notification('Please enter a permalink to validate', 'danger');
		}
	});

	// generates a random permalink
	$('#generate_permalink').click(function(){
		var min = 100000;
		var max = 999999;
		var num = Math.floor(Math.random() * (max - min + 1)) + min;
		$('#frm_kb_permalink').val(num);
	});

	// applies an article filter
	$('#btn_articles_filter').click(function(){
		window.location.href = '/articles/' + $('#article_filter').val();
	});

	// resets the article filter
	$('#btn_articles_reset').click(function(){
		window.location.href = '/articles';
	});

	// search button click event
	$('#btn_search').click(function(event){
		if($('#frm_search').val() === ''){
			show_notification('Please enter a search value', 'danger');
			event.preventDefault();
		}
	});

	if($('#input_notify_message').val() !== ''){
		// save values from inputs
		var message_val = $('#input_notify_message').val();
		var message_type_val = $('#input_notify_message_type').val();

		// clear inputs
		$('#input_notify_message').val('');
		$('#input_notify_message_type').val('');

		// alert
		show_notification(message_val, message_type_val, false);
	}
});

// Calls the API to delete a file
function file_delete_confirm(img, id){
	if(window.confirm('Are you sure you want to delete the file?')){
		$.ajax({
			method: 'POST',
			url: '/file/delete',
			data: {img: img}
		})
		.success(function(msg){
			$('#file-' + id).remove();
			show_notification(msg, 'success');
		})
		.error(function(msg){
			show_notification(msg, 'danger');
		});
	}
}

// show notification popup
function show_notification(msg, type, reload_page){
    // defaults to false
    reload_page = reload_page || false;

    $('#notify_message').removeClass();
    $('#notify_message').addClass('notify_message-' + type);
    $('#notify_message').html(msg);
    $('#notify_message').slideDown(600).delay(1200).slideUp(600, function(){
        if(reload_page === true){
            location.reload();
        }
    });
}

function search_form(id){
	$('form#' + id).submit();
}
<|endoftext|>


<commit_before>CKEDITOR.config.baseHref = '/assets/ckeditor/';
CKEDITOR.config.height = 400;
CKEDITOR.config.width = '95.5%';
// CKEDITOR.config.toolbarStartupExpanded = false;
CKEDITOR.config.toolbar = 'Full';
CKEDITOR.config.toolbar_Full =
[
	{ name: 'document', items : [ 'Source','-','ShowBlocks'] },
	{ name: 'clipboard', items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] },
	{ name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock' ] },
	{ name: 'tools', items : [ 'Maximize' ] },
	{ name: 'links', items : [ 'Link','Unlink' ] },
	{ name: 'colors', items : [ 'TextColor','BGColor' ] },
	{ name: 'insert', items : [ 'Image','HorizontalRule','SpecialChar','PageBreak' ] },
	'/',
	{ name: 'styles', items : [ 'Styles','Format','Font','FontSize' ] },
	{ name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] }
];<commit_msg>Add Ckeditor allowedContent config<commit_after>CKEDITOR.config.allowedContent = true;
CKEDITOR.config.baseHref = '/assets/ckeditor/';
CKEDITOR.config.height = 400;
CKEDITOR.config.width = '95.5%';
// CKEDITOR.config.toolbarStartupExpanded = false;
CKEDITOR.config.toolbar = 'Full';
CKEDITOR.config.toolbar_Full =
[
	{ name: 'document', items : [ 'Source','-','ShowBlocks'] },
	{ name: 'clipboard', items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] },
	{ name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock' ] },
	{ name: 'tools', items : [ 'Maximize' ] },
	{ name: 'links', items : [ 'Link','Unlink' ] },
	{ name: 'colors', items : [ 'TextColor','BGColor' ] },
	{ name: 'insert', items : [ 'Image','HorizontalRule','SpecialChar','PageBreak' ] },
	'/',
	{ name: 'styles', items : [ 'Styles','Format','Font','FontSize' ] },
	{ name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] }
];<|endoftext|>


<commit_before>/**
 * @fileOverview FundCtrl angular controller
 */
'use strict';

define(['./module', 'darkwallet'],
function (controllers, DarkWallet) {

  controllers.controller('FundCtrl', ['$scope', 'modals', 'notify', '$brc', '_Filter', function($scope, modals, notify, $brc, _) {

  $scope.Object = Object;

  /**
   * Check if we have enough signatures and put them into the transaction
   */
  function finishSigning(fund, task) {
      var tx = fund.finishTransaction(task);
      if (tx) {
          $brc.broadcast(tx, task);
      }
      return tx;
  };

  /**
   * Import a signature
   */
  $scope.importFundSig = function(form, task) {

      var fund = $scope.pocket.fund;
      var added;
      try {
          added = fund.importSignature(form.text, task);
      } catch(e) {
          notify.error(_(e.message));
          return;
      }

      // Show some notification and finish task if we have all signatures.
      if (added) {
          if (finishSigning(fund, task)) {
              // Wait for notification from broadcasting
          } else {
              notify.success(_('Imported'), _('Signature imported'));
          }
      } else {
          notify.warning(_('Error importing'), _('Can\'t verify'));
      }
  };

  /**
   * Import a partial transaction into the fund
   */
  $scope.importFundTx = function(form) {
      if (!form.$valid) {
          return;
      }

      var fund = $scope.pocket.fund;
      var frontTask;

      try {
          frontTask = fund.importTransaction(form.newTx);
      } catch(e) {
          notify.error(_('Error importing'), _(e.message));
          return;
      }

      // Add to tasks
      notify.success(_('Added transaction'));
  };

  /**
   * Continue signing after getting the password
   */
  function finishSignFundTx(password, fund, task, inputs) {
      var identity = DarkWallet.getIdentity();

      var signed;
      try {
          signed = fund.signTransaction(password, task, inputs);
      } catch (e) {
          notify.warning(_('Invalid Password'));
          return;
      }

      if (!signed) {
          notify.warning(_('Transaction was already signed by us'));
          return;
      }
      if (finishSigning(fund, task)) {
          notify.success(_('Signed transaction and ready to go!'));
      } else {
          notify.success(_('Signed transaction'));
      }
  };

  /**
   * Sign a transaction with our keys
   */
  $scope.signFundTxForeign = function(foreignKey, task) {
      var fund = $scope.pocket.fund;
      var signed;

      try {
          signed = fund.signTxForeign(foreignKey, task);
      } catch (e) {
          notify.error(_('Error importing'), _(e.message));
      }

      if (!signed) {
          notify.warning(_('Could not sign with the given key'));
          return;
      }
      if (finishSigning(fund, task)) {
          notify.success(_('Signed transaction and ready to go!'));
      } else {
          notify.success(_('Signed transaction'));
      }

  }

  /**
   * Sign a transaction with our keys
   */
  $scope.signFundTx = function(task) {
      // import transaction here
      var fund = $scope.pocket.fund;

      var inputs = fund.getValidInputs(task.tx);

      if (inputs.length) {
          modals.password(_('Write your password'), function(password) { finishSignFundTx(password, fund, task, inputs); } );
      } else {
          notify.error(_('Error importing'), _('Transaction is not for this multisig'));
      }
  };

  var finishDelete = function(fund) {
      var identity = DarkWallet.getIdentity();
      identity.wallet.multisig.deleteFund(fund.multisig);
      $scope.selectPocket();
  };
  $scope.deleteFund = function(fund) {
      modals.open('confirm-delete', {name: fund.name, object: fund}, finishDelete);
  };

}]);
});
<commit_msg>Update tabs after importing a transaction.<commit_after>/**
 * @fileOverview FundCtrl angular controller
 */
'use strict';

define(['./module', 'darkwallet'],
function (controllers, DarkWallet) {

  controllers.controller('FundCtrl', ['$scope', 'modals', 'notify', '$brc', '$tabs', '_Filter', function($scope, modals, notify, $brc, $tabs, _) {

  $scope.Object = Object;

  /**
   * Check if we have enough signatures and put them into the transaction
   */
  function finishSigning(fund, task) {
      var tx = fund.finishTransaction(task);
      if (tx) {
          $brc.broadcast(tx, task);
      }
      return tx;
  };

  /**
   * Import a signature
   */
  $scope.importFundSig = function(form, task) {

      var fund = $scope.pocket.fund;
      var added;
      try {
          added = fund.importSignature(form.text, task);
      } catch(e) {
          notify.error(_(e.message));
          return;
      }

      // Show some notification and finish task if we have all signatures.
      if (added) {
          if (finishSigning(fund, task)) {
              // Wait for notification from broadcasting
          } else {
              notify.success(_('Imported'), _('Signature imported'));
          }
      } else {
          notify.warning(_('Error importing'), _('Can\'t verify'));
      }
  };

  /**
   * Import a partial transaction into the fund
   */
  $scope.importFundTx = function(form) {
      if (!form.$valid) {
          return;
      }

      var fund = $scope.pocket.fund;
      var frontTask;

      try {
          frontTask = fund.importTransaction(form.newTx);
      } catch(e) {
          notify.error(_('Error importing'), _(e.message));
          return;
      }

      $tabs.updateTabs($scope.pocket.type, $scope.pocket.tasks);

      // Add to tasks
      notify.success(_('Added transaction'));
  };

  /**
   * Continue signing after getting the password
   */
  function finishSignFundTx(password, fund, task, inputs) {
      var identity = DarkWallet.getIdentity();

      var signed;
      try {
          signed = fund.signTransaction(password, task, inputs);
      } catch (e) {
          notify.warning(_('Invalid Password'));
          return;
      }

      if (!signed) {
          notify.warning(_('Transaction was already signed by us'));
          return;
      }
      if (finishSigning(fund, task)) {
          notify.success(_('Signed transaction and ready to go!'));
      } else {
          notify.success(_('Signed transaction'));
      }
  };

  /**
   * Sign a transaction with our keys
   */
  $scope.signFundTxForeign = function(foreignKey, task) {
      var fund = $scope.pocket.fund;
      var signed;

      try {
          signed = fund.signTxForeign(foreignKey, task);
      } catch (e) {
          notify.error(_('Error importing'), _(e.message));
      }

      if (!signed) {
          notify.warning(_('Could not sign with the given key'));
          return;
      }
      if (finishSigning(fund, task)) {
          notify.success(_('Signed transaction and ready to go!'));
      } else {
          notify.success(_('Signed transaction'));
      }

  }

  /**
   * Sign a transaction with our keys
   */
  $scope.signFundTx = function(task) {
      // import transaction here
      var fund = $scope.pocket.fund;

      var inputs = fund.getValidInputs(task.tx);

      if (inputs.length) {
          modals.password(_('Write your password'), function(password) { finishSignFundTx(password, fund, task, inputs); } );
      } else {
          notify.error(_('Error importing'), _('Transaction is not for this multisig'));
      }
  };

  var finishDelete = function(fund) {
      var identity = DarkWallet.getIdentity();
      identity.wallet.multisig.deleteFund(fund.multisig);
      $scope.selectPocket();
  };
  $scope.deleteFund = function(fund) {
      modals.open('confirm-delete', {name: fund.name, object: fund}, finishDelete);
  };

}]);
});
<|endoftext|>


<commit_before>'use strict'

const electron = require('electron')
const menu = require('./menu')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const globalShortcut = electron.globalShortcut
const Menu = electron.Menu

var mainWindow = null

const profile = process.env.SOUNDCLEOD_PROFILE
if (profile)
  app.setPath('userData', app.getPath('userData') + ' ' + profile)

app.on('window-all-closed', function() {
  app.quit()
})

app.on('ready', function() {
  console.log('ready')
  Menu.setApplicationMenu(Menu.buildFromTemplate(menu))
  mainWindow = new BrowserWindow({
    width: 1290,
    height: 800,
    minWidth: 1024,
    minHeight: 760,
    webPreferences: {
      nodeIntegration: false,
      preload: `${__dirname}/preload.js`
    }
  })

  mainWindow.loadURL('https://soundcloud.com')
  mainWindow.on('closed', function() {
    mainWindow = null
  })

  globalShortcut.register('MediaPlayPause', () => {
    mainWindow.webContents.send('playPause')
  })

  globalShortcut.register('MediaNextTrack', () => {
    mainWindow.webContents.send('next')
  })

  globalShortcut.register('MediaPreviousTrack', () => {
    mainWindow.webContents.send('previous')
  })
})
<commit_msg>Remove log<commit_after>'use strict'

const electron = require('electron')
const menu = require('./menu')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const globalShortcut = electron.globalShortcut
const Menu = electron.Menu

var mainWindow = null

const profile = process.env.SOUNDCLEOD_PROFILE
if (profile)
  app.setPath('userData', app.getPath('userData') + ' ' + profile)

app.on('window-all-closed', function() {
  app.quit()
})

app.on('ready', function() {
  Menu.setApplicationMenu(Menu.buildFromTemplate(menu))

  mainWindow = new BrowserWindow({
    width: 1290,
    height: 800,
    minWidth: 1024,
    minHeight: 760,
    webPreferences: {
      nodeIntegration: false,
      preload: `${__dirname}/preload.js`
    }
  })

  mainWindow.loadURL('https://soundcloud.com')
  mainWindow.on('closed', function() {
    mainWindow = null
  })

  globalShortcut.register('MediaPlayPause', () => {
    mainWindow.webContents.send('playPause')
  })

  globalShortcut.register('MediaNextTrack', () => {
    mainWindow.webContents.send('next')
  })

  globalShortcut.register('MediaPreviousTrack', () => {
    mainWindow.webContents.send('previous')
  })
})
<|endoftext|>


<commit_before>
/*!
 * gulp
 * $ npm install gulp-sass gulp-autoprefixer gulp-minify-css gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev
 */

// Load plugins
var gulp = require('gulp'),
    sass = require('gulp-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    minifycss = require('gulp-minify-css'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');

// Styles
gulp.task('styles', function() {
  gulp.src('./src/assets/styles/main.scss', { style: 'expanded' })
    .pipe(sass().on('error', sass.logError))
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(notify({ message: 'Styles task complete' }));
});

// Scripts
gulp.task('scripts', function() {
  return gulp.src('src/assets/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

// Images
gulp.task('images', function() {
  return gulp.src('src/assets/images/**/*')
    .pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })))
    .pipe(gulp.dest('dist/assets/img'))
    .pipe(notify({ message: 'Images task complete' }));
});

// Clean
gulp.task('clean', function() {
  return del(['dist/assets/css', 'dist/assests/js', 'dist/assests/img']);
});

// Default task
gulp.task('default', ['clean'], function() {
  gulp.start('styles', 'scripts', 'images');
});

// Watch
gulp.task('watch', function() {

  // Watch .scss files
  gulp.watch('src/assests/styles/**/*.scss', ['styles']);

  // Watch .js files
  gulp.watch('src/assests/scripts/**/*.js', ['scripts']);

  // Watch image files
  gulp.watch('src/assests/images/**/*', ['images']);

  // Create LiveReload server
  livereload.listen();

  // Watch any files in dist/, reload on change
  gulp.watch(['dist/**']).on('change', livereload.changed);

});
<commit_msg>Configure gulp-sourcemaps<commit_after>
/*!
 * gulp
 * $ npm install gulp-sass gulp-autoprefixer gulp-minify-css gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev
 */

// Load plugins
var gulp = require('gulp'),
    sass = require('gulp-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    minifycss = require('gulp-minify-css'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');
    sourcemaps = require('gulp-sourcemaps');

// Styles
gulp.task('styles', function() {
  gulp.src('./src/assets/styles/main.scss', { sourcemap: true, style: 'expanded' })
    .pipe(sourcemaps.init())
      .pipe(sass().on('error', sass.logError))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(notify({ message: 'Styles task complete' }));
});

// Scripts
gulp.task('scripts', function() {
  return gulp.src('src/assets/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

// Images
gulp.task('images', function() {
  return gulp.src('src/assets/images/**/*')
    .pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })))
    .pipe(gulp.dest('dist/assets/img'))
    .pipe(notify({ message: 'Images task complete' }));
});

// Clean
gulp.task('clean', function() {
  return del(['dist/assets/css', 'dist/assests/js', 'dist/assests/img']);
});

// Default task
gulp.task('default', ['clean'], function() {
  gulp.start('styles', 'scripts', 'images');
});

// Watch
gulp.task('watch', function() {

  // Watch .scss files
  gulp.watch('src/assests/styles/**/*.scss', ['styles']);

  // Watch .js files
  gulp.watch('src/assests/scripts/**/*.js', ['scripts']);

  // Watch image files
  gulp.watch('src/assests/images/**/*', ['images']);

  // Create LiveReload server
  livereload.listen();

  // Watch any files in dist/, reload on change
  gulp.watch(['dist/**']).on('change', livereload.changed);

});
<|endoftext|>


<commit_before>// Module is used for Iframework.Library and has info about ins and outs
// Node is used by Graph, and has info about x, y, w, h

$(function(){

  var library = new Iframework.Modules(
    [
      {"src":"http://forresto.github.com/meemoo-modules/canvasarray.html","info":{"title":"canvas array","author":"forresto","description":"hold a stack of canvases for reuse, click sends it, arrows navigate to prev/next"},"inputs":[{"name":"image","type":"image","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-image/transform.html","info":{"title":"transform","author":"forresto","description":"scale, translate, and/or rotate image (centered)"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/combine.html","info":{"title":"combine","author":"forresto","description":"combine image layers"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/text.html","info":{"title":"text","author":"forresto","description":"text to image"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/spritesheet.html","info":{"title":"spritesheet","author":"forresto","description":"makes a single-image filmstrip sprite sheet"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/spritesplit.html","info":{"title":"spritesplit","author":"forresto","description":"separates images from filmstrip sprite sheet"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-blend/blend.html","info":{"title":"blend","author":"forresto","description":"blend imageData under and over with given mode"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/mask.html","info":{"title":"alpha mask","author":"forresto","description":"use a grayscale image as the alpha for another image"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/threshold.html","info":{"title":"threshold","author":"forresto","description":"image to monochrome via luminosity threshold"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/alpha.html","info":{"title":"alpha","author":"forresto","description":"use a greyscale image as the alpha for a color image"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/aviary.html","info":{"title":"aviary","author":"aviary","description":"aviary.com image editor with enhance, brightness, contrast, crop, paint, stickers, text..."},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-canvas2gif/canvas2gif.html","info":{"title":"gif","author":"forresto","description":"canvas image data to animated gif"},"inputs":[{"name":"image","type":"image","description":"","min":"","max":"","default":""},{"name":"quality","type":"int","description":"from 0 (fastest) to 100 (best)","min":"0","max":"100","default":""},{"name":"delay","type":"int","description":"","min":"","max":"","default":""},{"name":"matte","type":"color","description":"","min":"","max":"","default":""},{"name":"finalize","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[{"name":"gif","type":"data:image/gif"}]},
      {"src":"http://forresto.github.com/meemoo-modules/file-reader-image.html","info":{"title":"image file","author":"forresto","description":"Select or drag local images to get the canvas pixel data. Requires a browser with the FileReader API, like Firefox or Chrome."},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/imgur.html","info":{"title":"imgur","author":"forresto","description":"image data url to Imgur image sharing service"},"inputs":[{"name":"dataurl","type":"data:image","description":"","min":"","max":"","default":""},{"name":"title","type":"string","description":"","min":"","max":"","default":""},{"name":"caption","type":"string","description":"","min":"","max":"","default":""}],"outputs":[{"name":"pageurl","type":"string"},{"name":"imageurl","type":"string"}]},
      {"src":"http://forresto.github.com/meemoo-modules/delay.html","info":{"title":"delay","author":"forresto","description":"hold a stack of stack of data until buffer is full"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/timer.html","info":{"title":"timer","author":"forresto","description":"countdown to bang"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/clock.html","info":{"title":"clock","author":"forresto","description":"time: hours minutes seconds with percentages"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/metronome.html","info":{"title":"metronome","author":"forresto","description":"meemoo.js module for rhythm in bpm or ms"},"inputs":[{"name":"ms","type":"int","description":"","min":"","max":"","default":""},{"name":"bpm","type":"number","description":"","min":"","max":"","default":""},{"name":"start","type":"bang","description":"","min":"","max":"","default":""},{"name":"stop","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[{"name":"beat","type":"bang"}]},
      {"src":"http://forresto.github.com/meemoo-modules/taptempo.html","info":{"title":"taptempo","author":"forresto","description":"tap out your rhythm, averages last 4 taps"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-paint/paint.html","info":{"title":"paint","author":"forresto","description":"canvas pixel paint"},"inputs":[{"name":"image","type":"image","description":"background image, part of output","min":"","max":"","default":""},{"name":"tracing","type":"image","description":"tracing image, not part of output","min":"","max":"","default":""},{"name":"undo","type":"bang","description":"","min":"","max":"","default":""},{"name":"clear","type":"bang","description":"","min":"","max":"","default":""},{"name":"send","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-camcanvas/onionskin.html","info":{"title":"cam+onionskin","author":"taboca + forresto + ginger","description":"flash webcam image to canvas with onionskin of last frame"},"inputs":[{"name":"capture","type":"bang","description":"","min":"","max":"","default":""},{"name":"onionskin","type":"image","description":"","min":"","max":"","default":""},{"name":"width","type":"int","description":"","min":"1","max":"640","default":""},{"name":"height","type":"int","description":"","min":"1","max":"480","default":""},{"name":"quality","type":"int","description":"","min":"0","max":"100","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-camcanvas/webcam2canvas.html","info":{"title":"cam","author":"taboca + Forrest Oliphant","description":"flash webcam image to canvas"},"inputs":[{"name":"capture","type":"bang","description":"","min":"","max":"","default":""},{"name":"width","type":"int","description":"","min":"1","max":"640","default":""},{"name":"height","type":"int","description":"","min":"1","max":"480","default":""},{"name":"quality","type":"int","description":"","min":"0","max":"100","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-modules/reflow.html","info":{"title":"reflow","description":"glitches by reflowing image data"},"inputs":[{"name":"image","type":"image","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-jpgglitch/jpgglitch.html","info":{"title":"jpgglitch","author":"mutaphysis + forresto","description":"jpg to glitched jpg, click image to set glitchPos"},"inputs":[{"name":"jpg","type":"data:image/jpeg","description":"","min":"","max":"","default":""},{"name":"glitchPos","type":"number","description":"","min":0,"max":1,"default":""},{"name":"glitchVal","type":"int","description":"","min":0,"max":255,"default":""},{"name":"random","type":"int","description":"","min":0,"max":100,"default":""}],"outputs":[{"name":"jpg","type":"data:image/jpeg"}]},
      {"src":"http://forresto.github.com/meemoo-modules/img2canvas.html","info":{"title":"img2canvas","author":"forresto","description":"image data url to canvas image data"},"inputs":[{"name":"dataurl","type":"data:image","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-modules/processing.html","info":{"title":"processing.js","description":"processing code loader"},"inputs":[{"name":"image","type":"image","description":"","min":"","max":"","default":""},{"name":"send","type":"bang","description":"","min":"","max":"","default":""},{"name":"pressed","type":"bang","description":"","min":"","max":"","default":""},{"name":"code","type":"code:java","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-modules/audioarray.html","info":{"title":"audioarray","author":"forresto","description":"hold and address a stack of audio objects"},"inputs":[{"name":"audio","type":"data:audio","description":"","min":"","max":"","default":""},{"name":"title","type":"string","description":"","min":"","max":"","default":""},{"name":"send","type":"int","description":"","min":"","max":"","default":""},{"name":"play","type":"int","description":"play the id specified","min":"","max":"","default":""},{"name":"playall","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/speech2text.html","info":{"title":"speech2text","author":"forresto","description":"speech to text with x-webkit-speech"},"inputs":[{"name":"send","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[{"name":"text","type":"string"}]},
      {"src":"http://forresto.github.com/meemoo-speak.js/text2speech.html","info":{"title":"text2speech","author":"forresto","description":"text to speech with speak.js"},"inputs":[{"name":"text","type":"string","description":"","min":"","max":"","default":""},{"name":"amplitude","type":"int","description":"","min":0,"max":100,"default":""},{"name":"pitch","type":"int","description":"","min":0,"max":100,"default":""},{"name":"speed","type":"int","description":"speed in words-per-minute","min":0,"max":"","default":""},{"name":"wordgap","type":"int","description":"gap between words in ms","min":0,"max":5000,"default":""}],"outputs":[{"name":"audio","type":"data:audio/x-wav"},{"name":"info","type":"string"}]}
    ]
  );

  Iframework.loadLibrary(library);

});
<commit_msg>meemoo-image-in<commit_after>// Module is used for Iframework.Library and has info about ins and outs
// Node is used by Graph, and has info about x, y, w, h

$(function(){

  var library = new Iframework.Modules(
    [
      {"src":"http://forresto.github.com/meemoo-modules/canvasarray.html","info":{"title":"canvas array","author":"forresto","description":"hold a stack of canvases for reuse, click sends it, arrows navigate to prev/next"},"inputs":[{"name":"image","type":"image","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-image/transform.html","info":{"title":"transform","author":"forresto","description":"scale, translate, and/or rotate image (centered)"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/combine.html","info":{"title":"combine","author":"forresto","description":"combine image layers"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/text.html","info":{"title":"text","author":"forresto","description":"text to image"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/spritesheet.html","info":{"title":"spritesheet","author":"forresto","description":"makes a single-image filmstrip sprite sheet"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/spritesplit.html","info":{"title":"spritesplit","author":"forresto","description":"separates images from filmstrip sprite sheet"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-blend/blend.html","info":{"title":"blend","author":"forresto","description":"blend imageData under and over with given mode"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/mask.html","info":{"title":"alpha mask","author":"forresto","description":"use a grayscale image as the alpha for another image"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/threshold.html","info":{"title":"threshold","author":"forresto","description":"image to monochrome via luminosity threshold"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/alpha.html","info":{"title":"alpha","author":"forresto","description":"use a greyscale image as the alpha for a color image"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-image/aviary.html","info":{"title":"aviary","author":"aviary","description":"aviary.com image editor with enhance, brightness, contrast, crop, paint, stickers, text..."},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-canvas2gif/canvas2gif.html","info":{"title":"gif","author":"forresto","description":"canvas image data to animated gif"},"inputs":[{"name":"image","type":"image","description":"","min":"","max":"","default":""},{"name":"quality","type":"int","description":"from 0 (fastest) to 100 (best)","min":"0","max":"100","default":""},{"name":"delay","type":"int","description":"","min":"","max":"","default":""},{"name":"matte","type":"color","description":"","min":"","max":"","default":""},{"name":"finalize","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[{"name":"gif","type":"data:image/gif"}]},
      {"src":"http://forresto.github.com/meemoo-modules/file-reader-image.html","info":{"title":"image file","author":"forresto","description":"Select or drag local images to get the canvas pixel data. Requires a browser with the FileReader API, like Firefox or Chrome."},"inputs":[],"outputs":[]},
      {"src":"http://meemoo-image-in.phpfogapp.com/image-in.html","info":{"title":"image-in","author":"forresto","description":"Public image url to get the canvas pixel data."},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/imgur.html","info":{"title":"imgur","author":"forresto","description":"image data url to Imgur image sharing service"},"inputs":[{"name":"dataurl","type":"data:image","description":"","min":"","max":"","default":""},{"name":"title","type":"string","description":"","min":"","max":"","default":""},{"name":"caption","type":"string","description":"","min":"","max":"","default":""}],"outputs":[{"name":"pageurl","type":"string"},{"name":"imageurl","type":"string"}]},
      {"src":"http://forresto.github.com/meemoo-modules/delay.html","info":{"title":"delay","author":"forresto","description":"hold a stack of stack of data until buffer is full"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/timer.html","info":{"title":"timer","author":"forresto","description":"countdown to bang"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/clock.html","info":{"title":"clock","author":"forresto","description":"time: hours minutes seconds with percentages"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/metronome.html","info":{"title":"metronome","author":"forresto","description":"meemoo.js module for rhythm in bpm or ms"},"inputs":[{"name":"ms","type":"int","description":"","min":"","max":"","default":""},{"name":"bpm","type":"number","description":"","min":"","max":"","default":""},{"name":"start","type":"bang","description":"","min":"","max":"","default":""},{"name":"stop","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[{"name":"beat","type":"bang"}]},
      {"src":"http://forresto.github.com/meemoo-modules/taptempo.html","info":{"title":"taptempo","author":"forresto","description":"tap out your rhythm, averages last 4 taps"},"inputs":[],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-paint/paint.html","info":{"title":"paint","author":"forresto","description":"canvas pixel paint"},"inputs":[{"name":"image","type":"image","description":"background image, part of output","min":"","max":"","default":""},{"name":"tracing","type":"image","description":"tracing image, not part of output","min":"","max":"","default":""},{"name":"undo","type":"bang","description":"","min":"","max":"","default":""},{"name":"clear","type":"bang","description":"","min":"","max":"","default":""},{"name":"send","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-camcanvas/onionskin.html","info":{"title":"cam+onionskin","author":"taboca + forresto + ginger","description":"flash webcam image to canvas with onionskin of last frame"},"inputs":[{"name":"capture","type":"bang","description":"","min":"","max":"","default":""},{"name":"onionskin","type":"image","description":"","min":"","max":"","default":""},{"name":"width","type":"int","description":"","min":"1","max":"640","default":""},{"name":"height","type":"int","description":"","min":"1","max":"480","default":""},{"name":"quality","type":"int","description":"","min":"0","max":"100","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-camcanvas/webcam2canvas.html","info":{"title":"cam","author":"taboca + Forrest Oliphant","description":"flash webcam image to canvas"},"inputs":[{"name":"capture","type":"bang","description":"","min":"","max":"","default":""},{"name":"width","type":"int","description":"","min":"1","max":"640","default":""},{"name":"height","type":"int","description":"","min":"1","max":"480","default":""},{"name":"quality","type":"int","description":"","min":"0","max":"100","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-modules/reflow.html","info":{"title":"reflow","description":"glitches by reflowing image data"},"inputs":[{"name":"image","type":"image","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-jpgglitch/jpgglitch.html","info":{"title":"jpgglitch","author":"mutaphysis + forresto","description":"jpg to glitched jpg, click image to set glitchPos"},"inputs":[{"name":"jpg","type":"data:image/jpeg","description":"","min":"","max":"","default":""},{"name":"glitchPos","type":"number","description":"","min":0,"max":1,"default":""},{"name":"glitchVal","type":"int","description":"","min":0,"max":255,"default":""},{"name":"random","type":"int","description":"","min":0,"max":100,"default":""}],"outputs":[{"name":"jpg","type":"data:image/jpeg"}]},
      {"src":"http://forresto.github.com/meemoo-modules/img2canvas.html","info":{"title":"img2canvas","author":"forresto","description":"image data url to canvas image data"},"inputs":[{"name":"dataurl","type":"data:image","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-modules/processing.html","info":{"title":"processing.js","description":"processing code loader"},"inputs":[{"name":"image","type":"image","description":"","min":"","max":"","default":""},{"name":"send","type":"bang","description":"","min":"","max":"","default":""},{"name":"pressed","type":"bang","description":"","min":"","max":"","default":""},{"name":"code","type":"code:java","description":"","min":"","max":"","default":""}],"outputs":[{"name":"image","type":"image"}]},
      {"src":"http://forresto.github.com/meemoo-modules/audioarray.html","info":{"title":"audioarray","author":"forresto","description":"hold and address a stack of audio objects"},"inputs":[{"name":"audio","type":"data:audio","description":"","min":"","max":"","default":""},{"name":"title","type":"string","description":"","min":"","max":"","default":""},{"name":"send","type":"int","description":"","min":"","max":"","default":""},{"name":"play","type":"int","description":"play the id specified","min":"","max":"","default":""},{"name":"playall","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[]},
      {"src":"http://forresto.github.com/meemoo-modules/speech2text.html","info":{"title":"speech2text","author":"forresto","description":"speech to text with x-webkit-speech"},"inputs":[{"name":"send","type":"bang","description":"","min":"","max":"","default":""}],"outputs":[{"name":"text","type":"string"}]},
      {"src":"http://forresto.github.com/meemoo-speak.js/text2speech.html","info":{"title":"text2speech","author":"forresto","description":"text to speech with speak.js"},"inputs":[{"name":"text","type":"string","description":"","min":"","max":"","default":""},{"name":"amplitude","type":"int","description":"","min":0,"max":100,"default":""},{"name":"pitch","type":"int","description":"","min":0,"max":100,"default":""},{"name":"speed","type":"int","description":"speed in words-per-minute","min":0,"max":"","default":""},{"name":"wordgap","type":"int","description":"gap between words in ms","min":0,"max":5000,"default":""}],"outputs":[{"name":"audio","type":"data:audio/x-wav"},{"name":"info","type":"string"}]}
    ]
  );

  Iframework.loadLibrary(library);

});
<|endoftext|>


<commit_before>'use strict';

var util = require('util'),
    path = require('path'),

    _ = require('lodash'),
    vow = require('vow'),
    vowFs = require('vow-fs'),
    semver = require('semver'),

    errors = require('../errors').TaskLibrariesDb,
    logger = require('../logger'),
    levelDb = require('../providers/level-db'),
    nodes = require('../model/nodes/index.js');

/**
 * Returns db hint object for improve db calls
 * @param {TargetNodes} target object
 * @returns {{gte: (TargetBase.KEY.NODE_PREFIX|*), lt: (TargetBase.KEY.PEOPLE_PREFIX|*), fillCache: boolean}}
 */
function getDbHints(target) {
    return { gte: target.KEY.NODE_PREFIX, lt: target.KEY.PEOPLE_PREFIX, fillCache: true };
}

/**
 * Returns array of library nodes from database
 * @param {TargetBase} target object
 * @returns {*}
 */
function getLibraryNodesFromDb(target) {
    return levelDb
        .get().getByCriteria(function (record) {
            var key = record.key,
                value = record.value;

            if (key.indexOf(target.KEY.NODE_PREFIX) < 0) {
                return false;
            }

            // criteria - is existed field lib
            return value.lib;
        }, getDbHints(target));
}

/**
 * Returns array of library version folders for current library
 * @param {TargetNodes} target object
 * @param {Object} value - database record of library node
 * @returns {*}
 */
function getLibraryVersionsFromCache(target, value) {
    return vowFs.listDir(path.join(target.LIBRARIES_FILE_PATH, value.lib));
}

/**
 * Returns array of library version nodes from database
 * @param {TargetNodes} target object
 * @param {Object} lib - database record of library node
 * @returns {*}
 */
function getLibraryVersionNodesFromDb(target, lib) {
    return levelDb
        .get().getByCriteria(function (record) {
            var key = record.key,
                value = record.value;

            if (key.indexOf(target.KEY.NODE_PREFIX) < 0) {
                return false;
            }

            // criteria is equality of parent and id fields of version and library nodes
            return value.parent === lib.id;
        }, getDbHints(target));
}

/**
 * Collect map of contents of {lib}/{version}/_data.json files
 * @param {TargetNodes} target object
 * @returns {*}
 */
function getPreviousStateMap(target) {
    var result = {};
    return vowFs.listDir(target.LIBRARIES_FILE_PATH).then(function (libraries) {
        return vow.all(libraries.map(function (lib) {
            return vowFs.listDir(path.join(target.LIBRARIES_FILE_PATH, lib)).then(function (versions) {
                return vow.all(versions.map(function (version) {
                    return vowFs.read(path.join(target.LIBRARIES_FILE_PATH, lib, version, '_data.json'), 'utf-8')
                        .then(function (content) {
                            result[lib] = result[lib] || {};
                            result[lib][version] = content;
                        });
                }));
            });
        }));
    }).then(function () {
        return result;
    });
}

function removeLibraryVersionNodesFromDb(target, lib, version) {
    return levelDb.get()
        .getByCriteria(function (record) {
            var key = record.key,
                value = record.value,
                route;

            if (key.indexOf(target.KEY.NODE_PREFIX) < 0) {
                return false;
            }

            route = value.route;
            if (!route || !route.conditions) {
                return false;
            }

            return route.conditions.lib === lib && !value.lib &&
                (version ? route.conditions.version === version : true);
        }, getDbHints(target))
        .then(function (result) {
            return levelDb.get().batch(result.map(function (record) {
                return { type: 'del', key: record.key };
            }));
        });
}

/**
 * Loads data.json file from libraries cache {cache}/{lib}/{version}/data.json
 * @param {TargetLibraries} target object
 * @param {String} lib - library name
 * @param {String} version - library version name
 * @returns {*}
 */
function loadVersionFile(target, lib, version) {
    return vowFs.read(path.join(target.LIBRARIES_FILE_PATH, lib, version, 'data.json'), 'utf-8')
        .then(function (content) {
            try {
                return JSON.parse(content);
            } catch (err) {
                return null;
            }
        });
}

/**
 * Synchronize versions of given library between file cache and database
 * @param {TargetNodes} target object
 * @param {Object} record - library node record
 * @param {Object} stateMap current cache state
 * @returns {*}
 */
function syncLibraryVersions(target, record, stateMap) {
    var key = record.key,
        value = record.value;
    return vow.all([
            getLibraryVersionsFromCache(target, value),
            getLibraryVersionNodesFromDb(target, value)
       ])
        .spread(function (newVersions, oldVersions) {
            // hide library if there no  versions for it
            if (!newVersions.length) {
                util.getLanguages().forEach(function (lang) {
                    value.hidden[lang] = true;
                });
                return vow.all([
                    removeLibraryVersionNodesFromDb(target, value.lib),
                    levelDb.get().put(key, value)
               ]);
            }

            newVersions = newVersions.sort(function (a, b) {
                return compareVersions(a, b);
            });

            var added = [],
                modified = [],
                removed = [],
                current = newVersions[0];

            newVersions.forEach(function (cacheVersion) {
                var dbRecord = _.find(oldVersions, function (record) {
                    var route = record.value.route,
                        conditions = route.conditions,
                        dbVersion = conditions.version;

                    return cacheVersion === dbVersion;
                });

                if (!dbRecord) {
                    added.push(cacheVersion);
                } else if (stateMap[value.lib] &&
                    stateMap[value.lib][cacheVersion] !== dbRecord.value.cacheVersion) {
                    modified.push(cacheVersion);
                } else if (dbRecord.value.current && dbRecord.value.route.conditions.version !== current) {
                    modified.push(cacheVersion);
                }
            });

            oldVersions.forEach(function (record) {
                var cacheRecord = _.find(newVersions, function (cacheVersion) {
                    var route = record.value.route,
                        conditions = route.conditions,
                        dbVersion = conditions.version;

                    return cacheVersion === dbVersion;
                });

                if (!cacheRecord) {
                    removed.push(record);
                }
            });

            // library versions that should be added to database
            added = added.map(function (item) {
                logger.debug(util.format('add lib: %s version: %s to db', value.lib, item), module);
                target.getChanges().getLibraries().addAdded({ lib: value.lib, version: item });
                return loadVersionFile(target, value.lib, item).then(function (versionData) {
                    versionData.isCurrent = item === current;
                    return (new nodes.version.VersionNode(value, versionData, stateMap[value.lib][item])).saveToDb();
                });
            });

            // library versions that should be rewrited
            modified = modified.map(function (item) {
                logger.debug(util.format('modify lib: %s version: %s into db', value.lib, item), module);
                target.getChanges().getLibraries().addModified({ lib: value.lib, version: item });
                return removeLibraryVersionNodesFromDb(target, value.lib, item)
                    .then(function () {
                        return loadVersionFile(target, value.lib, item).then(function (versionData) {
                            versionData.isCurrent = item === current;
                            return (new nodes.version.VersionNode(
                                value, versionData, stateMap[value.lib][item])).saveToDb();
                        });
                    });
            });

            // library versions for remove from db
            removed = removed.map(function (item) {
                var _version = item.value.route.conditions.version;
                logger.debug(util.format('remove lib: %s version: %s from db',
                    value.lib, _version), module);
                target.getChanges().getLibraries().addRemoved({ lib: value.lib, version: _version });
                return removeLibraryVersionNodesFromDb(target, value.lib, _version);
            });

            return vow.all(added.concat(modified).concat(removed));
        });
}

function compareVersions(a, b) {
    var BRANCHES = ['master', 'dev'],
        VERSION_REGEXP = /^\d+\.\d+\.\d+$/;

    if (BRANCHES.indexOf(a) !== -1) { return 1; }
    if (BRANCHES.indexOf(b) !== -1) { return -1; }

    a = semver.clean(a);
    b = semver.clean(b);

    if (VERSION_REGEXP.test(a) && VERSION_REGEXP.test(b)) { return semver.rcompare(a, b); }

    if (VERSION_REGEXP.test(a)) { return -1; }
    if (VERSION_REGEXP.test(b)) { return 1; }

    if (semver.valid(a) && semver.valid(b)) { return semver.rcompare(a, b); }

    if (semver.valid(a)) { return -1; }
    if (semver.valid(b)) { return 1; }
    return a - b;
}

/**
 * Method for sorting library versions in correct order
 * @param {TargetLibraries} target object
 * @param {Object} record - library record from database
 * @returns {*}
 */
function sortLibraryVersions(target, record) {
    var value = record.value;
    return getLibraryVersionNodesFromDb(target, value)
        .then(function (records) {
            return levelDb.get().batch(records
                .sort(function (a, b) {
                    a = a.value.route.conditions.version;
                    b = b.value.route.conditions.version;
                    return compareVersions(a, b);
                })
                .map(function (record, index) {
                    record.value.current = index === 0;
                    record.value.order = index;
                    return { type: 'put', key: record.key, value: record.value };
                })
            );
        });
}

module.exports = function (target) {
    return vow.all([
            getLibraryNodesFromDb(target),
            getPreviousStateMap(target)
       ])
        .spread(function (records, stateMap) {
            return vow.all(records.map(function (record) {
                return syncLibraryVersions(target, record, stateMap)
                    .then(function () {
                        return sortLibraryVersions(target, record);
                    });
            }));
        })
        .then(function () {
            logger.info('Libraries were synchronized  successfully with database', module);
            return vow.resolve(target);
        })
        .fail(function (err) {
            errors.createError(errors.CODES.COMMON, { err: err }).log();
            return vow.reject(err);
        });
};
<commit_msg>Add verification for empty version data in libraries-db task module<commit_after>'use strict';

var util = require('util'),
    path = require('path'),

    _ = require('lodash'),
    vow = require('vow'),
    vowFs = require('vow-fs'),
    semver = require('semver'),

    errors = require('../errors').TaskLibrariesDb,
    logger = require('../logger'),
    levelDb = require('../providers/level-db'),
    nodes = require('../model/nodes/index.js');

/**
 * Returns db hint object for improve db calls
 * @param {TargetNodes} target object
 * @returns {{gte: (TargetBase.KEY.NODE_PREFIX|*), lt: (TargetBase.KEY.PEOPLE_PREFIX|*), fillCache: boolean}}
 */
function getDbHints(target) {
    return { gte: target.KEY.NODE_PREFIX, lt: target.KEY.PEOPLE_PREFIX, fillCache: true };
}

/**
 * Returns array of library nodes from database
 * @param {TargetBase} target object
 * @returns {*}
 */
function getLibraryNodesFromDb(target) {
    return levelDb
        .get().getByCriteria(function (record) {
            var key = record.key,
                value = record.value;

            if (key.indexOf(target.KEY.NODE_PREFIX) < 0) {
                return false;
            }

            // criteria - is existed field lib
            return value.lib;
        }, getDbHints(target));
}

/**
 * Returns array of library version folders for current library
 * @param {TargetNodes} target object
 * @param {Object} value - database record of library node
 * @returns {*}
 */
function getLibraryVersionsFromCache(target, value) {
    return vowFs.listDir(path.join(target.LIBRARIES_FILE_PATH, value.lib));
}

/**
 * Returns array of library version nodes from database
 * @param {TargetNodes} target object
 * @param {Object} lib - database record of library node
 * @returns {*}
 */
function getLibraryVersionNodesFromDb(target, lib) {
    return levelDb
        .get().getByCriteria(function (record) {
            var key = record.key,
                value = record.value;

            if (key.indexOf(target.KEY.NODE_PREFIX) < 0) {
                return false;
            }

            // criteria is equality of parent and id fields of version and library nodes
            return value.parent === lib.id;
        }, getDbHints(target));
}

/**
 * Collect map of contents of {lib}/{version}/_data.json files
 * @param {TargetNodes} target object
 * @returns {*}
 */
function getPreviousStateMap(target) {
    var result = {};
    return vowFs.listDir(target.LIBRARIES_FILE_PATH).then(function (libraries) {
        return vow.all(libraries.map(function (lib) {
            return vowFs.listDir(path.join(target.LIBRARIES_FILE_PATH, lib)).then(function (versions) {
                return vow.all(versions.map(function (version) {
                    return vowFs.read(path.join(target.LIBRARIES_FILE_PATH, lib, version, '_data.json'), 'utf-8')
                        .then(function (content) {
                            result[lib] = result[lib] || {};
                            result[lib][version] = content;
                        });
                }));
            });
        }));
    }).then(function () {
        return result;
    });
}

function removeLibraryVersionNodesFromDb(target, lib, version) {
    return levelDb.get()
        .getByCriteria(function (record) {
            var key = record.key,
                value = record.value,
                route;

            if (key.indexOf(target.KEY.NODE_PREFIX) < 0) {
                return false;
            }

            route = value.route;
            if (!route || !route.conditions) {
                return false;
            }

            return route.conditions.lib === lib && !value.lib &&
                (version ? route.conditions.version === version : true);
        }, getDbHints(target))
        .then(function (result) {
            return levelDb.get().batch(result.map(function (record) {
                return { type: 'del', key: record.key };
            }));
        });
}

/**
 * Loads data.json file from libraries cache {cache}/{lib}/{version}/data.json
 * @param {TargetLibraries} target object
 * @param {String} lib - library name
 * @param {String} version - library version name
 * @returns {*}
 */
function loadVersionFile(target, lib, version) {
    return vowFs.read(path.join(target.LIBRARIES_FILE_PATH, lib, version, 'data.json'), 'utf-8')
        .then(function (content) {
            try {
                return JSON.parse(content);
            } catch (err) {
                return null;
            }
        });
}

/**
 * Synchronize versions of given library between file cache and database
 * @param {TargetNodes} target object
 * @param {Object} record - library node record
 * @param {Object} stateMap current cache state
 * @returns {*}
 */
function syncLibraryVersions(target, record, stateMap) {
    var key = record.key,
        value = record.value;
    return vow.all([
            getLibraryVersionsFromCache(target, value),
            getLibraryVersionNodesFromDb(target, value)
       ])
        .spread(function (newVersions, oldVersions) {
            // hide library if there no  versions for it
            if (!newVersions.length) {
                util.getLanguages().forEach(function (lang) {
                    value.hidden[lang] = true;
                });
                return vow.all([
                    removeLibraryVersionNodesFromDb(target, value.lib),
                    levelDb.get().put(key, value)
               ]);
            }

            newVersions = newVersions.sort(function (a, b) {
                return compareVersions(a, b);
            });

            var added = [],
                modified = [],
                removed = [],
                current = newVersions[0];

            newVersions.forEach(function (cacheVersion) {
                var dbRecord = _.find(oldVersions, function (record) {
                    var route = record.value.route,
                        conditions = route.conditions,
                        dbVersion = conditions.version;

                    return cacheVersion === dbVersion;
                });

                if (!dbRecord) {
                    added.push(cacheVersion);
                } else if (stateMap[value.lib] &&
                    stateMap[value.lib][cacheVersion] !== dbRecord.value.cacheVersion) {
                    modified.push(cacheVersion);
                } else if (dbRecord.value.current && dbRecord.value.route.conditions.version !== current) {
                    modified.push(cacheVersion);
                }
            });

            oldVersions.forEach(function (record) {
                var cacheRecord = _.find(newVersions, function (cacheVersion) {
                    var route = record.value.route,
                        conditions = route.conditions,
                        dbVersion = conditions.version;

                    return cacheVersion === dbVersion;
                });

                if (!cacheRecord) {
                    removed.push(record);
                }
            });

            // library versions that should be added to database
            added = added.map(function (item) {
                logger.debug(util.format('add lib: %s version: %s to db', value.lib, item), module);
                target.getChanges().getLibraries().addAdded({ lib: value.lib, version: item });
                return loadVersionFile(target, value.lib, item).then(function (versionData) {
                    if(!versionData) {
                        logger.warn(util.format('version data is null for %s %s', value.lib, item), module);
                        return vow.resolve();
                    }

                    versionData.isCurrent = item === current;
                    return (new nodes.version.VersionNode(value, versionData, stateMap[value.lib][item])).saveToDb();
                });
            });

            // library versions that should be rewrited
            modified = modified.map(function (item) {
                logger.debug(util.format('modify lib: %s version: %s into db', value.lib, item), module);
                target.getChanges().getLibraries().addModified({ lib: value.lib, version: item });
                return removeLibraryVersionNodesFromDb(target, value.lib, item)
                    .then(function () {
                        return loadVersionFile(target, value.lib, item).then(function (versionData) {
                            if(!versionData) {
                                logger.warn(util.format('version data is null for %s %s', value.lib, item), module);
                                return vow.resolve();
                            }
                            versionData.isCurrent = item === current;
                            return (new nodes.version.VersionNode(
                                value, versionData, stateMap[value.lib][item])).saveToDb();
                        });
                    });
            });

            // library versions for remove from db
            removed = removed.map(function (item) {
                var _version = item.value.route.conditions.version;
                logger.debug(util.format('remove lib: %s version: %s from db',
                    value.lib, _version), module);
                target.getChanges().getLibraries().addRemoved({ lib: value.lib, version: _version });
                return removeLibraryVersionNodesFromDb(target, value.lib, _version);
            });

            return vow.all(added.concat(modified).concat(removed));
        });
}

function compareVersions(a, b) {
    var BRANCHES = ['master', 'dev'],
        VERSION_REGEXP = /^\d+\.\d+\.\d+$/;

    if (BRANCHES.indexOf(a) !== -1) { return 1; }
    if (BRANCHES.indexOf(b) !== -1) { return -1; }

    a = semver.clean(a);
    b = semver.clean(b);

    if (VERSION_REGEXP.test(a) && VERSION_REGEXP.test(b)) { return semver.rcompare(a, b); }

    if (VERSION_REGEXP.test(a)) { return -1; }
    if (VERSION_REGEXP.test(b)) { return 1; }

    if (semver.valid(a) && semver.valid(b)) { return semver.rcompare(a, b); }

    if (semver.valid(a)) { return -1; }
    if (semver.valid(b)) { return 1; }
    return a - b;
}

/**
 * Method for sorting library versions in correct order
 * @param {TargetLibraries} target object
 * @param {Object} record - library record from database
 * @returns {*}
 */
function sortLibraryVersions(target, record) {
    var value = record.value;
    return getLibraryVersionNodesFromDb(target, value)
        .then(function (records) {
            return levelDb.get().batch(records
                .sort(function (a, b) {
                    a = a.value.route.conditions.version;
                    b = b.value.route.conditions.version;
                    return compareVersions(a, b);
                })
                .map(function (record, index) {
                    record.value.current = index === 0;
                    record.value.order = index;
                    return { type: 'put', key: record.key, value: record.value };
                })
            );
        });
}

module.exports = function (target) {
    return vow.all([
            getLibraryNodesFromDb(target),
            getPreviousStateMap(target)
       ])
        .spread(function (records, stateMap) {
            return vow.all(records.map(function (record) {
                return syncLibraryVersions(target, record, stateMap)
                    .then(function () {
                        return sortLibraryVersions(target, record);
                    });
            }));
        })
        .then(function () {
            logger.info('Libraries were synchronized  successfully with database', module);
            return vow.resolve(target);
        })
        .fail(function (err) {
            errors.createError(errors.CODES.COMMON, { err: err }).log();
            return vow.reject(err);
        });
};
<|endoftext|>