<commit_before>import { expose } from "comlink/dist/esm/comlink.mjs";
import MessagePortState from "../utils/message-port-state";
import libcsoundFactory from "../libcsound.js";
import loadWasm from "../module";
import { logSABWorker as log } from "../logger";
import { handleCsoundStart } from "../workers/common.utils";
import {
  AUDIO_STATE,
  RING_BUFFER_SIZE,
  MIDI_BUFFER_SIZE,
  MIDI_BUFFER_PAYLOAD_SIZE,
  initialSharedState,
} from "../constants";
import { assoc, pipe } from "rambda/dist/rambda.esm.js";

/**
 * @type {Map.<LibcsoundUncloned>}
 */
let combined;
let pollPromise;
let unlockPromise;

const callUncloned = async (k, arguments_) => {
  const caller = combined.get(k);
  const returnValue = caller && caller.apply({}, arguments_ || []);
  return returnValue;
};

const sabCreateRealtimeAudioThread =
  ({
    libraryCsound,
    callbacksRequest,
    releaseStop,
    releasePause,
    releaseResumed,
    wasm,
    wasi,
    workerMessagePort,
  }) =>
  async ({ audioStateBuffer, audioStreamIn, audioStreamOut, midiBuffer, csound }) => {
    const audioStatePointer = new Int32Array(audioStateBuffer);

    // In case of multiple performances, let's reset the sab state
    initialSharedState.forEach((value, index) => {
      Atomics.store(audioStatePointer, index, value);
    });

    // Prompt for midi-input on demand
    const isRequestingRtMidiInput = libraryCsound._isRequestingRtMidiInput(csound);

    // Prompt for microphone only on demand!
    const isExpectingInput =
      Atomics.load(audioStatePointer, AUDIO_STATE.NCHNLS_I) === 0 &&
      libraryCsound.csoundGetInputName(csound).includes("adc");

    // Store Csound AudioParams for upcoming performance
    const userProvidedNchnls = Atomics.load(audioStatePointer, AUDIO_STATE.NCHNLS);
    const userProvidedNchnlsIn = Atomics.load(audioStatePointer, AUDIO_STATE.NCHNLS_I);
    const userProvidedSr = Atomics.load(audioStatePointer, AUDIO_STATE.SAMPLE_RATE);

    userProvidedNchnls > -1 &&
      libraryCsound.csoundSetOption(csound, `--nchnls=${userProvidedNchnls}`);
    userProvidedNchnlsIn > -1 &&
      libraryCsound.csoundSetOption(csound, `--nchnls_i=${userProvidedNchnlsIn}`);
    userProvidedSr > -1 && libraryCsound.csoundSetOption(csound, `--sr=${userProvidedSr}`);

    const nchnls = libraryCsound.csoundGetNchnls(csound);

    const nchnlsInput =
      userProvidedNchnlsIn || isExpectingInput ? libraryCsound.csoundGetNchnlsInput(csound) : 0;
    const sampleRate =
      Atomics.load(audioStatePointer, AUDIO_STATE.SAMPLE_RATE) || libraryCsound.csoundGetSr(csound);

    // a final merge of user configuration and csound options
    Atomics.store(audioStatePointer, AUDIO_STATE.NCHNLS, nchnls);
    Atomics.store(audioStatePointer, AUDIO_STATE.NCHNLS_I, nchnlsInput);
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_REQUESTING_MIC, isExpectingInput ? 1 : 0);
    Atomics.store(audioStatePointer, AUDIO_STATE.SAMPLE_RATE, libraryCsound.csoundGetSr(csound));
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_REQUESTING_RTMIDI, isRequestingRtMidiInput);

    const ksmps = libraryCsound.csoundGetKsmps(csound);
    Atomics.store(audioStatePointer, AUDIO_STATE.KSMPS, ksmps);

    const zeroDecibelFullScale = libraryCsound.csoundGet0dBFS(csound);

    // Get the Worklet channels
    const channelsOutput = [];
    const channelsInput = [];

    for (let channelIndex = 0; channelIndex < nchnls; ++channelIndex) {
      channelsOutput.push(
        new Float64Array(audioStreamOut, RING_BUFFER_SIZE * channelIndex, RING_BUFFER_SIZE),
      );
    }

    for (let channelIndex = 0; channelIndex < nchnlsInput; ++channelIndex) {
      channelsInput.push(
        new Float64Array(audioStreamIn, RING_BUFFER_SIZE * channelIndex, RING_BUFFER_SIZE),
      );
    }

    workerMessagePort.broadcastPlayState("realtimePerformanceStarted");
    // Let's notify the audio-worker that performance has started
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_PERFORMING, 1);

    log(`Atomic.wait started (thread is now locked)\n`)();

    let firstRound = true;
    let lastReturn = 0;
    let currentCsoundBufferPos = 0;
    let currentInputReadIndex = 0;
    let currentOutputWriteIndex = 0;
    let waitResult;

    const maybeStop = (forceStop = false) => {
      if (
        Atomics.load(audioStatePointer, AUDIO_STATE.STOP) === 1 ||
        Atomics.load(audioStatePointer, AUDIO_STATE.IS_PERFORMING) !== 1 ||
        lastReturn !== 0 ||
        forceStop
      ) {
        if (lastReturn === 0) {
          log(`calling csoundStop and one performKsmps to trigger endof logs`)();
          // Trigger "performance ended" logs
          libraryCsound.csoundStop(csound);
          libraryCsound.csoundPerformKsmps(csound);
        }
        log(`triggering realtimePerformanceEnded event`)();
        workerMessagePort.broadcastPlayState("realtimePerformanceEnded");
        log(`End of realtimePerformance loop!`)();
        releaseStop();
        return true;
      } else {
        return false;
      }
    };

    let firstPerformKsmps = true;

    while (
      !firstPerformKsmps ||
      (waitResult = Atomics.wait(audioStatePointer, AUDIO_STATE.CSOUND_LOCK, 1, 10 * 1000))
    ) {
      if (waitResult === "timed-out") {
        maybeStop(true);
        return;
      }

      if (firstRound) {
        firstRound = false;
        await new Promise((resolve) => {
          unlockPromise = resolve;
          workerMessagePort.broadcastSabUnlocked();
        });

        log(`Atomic.wait unlocked, performance started`)();
      }

      if (Atomics.load(audioStatePointer, AUDIO_STATE.IS_PAUSED) === 1) {
        releasePause();
        Atomics.wait(audioStatePointer, AUDIO_STATE.IS_PAUSED, 0);
        await new Promise((resolve) => setTimeout(resolve, 10));
        releaseResumed();
      }

      if (maybeStop()) {
        return;
      }

      if (isRequestingRtMidiInput) {
        const availableMidiEvents = Atomics.load(
          audioStatePointer,
          AUDIO_STATE.AVAIL_RTMIDI_EVENTS,
        );
        if (availableMidiEvents > 0) {
          const rtmidiBufferIndex = Atomics.load(audioStatePointer, AUDIO_STATE.RTMIDI_INDEX);
          let absIndex = rtmidiBufferIndex;
          for (let index = 0; index < availableMidiEvents; index++) {
            absIndex = (rtmidiBufferIndex + MIDI_BUFFER_PAYLOAD_SIZE * index) % MIDI_BUFFER_SIZE;
            const status = Atomics.load(midiBuffer, absIndex);
            const data1 = Atomics.load(midiBuffer, absIndex + 1);
            const data2 = Atomics.load(midiBuffer, absIndex + 2);
            libraryCsound.csoundPushMidiMessage(csound, status, data1, data2);
          }

          Atomics.store(
            audioStatePointer,
            AUDIO_STATE.RTMIDI_INDEX,
            (absIndex + 1) % MIDI_BUFFER_SIZE,
          );
          Atomics.sub(audioStatePointer, AUDIO_STATE.AVAIL_RTMIDI_EVENTS, availableMidiEvents);
        }
      }

      const bufferLength = Atomics.load(audioStatePointer, AUDIO_STATE.BUFFER_LEN);
      const availableInputFrames = Atomics.load(audioStatePointer, AUDIO_STATE.AVAIL_IN_BUFS);

      const hasInput = availableInputFrames >= bufferLength;
      const inputBufferPtr = libraryCsound.csoundGetSpin(csound);
      const outputBufferPtr = libraryCsound.csoundGetSpout(csound);

      const csoundInputBuffer =
        hasInput && new Float64Array(wasm.wasi.memory.buffer, inputBufferPtr, ksmps * nchnlsInput);

      const csoundOutputBuffer = new Float64Array(
        wasm.wasi.memory.buffer,
        outputBufferPtr,
        ksmps * nchnls,
      );

      const framesRequested = Atomics.load(audioStatePointer, AUDIO_STATE.FRAMES_REQUESTED);

      for (let index = 0; index < framesRequested; index++) {
        if (currentCsoundBufferPos === 0) {
          if (lastReturn === 0) {
            lastReturn = libraryCsound.csoundPerformKsmps(csound);
            !firstPerformKsmps && Atomics.add(audioStatePointer, AUDIO_STATE.AVAIL_OUT_BUFS, ksmps);
            firstPerformKsmps = false;
          } else if (lastReturn !== 0) {
            Atomics.store(audioStatePointer, AUDIO_STATE.IS_PERFORMING, 0);
            maybeStop(true);
            return;
          }
        }

        channelsOutput.forEach((channel, channelIndex) => {
          channel[currentOutputWriteIndex] =
            (csoundOutputBuffer[currentCsoundBufferPos * nchnls + channelIndex] || 0) /
            zeroDecibelFullScale;
        });

        if (hasInput) {
          channelsInput.forEach((channel, channelIndex) => {
            csoundInputBuffer[currentCsoundBufferPos * nchnlsInput + channelIndex] =
              (channel[currentInputReadIndex] || 0) * zeroDecibelFullScale;
          });

          currentInputReadIndex = hasInput && (currentInputReadIndex + 1) % RING_BUFFER_SIZE;
        }

        currentOutputWriteIndex = (currentOutputWriteIndex + 1) % RING_BUFFER_SIZE;
        currentCsoundBufferPos = (currentCsoundBufferPos + 1) % ksmps;
      }

      hasInput && Atomics.sub(audioStatePointer, AUDIO_STATE.AVAIL_IN_BUFS, framesRequested);

      if (
        Atomics.compareExchange(audioStatePointer, AUDIO_STATE.HAS_PENDING_CALLBACKS, 1, 0) === 1
      ) {
        await new Promise((resolve) => {
          pollPromise = resolve;
          callbacksRequest();
        });
      }

      if (maybeStop()) {
        return;
      }

      const readIndex = Atomics.load(audioStatePointer, AUDIO_STATE.OUTPUT_READ_INDEX);

      const ioDelay =
        currentOutputWriteIndex < readIndex
          ? currentOutputWriteIndex + RING_BUFFER_SIZE - readIndex
          : currentOutputWriteIndex - readIndex;

      // 2048 is only an indicator of MAX latency
      const nextFramesRequested = Math.max(2048 - ioDelay, 0);

      Atomics.store(audioStatePointer, AUDIO_STATE.FRAMES_REQUESTED, nextFramesRequested);

      if (nextFramesRequested === 0) {
        await new Promise((resolve) => setTimeout(resolve, 1000 * (bufferLength / sampleRate)));
      }
    }
  };

const initMessagePort = ({ port }) => {
  const workerMessagePort = new MessagePortState();
  workerMessagePort.post = (messageLog) => port.postMessage({ log: messageLog });
  workerMessagePort.broadcastPlayState = (playStateChange) => port.postMessage({ playStateChange });
  workerMessagePort.broadcastSabUnlocked = () => port.postMessage({ sabWorker: "unlocked" });
  workerMessagePort.ready = true;
  return workerMessagePort;
};

const initCallbackReplyPort = ({ port }) => {
  port.addEventListener("message", (event) => {
    if (event.data && event.data.unlock) {
      const unlockPromise_ = unlockPromise;
      unlockPromise = undefined;
      unlockPromise_ && unlockPromise_();
    } else {
      const callbacks = event.data;
      const answers = callbacks.reduce((accumulator, { id, argumentz, apiKey }) => {
        try {
          const caller = combined.get(apiKey);
          const answer = caller && caller.apply({}, argumentz || []);
          accumulator.push({ id, answer });
        } catch (error) {
          throw new Error(error);
        }
        return accumulator;
      }, []);
      port.postMessage(answers);
      const pollPromise_ = pollPromise;
      pollPromise = undefined;
      pollPromise_ && pollPromise_(callbacks);
    }
  });
  port.start();
};

const renderFunction =
  ({
    libraryCsound,
    callbacksRequest,
    releaseStop,
    releasePause,
    releaseResumed,
    wasi,
    workerMessagePort,
  }) =>
  async ({ audioStateBuffer, csound }) => {
    const audioStatePointer = new Int32Array(audioStateBuffer);
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_RENDERING, 1);
    workerMessagePort.broadcastSabUnlocked();

    while (
      Atomics.load(audioStatePointer, AUDIO_STATE.STOP) !== 1 &&
      libraryCsound.csoundPerformKsmps(csound) === 0
    ) {
      if (Atomics.load(audioStatePointer, AUDIO_STATE.IS_PAUSED) === 1) {
        releasePause();
        // eslint-disable-next-line no-unused-expressions
        Atomics.wait(audioStatePointer, AUDIO_STATE.IS_PAUSED, 0);
        releaseResumed();
      }
      if (
        Atomics.compareExchange(audioStatePointer, AUDIO_STATE.HAS_PENDING_CALLBACKS, 1, 0) === 1
      ) {
        await new Promise((resolve) => {
          pollPromise = resolve;
          callbacksRequest();
        });
      }
    }
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_RENDERING, 0);
    workerMessagePort.broadcastPlayState("renderEnded");
    releaseStop();
  };

const initialize = async ({ wasmDataURI, withPlugins = [], messagePort, callbackPort }) => {
  log(`initializing SABWorker and WASM`)();
  const workerMessagePort = initMessagePort({ port: messagePort });
  const callbacksRequest = () => callbackPort.postMessage("poll");
  const releaseStop = () => callbackPort.postMessage("releaseStop");
  const releasePause = () => callbackPort.postMessage("releasePause");
  const releaseResumed = () => callbackPort.postMessage("releaseResumed");

  initCallbackReplyPort({ port: callbackPort });

  const [wasm, wasi] = await loadWasm({
    wasmDataURI,
    withPlugins,
    messagePort: workerMessagePort,
  });

  wasm.wasi = wasi;

  const libraryCsound = libcsoundFactory(wasm);

  const startHandler = (_, arguments_) =>
    handleCsoundStart(
      workerMessagePort,
      libraryCsound,
      wasi,
      sabCreateRealtimeAudioThread({
        libraryCsound,
        callbacksRequest,
        wasm,
        workerMessagePort,
        releaseStop,
        releasePause,
        releaseResumed,
      }),
      renderFunction({
        libraryCsound,
        callbacksRequest,
        workerMessagePort,
        wasi,
        releaseStop,
        releasePause,
        releaseResumed,
      }),
    )(arguments_);

  const allAPI = pipe(assoc("csoundStart", startHandler), assoc("wasm", wasm))(libraryCsound);
  combined = new Map(Object.entries(allAPI));

  libraryCsound.csoundInitialize(0);
  const csoundInstance = libraryCsound.csoundCreate();

  return csoundInstance;
};

export const sabWorker = { initialize, callUncloned };

expose({ initialize: sabWorker.initialize, callUncloned: sabWorker.callUncloned });
<commit_msg>fix(wasm): use set-timeout to force trigger event-loop cycle when emitting pause/resume events<commit_after>import { expose } from "comlink/dist/esm/comlink.mjs";
import MessagePortState from "../utils/message-port-state";
import libcsoundFactory from "../libcsound.js";
import loadWasm from "../module";
import { logSABWorker as log } from "../logger";
import { handleCsoundStart } from "../workers/common.utils";
import {
  AUDIO_STATE,
  RING_BUFFER_SIZE,
  MIDI_BUFFER_SIZE,
  MIDI_BUFFER_PAYLOAD_SIZE,
  initialSharedState,
} from "../constants";
import { assoc, pipe } from "rambda/dist/rambda.esm.js";

/**
 * @type {Map.<LibcsoundUncloned>}
 */
let combined;
let pollPromise;
let unlockPromise;

const callUncloned = async (k, arguments_) => {
  const caller = combined.get(k);
  const returnValue = caller && caller.apply({}, arguments_ || []);
  return returnValue;
};

const sabCreateRealtimeAudioThread =
  ({
    libraryCsound,
    callbacksRequest,
    releaseStop,
    releasePause,
    releaseResumed,
    wasm,
    wasi,
    workerMessagePort,
  }) =>
  async ({ audioStateBuffer, audioStreamIn, audioStreamOut, midiBuffer, csound }) => {
    const audioStatePointer = new Int32Array(audioStateBuffer);

    // In case of multiple performances, let's reset the sab state
    initialSharedState.forEach((value, index) => {
      Atomics.store(audioStatePointer, index, value);
    });

    // Prompt for midi-input on demand
    const isRequestingRtMidiInput = libraryCsound._isRequestingRtMidiInput(csound);

    // Prompt for microphone only on demand!
    const isExpectingInput =
      Atomics.load(audioStatePointer, AUDIO_STATE.NCHNLS_I) === 0 &&
      libraryCsound.csoundGetInputName(csound).includes("adc");

    // Store Csound AudioParams for upcoming performance
    const userProvidedNchnls = Atomics.load(audioStatePointer, AUDIO_STATE.NCHNLS);
    const userProvidedNchnlsIn = Atomics.load(audioStatePointer, AUDIO_STATE.NCHNLS_I);
    const userProvidedSr = Atomics.load(audioStatePointer, AUDIO_STATE.SAMPLE_RATE);

    userProvidedNchnls > -1 &&
      libraryCsound.csoundSetOption(csound, `--nchnls=${userProvidedNchnls}`);
    userProvidedNchnlsIn > -1 &&
      libraryCsound.csoundSetOption(csound, `--nchnls_i=${userProvidedNchnlsIn}`);
    userProvidedSr > -1 && libraryCsound.csoundSetOption(csound, `--sr=${userProvidedSr}`);

    const nchnls = libraryCsound.csoundGetNchnls(csound);

    const nchnlsInput =
      userProvidedNchnlsIn || isExpectingInput ? libraryCsound.csoundGetNchnlsInput(csound) : 0;
    const sampleRate =
      Atomics.load(audioStatePointer, AUDIO_STATE.SAMPLE_RATE) || libraryCsound.csoundGetSr(csound);

    // a final merge of user configuration and csound options
    Atomics.store(audioStatePointer, AUDIO_STATE.NCHNLS, nchnls);
    Atomics.store(audioStatePointer, AUDIO_STATE.NCHNLS_I, nchnlsInput);
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_REQUESTING_MIC, isExpectingInput ? 1 : 0);
    Atomics.store(audioStatePointer, AUDIO_STATE.SAMPLE_RATE, libraryCsound.csoundGetSr(csound));
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_REQUESTING_RTMIDI, isRequestingRtMidiInput);

    const ksmps = libraryCsound.csoundGetKsmps(csound);
    Atomics.store(audioStatePointer, AUDIO_STATE.KSMPS, ksmps);

    const zeroDecibelFullScale = libraryCsound.csoundGet0dBFS(csound);

    // Get the Worklet channels
    const channelsOutput = [];
    const channelsInput = [];

    for (let channelIndex = 0; channelIndex < nchnls; ++channelIndex) {
      channelsOutput.push(
        new Float64Array(audioStreamOut, RING_BUFFER_SIZE * channelIndex, RING_BUFFER_SIZE),
      );
    }

    for (let channelIndex = 0; channelIndex < nchnlsInput; ++channelIndex) {
      channelsInput.push(
        new Float64Array(audioStreamIn, RING_BUFFER_SIZE * channelIndex, RING_BUFFER_SIZE),
      );
    }

    workerMessagePort.broadcastPlayState("realtimePerformanceStarted");
    // Let's notify the audio-worker that performance has started
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_PERFORMING, 1);

    log(`Atomic.wait started (thread is now locked)\n`)();

    let firstRound = true;
    let lastReturn = 0;
    let currentCsoundBufferPos = 0;
    let currentInputReadIndex = 0;
    let currentOutputWriteIndex = 0;
    let waitResult;

    const maybeStop = (forceStop = false) => {
      if (
        Atomics.load(audioStatePointer, AUDIO_STATE.STOP) === 1 ||
        Atomics.load(audioStatePointer, AUDIO_STATE.IS_PERFORMING) !== 1 ||
        lastReturn !== 0 ||
        forceStop
      ) {
        if (lastReturn === 0) {
          log(`calling csoundStop and one performKsmps to trigger endof logs`)();
          // Trigger "performance ended" logs
          libraryCsound.csoundStop(csound);
          libraryCsound.csoundPerformKsmps(csound);
        }
        log(`triggering realtimePerformanceEnded event`)();
        workerMessagePort.broadcastPlayState("realtimePerformanceEnded");
        log(`End of realtimePerformance loop!`)();
        releaseStop();
        return true;
      } else {
        return false;
      }
    };

    let firstPerformKsmps = true;

    while (
      !firstPerformKsmps ||
      (waitResult = Atomics.wait(audioStatePointer, AUDIO_STATE.CSOUND_LOCK, 1, 10 * 1000))
    ) {
      if (waitResult === "timed-out") {
        maybeStop(true);
        return;
      }

      if (firstRound) {
        firstRound = false;
        await new Promise((resolve) => {
          unlockPromise = resolve;
          workerMessagePort.broadcastSabUnlocked();
        });

        log(`Atomic.wait unlocked, performance started`)();
      }

      if (Atomics.load(audioStatePointer, AUDIO_STATE.IS_PAUSED) === 1) {
        await new Promise((resolve) => setTimeout(resolve, 0));
        releasePause();
        await new Promise((resolve) => setTimeout(resolve, 0));
        Atomics.wait(audioStatePointer, AUDIO_STATE.IS_PAUSED, 0);
        await new Promise((resolve) => setTimeout(resolve, 0));
        releaseResumed();
        await new Promise((resolve) => setTimeout(resolve, 0));
      }

      if (maybeStop()) {
        return;
      }

      if (isRequestingRtMidiInput) {
        const availableMidiEvents = Atomics.load(
          audioStatePointer,
          AUDIO_STATE.AVAIL_RTMIDI_EVENTS,
        );
        if (availableMidiEvents > 0) {
          const rtmidiBufferIndex = Atomics.load(audioStatePointer, AUDIO_STATE.RTMIDI_INDEX);
          let absIndex = rtmidiBufferIndex;
          for (let index = 0; index < availableMidiEvents; index++) {
            absIndex = (rtmidiBufferIndex + MIDI_BUFFER_PAYLOAD_SIZE * index) % MIDI_BUFFER_SIZE;
            const status = Atomics.load(midiBuffer, absIndex);
            const data1 = Atomics.load(midiBuffer, absIndex + 1);
            const data2 = Atomics.load(midiBuffer, absIndex + 2);
            libraryCsound.csoundPushMidiMessage(csound, status, data1, data2);
          }

          Atomics.store(
            audioStatePointer,
            AUDIO_STATE.RTMIDI_INDEX,
            (absIndex + 1) % MIDI_BUFFER_SIZE,
          );
          Atomics.sub(audioStatePointer, AUDIO_STATE.AVAIL_RTMIDI_EVENTS, availableMidiEvents);
        }
      }

      const bufferLength = Atomics.load(audioStatePointer, AUDIO_STATE.BUFFER_LEN);
      const availableInputFrames = Atomics.load(audioStatePointer, AUDIO_STATE.AVAIL_IN_BUFS);

      const hasInput = availableInputFrames >= bufferLength;
      const inputBufferPtr = libraryCsound.csoundGetSpin(csound);
      const outputBufferPtr = libraryCsound.csoundGetSpout(csound);

      const csoundInputBuffer =
        hasInput && new Float64Array(wasm.wasi.memory.buffer, inputBufferPtr, ksmps * nchnlsInput);

      const csoundOutputBuffer = new Float64Array(
        wasm.wasi.memory.buffer,
        outputBufferPtr,
        ksmps * nchnls,
      );

      const framesRequested = Atomics.load(audioStatePointer, AUDIO_STATE.FRAMES_REQUESTED);

      for (let index = 0; index < framesRequested; index++) {
        if (currentCsoundBufferPos === 0) {
          if (lastReturn === 0) {
            lastReturn = libraryCsound.csoundPerformKsmps(csound);
            !firstPerformKsmps && Atomics.add(audioStatePointer, AUDIO_STATE.AVAIL_OUT_BUFS, ksmps);
            firstPerformKsmps = false;
          } else if (lastReturn !== 0) {
            Atomics.store(audioStatePointer, AUDIO_STATE.IS_PERFORMING, 0);
            maybeStop(true);
            return;
          }
        }

        channelsOutput.forEach((channel, channelIndex) => {
          channel[currentOutputWriteIndex] =
            (csoundOutputBuffer[currentCsoundBufferPos * nchnls + channelIndex] || 0) /
            zeroDecibelFullScale;
        });

        if (hasInput) {
          channelsInput.forEach((channel, channelIndex) => {
            csoundInputBuffer[currentCsoundBufferPos * nchnlsInput + channelIndex] =
              (channel[currentInputReadIndex] || 0) * zeroDecibelFullScale;
          });

          currentInputReadIndex = hasInput && (currentInputReadIndex + 1) % RING_BUFFER_SIZE;
        }

        currentOutputWriteIndex = (currentOutputWriteIndex + 1) % RING_BUFFER_SIZE;
        currentCsoundBufferPos = (currentCsoundBufferPos + 1) % ksmps;
      }

      hasInput && Atomics.sub(audioStatePointer, AUDIO_STATE.AVAIL_IN_BUFS, framesRequested);

      if (
        Atomics.compareExchange(audioStatePointer, AUDIO_STATE.HAS_PENDING_CALLBACKS, 1, 0) === 1
      ) {
        await new Promise((resolve) => {
          pollPromise = resolve;
          callbacksRequest();
        });
      }

      if (maybeStop()) {
        return;
      }

      const readIndex = Atomics.load(audioStatePointer, AUDIO_STATE.OUTPUT_READ_INDEX);

      const ioDelay =
        currentOutputWriteIndex < readIndex
          ? currentOutputWriteIndex + RING_BUFFER_SIZE - readIndex
          : currentOutputWriteIndex - readIndex;

      // 2048 is only an indicator of MAX latency
      const nextFramesRequested = Math.max(2048 - ioDelay, 0);

      Atomics.store(audioStatePointer, AUDIO_STATE.FRAMES_REQUESTED, nextFramesRequested);

      if (nextFramesRequested === 0) {
        await new Promise((resolve) => setTimeout(resolve, 1000 * (bufferLength / sampleRate)));
      }
    }
  };

const initMessagePort = ({ port }) => {
  const workerMessagePort = new MessagePortState();
  workerMessagePort.post = (messageLog) => port.postMessage({ log: messageLog });
  workerMessagePort.broadcastPlayState = (playStateChange) => port.postMessage({ playStateChange });
  workerMessagePort.broadcastSabUnlocked = () => port.postMessage({ sabWorker: "unlocked" });
  workerMessagePort.ready = true;
  return workerMessagePort;
};

const initCallbackReplyPort = ({ port }) => {
  port.addEventListener("message", (event) => {
    if (event.data && event.data.unlock) {
      const unlockPromise_ = unlockPromise;
      unlockPromise = undefined;
      unlockPromise_ && unlockPromise_();
    } else {
      const callbacks = event.data;
      const answers = callbacks.reduce((accumulator, { id, argumentz, apiKey }) => {
        try {
          const caller = combined.get(apiKey);
          const answer = caller && caller.apply({}, argumentz || []);
          accumulator.push({ id, answer });
        } catch (error) {
          throw new Error(error);
        }
        return accumulator;
      }, []);
      port.postMessage(answers);
      const pollPromise_ = pollPromise;
      pollPromise = undefined;
      pollPromise_ && pollPromise_(callbacks);
    }
  });
  port.start();
};

const renderFunction =
  ({
    libraryCsound,
    callbacksRequest,
    releaseStop,
    releasePause,
    releaseResumed,
    wasi,
    workerMessagePort,
  }) =>
  async ({ audioStateBuffer, csound }) => {
    const audioStatePointer = new Int32Array(audioStateBuffer);
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_RENDERING, 1);
    workerMessagePort.broadcastSabUnlocked();

    while (
      Atomics.load(audioStatePointer, AUDIO_STATE.STOP) !== 1 &&
      libraryCsound.csoundPerformKsmps(csound) === 0
    ) {
      if (Atomics.load(audioStatePointer, AUDIO_STATE.IS_PAUSED) === 1) {
        releasePause();
        // eslint-disable-next-line no-unused-expressions
        Atomics.wait(audioStatePointer, AUDIO_STATE.IS_PAUSED, 0);
        releaseResumed();
      }
      if (
        Atomics.compareExchange(audioStatePointer, AUDIO_STATE.HAS_PENDING_CALLBACKS, 1, 0) === 1
      ) {
        await new Promise((resolve) => {
          pollPromise = resolve;
          callbacksRequest();
        });
      }
    }
    Atomics.store(audioStatePointer, AUDIO_STATE.IS_RENDERING, 0);
    workerMessagePort.broadcastPlayState("renderEnded");
    releaseStop();
  };

const initialize = async ({ wasmDataURI, withPlugins = [], messagePort, callbackPort }) => {
  log(`initializing SABWorker and WASM`)();
  const workerMessagePort = initMessagePort({ port: messagePort });
  const callbacksRequest = () => callbackPort.postMessage("poll");
  const releaseStop = () => callbackPort.postMessage("releaseStop");
  const releasePause = () => callbackPort.postMessage("releasePause");
  const releaseResumed = () => callbackPort.postMessage("releaseResumed");

  initCallbackReplyPort({ port: callbackPort });

  const [wasm, wasi] = await loadWasm({
    wasmDataURI,
    withPlugins,
    messagePort: workerMessagePort,
  });

  wasm.wasi = wasi;

  const libraryCsound = libcsoundFactory(wasm);

  const startHandler = (_, arguments_) =>
    handleCsoundStart(
      workerMessagePort,
      libraryCsound,
      wasi,
      sabCreateRealtimeAudioThread({
        libraryCsound,
        callbacksRequest,
        wasm,
        workerMessagePort,
        releaseStop,
        releasePause,
        releaseResumed,
      }),
      renderFunction({
        libraryCsound,
        callbacksRequest,
        workerMessagePort,
        wasi,
        releaseStop,
        releasePause,
        releaseResumed,
      }),
    )(arguments_);

  const allAPI = pipe(assoc("csoundStart", startHandler), assoc("wasm", wasm))(libraryCsound);
  combined = new Map(Object.entries(allAPI));

  libraryCsound.csoundInitialize(0);
  const csoundInstance = libraryCsound.csoundCreate();

  return csoundInstance;
};

export const sabWorker = { initialize, callUncloned };

expose({ initialize: sabWorker.initialize, callUncloned: sabWorker.callUncloned });
<|endoftext|>


<commit_before>/**
 * @author EmmanuelOlaojo
 * @since 6/22/16
 */

var chai = require("chai");
var expect = chai.expect;
var Promise = require("bluebird");
chai.use(require("chai-as-promised"));

var config = require("../test_conf");
var Lint = require("../lib/lint");
var DB = "test";
var TASKS = "LINT";
var TEST_COLLECTION = "humans";
var Task;
var testMdl;
var taskMdl;
var task;

describe("Task", function(){
  before(function(){
    var lint = new Lint(config.db + DB, TASKS);
    Task = lint.Task;
    task = new Task();
    testMdl = task.getCollection(TEST_COLLECTION);
    taskMdl = task.getTaskCollection();
  });

  after(function(){
    return task.dropCollection(TEST_COLLECTION);
  });

  it("should not run twice", function(){
    var task = new Task();

    task.run();
    expect(task.run).to.throw(/cannot be reused/);
  });

  it("should save successfully", function(){
    var task = new Task();

    task.save(TEST_COLLECTION, {name: "Emmanuel Olaojo", age: 20});
    task.save(TEST_COLLECTION, {name: "John Damos", age: 26});

    return task.run()
      .then(function(){
        return Promise.all([
          expect(testMdl.find({name: "Emmanuel Olaojo", age: 20})).to.eventually.have.length(1)
          , expect(testMdl.find({name: "John Damos", age: 26})).to.eventually.have.length(1)
        ])
      })
  });

  it("should update successfully", function(){
    var task = new Task();

    task.update(TEST_COLLECTION, {name: "John Damos"}, {name: "John Snow"});
    task.update(TEST_COLLECTION, {name: "Emmanuel Olaojo"}, {name: "OJ"});

    return task.run()
      .then(function(){
        return Promise.all([
          expect(testMdl.find({name: "John Snow"})).to.eventually.have.length(1)
          , expect(testMdl.find({name: "OJ"})).to.eventually.have.length(1)
          , expect(testMdl.find({name: "John Damos"})).to.eventually.have.length(0)
          , expect(testMdl.find({name: "Emmanuel Olaojo"})).to.eventually.have.length(0)
        ]);
      })
  });

  it("should remove successfully");
});
<commit_msg>Added test for task.remove<commit_after>/**
 * @author EmmanuelOlaojo
 * @since 6/22/16
 */

var chai = require("chai");
var expect = chai.expect;
var Promise = require("bluebird");
chai.use(require("chai-as-promised"));

var config = require("../test_conf");
var Lint = require("../lib/lint");
var DB = "test";
var TASKS = "LINT";
var TEST_COLLECTION = "humans";
var Task;
var testMdl;
var taskMdl;
var task;

describe("Task", function(){
  before(function(){
    var lint = new Lint(config.db + DB, TASKS);
    Task = lint.Task;
    task = new Task();
    testMdl = task.getCollection(TEST_COLLECTION);
    taskMdl = task.getTaskCollection();
  });

  after(function(){
    return task.dropCollection(TEST_COLLECTION);
  });

  it("should not run twice", function(){
    var task = new Task();

    task.run();
    expect(task.run).to.throw(/cannot be reused/);
  });

  it("should save successfully", function(){
    var task = new Task();

    task.save(TEST_COLLECTION, {name: "Emmanuel Olaojo", age: 20});
    task.save(TEST_COLLECTION, {name: "John Damos", age: 26});
    task.save(TEST_COLLECTION, {name: "unimportant", age: 5000});

    return task.run()
      .then(function(){
        return Promise.all([
          expect(testMdl.find({name: "Emmanuel Olaojo", age: 20})).to.eventually.have.length(1)
          , expect(testMdl.find({name: "John Damos", age: 26})).to.eventually.have.length(1)
          , expect(testMdl.find({name: "unimportant", age: 5000})).to.eventually.have.length(1)
        ])
      })
  });

  it("should update successfully", function(){
    var task = new Task();

    task.update(TEST_COLLECTION, {name: "John Damos"}, {name: "John Snow"});
    task.update(TEST_COLLECTION, {name: "Emmanuel Olaojo"}, {name: "OJ"});

    return task.run()
      .then(function(){
        return Promise.all([
          expect(testMdl.find({name: "John Snow"})).to.eventually.have.length(1)
          , expect(testMdl.find({name: "OJ"})).to.eventually.have.length(1)
          , expect(testMdl.find({name: "unimportant"})).to.eventually.have.length(1)
          , expect(testMdl.find({name: "John Damos"})).to.eventually.have.length(0)
          , expect(testMdl.find({name: "Emmanuel Olaojo"})).to.eventually.have.length(0)
        ]);
      })
  });

  it("should remove successfully", function(){
    var task =  new Task();

    task.remove(TEST_COLLECTION, {age: 5000});
    task.remove(TEST_COLLECTION, {name: "John Snow"});

    return task.run()
      .then(function(){
        return Promise.all([
          expect(testMdl.find({name: "John Snow"})).to.eventually.have.length(0)
          , expect(testMdl.find({name: "unimportant"})).to.eventually.have.length(0)
          , expect(testMdl.find({name: "OJ"})).to.eventually.have.length(1)
          , expect(testMdl.find()).to.eventually.have.length(1)
        ]);
      })
  });
});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2010 iceScrum Technologies.
 *
 * This file is part of iceScrum.
 *
 * iceScrum is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * iceScrum is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Stéphane Maldini (stephane.maldini@icescrum.com)
 * Manuarii Stein (manuarii.stein@icescrum.com)
 *
 */

var stack_bottomleft = {"dir1": "up", "dir2": "right"};
var autoCompleteCache = {}, autoCompleteLastXhr;

(function($) {
    $.icescrum = {

        defaults:{
            debug:false,
            baseUrlProduct:null,
            baseUrl:null,
            grailsServer:null,
            streamUrl:null,
            urlOpenWidget:null,
            urlOpenWindow:null,
            widgetContainer:"#widget-list",
            windowContainer:"#main-content",
            deleting:false,
            fullscreen:false,
            deleteConfirmMessage:'Are you sure?',
            cancelFormConfirmMessage:'Do you want to quit this form?',
            more:'more',
            uploading:'',
            dialogErrorContent:null,
            openWindow:false,
            locale:'en',
            showUpgrade:true,
            push:{enable:true,websocket:false},
            selectedObject:{obj:'',time:'',callback:''}
        },
        o:{},

        init:function(options) {
            if (typeof icescrum === undefined) {
                icescrum = options;
            }
            this.o = jQuery.extend({}, this.defaults, icescrum);

            $.cookie.defaults = { path: '/' };

            if (!window.console) window.console = {};
            if (!window.console.log) window.console.log = function () { };

            $.datepicker.setDefaults($.datepicker.regional[this.o.locale]);
            if (!$.getUrlVar('ref')){
                var url = location.hash.replace(/^.*#/, '');
                if (url != '') {
                    $.icescrum.openWindow(url);
                }
            }

            $.icescrum.initHistory();
            var currentWindow = location.hash.replace(/^.*#/, '');
            var $menubar = $('li.menubar:first a');
            if ($.icescrum.o.baseUrlProduct && !currentWindow && $menubar){
                var menubar = $menubar.attr('href').replace(/^.*#/, '');
                document.location.hash = menubar;
                $.icescrum.removeFromWidgetsList(menubar);
            }

            if ($.icescrum.getWidgetsList().length > 0) {
                var tmp = $.icescrum.getWidgetsList();
                $.icescrum.saveWidgetsList([]);
                for (i = 0; i < tmp.length; i++) {
                    this.addToWidgetBar(tmp[i]);
                }
            }

            $.event.trigger('init.icescrum');

            if (this.o.push.enable){
                $.icescrum.listenServer();
            }

            if (window.webkitNotifications) {
                console.log("[notifications] are supported!");
                if (!window.webkitNotifications.checkPermission()) {
                    console.log("[notifications] got permission");
                    this.o.notifications = true;
                }
                else if(window.webkitNotifications.checkPermission() != 2 && $.cookie('hide_notifications') != "true"){
                    $("#notifications").show();
                    $("#accept_notifications").click(function(){
                        window.webkitNotifications.requestPermission(function(){
                            if (window.webkitNotifications.checkPermission() == 0){
                                console.log("[notifications] got permission");
                                $.icescrum.o.notifications = true;
                            }
                            $.cookie('hide_notifications', true, { expires: 15 });
                            $("#notifications").remove();
                        });
                    });
                    $("#hide_notifications").click(function(){
                        $.cookie('hide_notifications', true, { expires: 15 });
                        $("#notifications").remove();
                    });
                }else{
                    console.log("[notifications] permission refused");
                    $("#notifications").remove();
                }
            }else{
                $("#accept_notifications").remove();
                console.log("Notifications are not supported for this Browser/OS version yet.");
                this.o.notifications = false;
            }

            $(window).bind('resize',function(){
                $.icescrum.checkBars();
            });
        },

        log:function() {
            if (window.console) {
                console.log.apply(console, arguments);
            }
        },

        renderNotice:function(text, type, title) {
            var timeout = 7000;
            var titleP = "";
            if (title) {
                titleP = title;
            }
            var typeP = "notice";
            if (typeP) {
                typeP = type;
            }
            if (this.o.notifications){
                var notification = this.displayNotification(title ? title : 'iceScrum '+ (type ?' - '+type : ''), text, type);
                if(notification) {
                    $.doTimeout(timeout,function(){
                        notification.close();
                    });
                }
            }else{
                $.pnotify({
                    pnotify_addclass:'stack-bottomleft',
                    pnotify_animation:{effect_in: 'slide', effect_out: 'fade'},
                    pnotify_delay:timeout,
                    pnotify_history:false,
                    pnotify_stack:stack_bottomleft,
                    pnotify_text:text,
                    pnotify_type:typeP,
                    pnotify_title:titleP
                });
            }
        },

        displayNotification:function(title, msg, type){
            var image = $.icescrum.o.baseUrl + "themes/is/images/";
            image += type == "error" ?  "logo-disconnected.png" : "logo-connected.png";
            if (this.o.notifications){
                var notification = window.webkitNotifications.createNotification(image, title, $('
').html(msg.replace(/<\/?[^>]+>/gi, '')).text());
                notification.show();
                return notification;
            }
        },

        displayTemplate:function(selector, show) {
            if (show) {
                $(selector + " .text-template").show();
                $("#displayTemplate").val('1');
            } else {
                $(selector + " .text-template").hide();
                $("#displayTemplate").val('0');
            }
        },

        displayView:function() {
            $('#menu-display-list').find('.content').html('');
        },

        selectableAction:function(action, doNotConfirm, idParam, onSuccess) {
            var elements = jQuery('.ui-selected, .table-row-focus');
            if (elements.length && !this.o.deleting) {
                this.o.deleting = true;
                if (!doNotConfirm && !confirm(this.o.deleteConfirmMessage)) {
                    this.o.deleting = false;
                    return false;
                }
                var dataToSend = '';
                dataToSend = $.icescrum.postit.ids(elements, idParam);

                if (!action || (action && action.indexOf('/') == -1)) {
                    action = $.icescrum.o.currentOpenedWindow.data('id') + '/' + (action ? action : 'delete');
                }

                jQuery.ajax({type:'POST', data:dataToSend,
                            url: $.icescrum.o.baseUrlProduct + action,
                            success:function(data, textStatus) {
                                $.icescrum.o.deleting = false;
                                if (data.dialog){
                                    $(document.body).append(data.dialog);
                                    return;
                                }
                                if (onSuccess) {
                                    onSuccess(data);
                                }
                            },
                            error:function(XMLHttpRequest, textStatus, errorThrown) {
                                elements.css('background-color', '');
                                $.icescrum.o.deleting = false;
                            }
                        });
                return false;
            }
            return true;
        },

        changeRank:function(container, source, ui, name, call) {
            var postitid = ui.attr('id');
            if (!postitid){
                 return;
            }
            var idmoved = postitid.substring(postitid.lastIndexOf("-") + 1, postitid.length);
            var newPosition = $(container).index(ui) + 1;
            //finally we send the update to server
            var params = {id:idmoved};
            params[name] = newPosition;
            call(params, source);
        },

        dblclickSelectable:function(obj, delay, callback, force) {
            if (force != undefined && force) {
                if ($.icescrum.o.selectedObject.obj != "" && $('#' + $.icescrum.o.selectedObject.obj.selected.id).hasClass('ui-selected')) {
                    if ($.icescrum.o.selectedObject.callback) {
                        callback($.icescrum.o.selectedObject.obj);
                    }
                }
                return false;
            }
            if ($.icescrum.o.selectedObject.obj == "" || (obj.selected.id != $.icescrum.o.selectedObject.obj.selected.id)) {
                $.icescrum.o.selectedObject.obj = obj;
                $.icescrum.o.selectedObject.time = new Date().getTime();
                $.icescrum.o.selectedObject.callback = callback;
            }
            else {
                var c = new Date().getTime();
                var d = c - $.icescrum.o.selectedObject.time;
                if (d <= delay) {
                    if ($.icescrum.o.selectedObject.callback) {
                        $.icescrum.o.selectedObject.callback(obj);
                    }
                    $.icescrum.o.selectedObject = {obj:'',time:'',callback:''};
                }
                else {
                    $.icescrum.o.selectedObject = {obj:'',time:'',callback:''};
                    $.icescrum.dblclickSelectable(obj, delay, callback);
                }
            }
            return false;
        },

        openCommentTab:function(relation) {
            $('#commentEditorContainer').show();
            $.icescrum.openTab(relation, true);
        },

        openTab:function(relation, scrollBottom) {
            $('.panel-box a[rel=' + relation + ']').click();
            if (scrollBottom) {
                var contentWindow = jQuery('div .window-content');
                contentWindow.scrollTop(contentWindow.outerHeight());
            }
        },

        displayChart:function(container, url, save) {
            jQuery.ajax({
                        type:'GET',
                        global:false,
                        cache:true,
                        url:$.icescrum.o.baseUrlProduct + url,
                        data:'withButtonBar=false',
                        beforeSend:function() {
                            $(container).addClass('loading').html('');
                        },
                        success:function(data, textStatus) {
                            $(container).height(300);
                            $(container)
                                    .removeClass('loading')
                                    .html(data);
                            $('.save-chart', $(container)).remove();
                            if (typeof save != 'undefined' && save) {
                                $.cookie(container + $.icescrum.product.id, url);
                            }
                            var test = /\/(.*)/;
                            var match = test.exec(url);
                            if (match[1]) {
                                if (match[1].endsWith('/')){
                                    match[1] = match[1].substr(0, match[1].length - 1)
                                }
                                $('#panel-chart').find('.right').removeClass('selected');
                                $('#chart-' + match[1]).addClass('selected');
                            }
                        },
                        error:function(XMLHttpRequest, textStatus, errorThrown) {
                            var data = $.parseJSON(XMLHttpRequest.responseText);
                            $(container).css("height", null);
                            $(container)
                                    .removeClass('loading')
                                    .addClass('error-loading')
                                    .html(data.notice.text);
                        }
                    });
        },

        displayChartFromCookie:function(container, url, save) {
            var saveChartType = $.cookie(container + $.icescrum.product.id);
            if (saveChartType) {
                this.displayChart(container, saveChartType, false);
            } else {
                this.displayChart(container, url, save);
            }
        },

        listenServer:function() {
            if (!$.icescrum.o.push.websocket){
                 $.stream.options.type = 'http';
            }

            $.stream.setup({
                enableXDR: true,
                handleOpen: function(text, message) {
                        if (!$.icescrum.o.push.websocket){
                            message.index = text.indexOf("") + 12;
                        }
                },
                handleSend: function(type) {
                        if (type !== "send") {
                                return false;
                        }
                }
            });

            $.stream($.icescrum.o.streamUrl, {
                        dataType: "json",
                        openData: {useWebSocket: ($.icescrum.o.push.websocket && (window.MozWebSocket || window.WebSocket)) ? "true" : "false"},
                        throbber: {type:'lazy',delay:0},
                        open: function() {
                            $("#is-logo").removeClass().addClass('connected');
                        },
                        error: function() {
                            $("#is-logo").removeClass().addClass('disconnected');
                        },
                        close: function() {
                            $("#is-logo").removeClass().addClass('disconnected');
                        },
                        message: function(event) {
                            try {
                                $(event.data).each(function() {
                                    if (this.call && this.object) {
                                        if (this.object['class']) {
                                            var type = this.object['class'].substring(this.object['class'].lastIndexOf('.') + 1).toLowerCase();
                                            this.call = (this.call == 'delete') ? 'remove' : this.call;
                                            $.event.trigger(this.call + '_' + type + '.stream', this.object);
                                        } else{
                                            $.event.trigger(this.call + '.stream', this.object);
                                        }
                                    } else if(this.broadcaster) {
                                        if(this.broadcaster.users && this.broadcaster.users.length > 1){
                                            var users = [];
                                            $(this.broadcaster.users).each(function(){
                                                users.push(this.fullName);
                                            });
                                            $('#menu-project').find('.content').attr('title',users.length+' users online ('+users.join(', ')+')');
                                        }else{
                                            $('#menu-project').find('.content').attr('title', 'Do you feel lonely?');
                                        }
                                    }
                                });
                            } catch(e) {
                            }
                        }
                    });
        },

        formattedTaskEstimation:function(estimation, defaultChar) {
            if(estimation == null && defaultChar)
                return '?';
            else if(estimation == null)
                return null;
            else
                return estimation.toString().indexOf('.') == -1 ? estimation.toString().concat('.0') : estimation;
        },

        redirectOnLogin:function(data){
            var refVar = "?ref=";
            var ref = '';
            if (window.location.href.indexOf(refVar) > 0){
                ref = window.location.href.slice(window.location.href.indexOf(refVar) + refVar.length);
            }
            var hash = ref ? ref : data ? data.url : '';
            if(hash.indexOf('#') == 0){
                var url = document.location.toString();
                document.location = url.substring(0,url.indexOf('login')) + hash;
            }else {
                document.location = hash;
            }
        },

        showAndHideOnClickAnywhere:function(selector){
            var element = $(selector);
            if(element.css('display') == 'none') {
                element.show();
                var handler = function(event){
                    element.hide();
                    $(document).off("click", handler);
                }
                setTimeout(function () {
                    $(document).on("click", handler)
                ;}, 10);
            }
        },

        showUpgrade:function(){
            if (this.o.showUpgrade){
                var upgrade = $('.upgrade');
                if (upgrade.length && $.cookie('hide_upgrade') != "true"){
                    upgrade.show();
                    upgrade.find('.close').click(function(){
                        upgrade.remove();
                        $.cookie('hide_upgrade', true, { expires: 30 });
                    });
                }else if(upgrade.length){
                    upgrade.remove();
                }
            }
        }
    }

})(jQuery);

$.fn.icescrum = function(options) {
    if ((typeof options) == "string") {
        //TODO : refactor icescrum('toolbar')
        if (options == 'toolbar')
            return $.icescrum[options](this, arguments);
        else
            return $.icescrum[options].apply(this, arguments);
    }
    return $.icescrum;
};

$(document).ready(function($) {
    $.ajaxSetup({ timeout:45000 });
    $(document).ajaxSend(function(event, xhr){
        xhr.setRequestHeader("If-Modified-Since",new Date(1970,1,1).toUTCString());
        xhr.setRequestHeader("Pragma","no-cache");
    });
    $.icescrum.showUpgrade();
    $.fn.editable.defaults.placeholder = " ";
});
<commit_msg>check lost connection<commit_after>/*
 * Copyright (c) 2010 iceScrum Technologies.
 *
 * This file is part of iceScrum.
 *
 * iceScrum is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * iceScrum is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Stéphane Maldini (stephane.maldini@icescrum.com)
 * Manuarii Stein (manuarii.stein@icescrum.com)
 *
 */

var stack_bottomleft = {"dir1": "up", "dir2": "right"};
var autoCompleteCache = {}, autoCompleteLastXhr;

(function($) {
    $.icescrum = {

        defaults:{
            debug:false,
            baseUrlProduct:null,
            baseUrl:null,
            grailsServer:null,
            streamUrl:null,
            urlOpenWidget:null,
            urlOpenWindow:null,
            widgetContainer:"#widget-list",
            windowContainer:"#main-content",
            deleting:false,
            fullscreen:false,
            deleteConfirmMessage:'Are you sure?',
            cancelFormConfirmMessage:'Do you want to quit this form?',
            more:'more',
            uploading:'',
            dialogErrorContent:null,
            openWindow:false,
            locale:'en',
            showUpgrade:true,
            push:{enable:true,websocket:false},
            selectedObject:{obj:'',time:'',callback:''}
        },
        o:{},

        init:function(options) {
            if (typeof icescrum === undefined) {
                icescrum = options;
            }
            this.o = jQuery.extend({}, this.defaults, icescrum);

            $.cookie.defaults = { path: '/' };

            if (!window.console) window.console = {};
            if (!window.console.log) window.console.log = function () { };

            $.datepicker.setDefaults($.datepicker.regional[this.o.locale]);
            if (!$.getUrlVar('ref')){
                var url = location.hash.replace(/^.*#/, '');
                if (url != '') {
                    $.icescrum.openWindow(url);
                }
            }

            $.icescrum.initHistory();
            var currentWindow = location.hash.replace(/^.*#/, '');
            var $menubar = $('li.menubar:first a');
            if ($.icescrum.o.baseUrlProduct && !currentWindow && $menubar){
                var menubar = $menubar.attr('href').replace(/^.*#/, '');
                document.location.hash = menubar;
                $.icescrum.removeFromWidgetsList(menubar);
            }

            if ($.icescrum.getWidgetsList().length > 0) {
                var tmp = $.icescrum.getWidgetsList();
                $.icescrum.saveWidgetsList([]);
                for (i = 0; i < tmp.length; i++) {
                    this.addToWidgetBar(tmp[i]);
                }
            }

            $.event.trigger('init.icescrum');

            if (this.o.push.enable){
                $.icescrum.listenServer();
            }

            if (window.webkitNotifications) {
                console.log("[notifications] are supported!");
                if (!window.webkitNotifications.checkPermission()) {
                    console.log("[notifications] got permission");
                    this.o.notifications = true;
                }
                else if(window.webkitNotifications.checkPermission() != 2 && $.cookie('hide_notifications') != "true"){
                    $("#notifications").show();
                    $("#accept_notifications").click(function(){
                        window.webkitNotifications.requestPermission(function(){
                            if (window.webkitNotifications.checkPermission() == 0){
                                console.log("[notifications] got permission");
                                $.icescrum.o.notifications = true;
                            }
                            $.cookie('hide_notifications', true, { expires: 15 });
                            $("#notifications").remove();
                        });
                    });
                    $("#hide_notifications").click(function(){
                        $.cookie('hide_notifications', true, { expires: 15 });
                        $("#notifications").remove();
                    });
                }else{
                    console.log("[notifications] permission refused");
                    $("#notifications").remove();
                }
            }else{
                $("#accept_notifications").remove();
                console.log("Notifications are not supported for this Browser/OS version yet.");
                this.o.notifications = false;
            }

            $(window).bind('resize',function(){
                $.icescrum.checkBars();
            });
        },

        log:function() {
            if (window.console) {
                console.log.apply(console, arguments);
            }
        },

        renderNotice:function(text, type, title) {
            var timeout = 7000;
            var titleP = "";
            if (title) {
                titleP = title;
            }
            var typeP = "notice";
            if (typeP) {
                typeP = type;
            }
            if (this.o.notifications){
                var notification = this.displayNotification(title ? title : 'iceScrum '+ (type ?' - '+type : ''), text, type);
                if(notification) {
                    $.doTimeout(timeout,function(){
                        notification.close();
                    });
                }
            }else{
                $.pnotify({
                    pnotify_addclass:'stack-bottomleft',
                    pnotify_animation:{effect_in: 'slide', effect_out: 'fade'},
                    pnotify_delay:timeout,
                    pnotify_history:false,
                    pnotify_stack:stack_bottomleft,
                    pnotify_text:text,
                    pnotify_type:typeP,
                    pnotify_title:titleP
                });
            }
        },

        displayNotification:function(title, msg, type){
            var image = $.icescrum.o.baseUrl + "themes/is/images/";
            image += type == "error" ?  "logo-disconnected.png" : "logo-connected.png";
            if (this.o.notifications){
                var notification = window.webkitNotifications.createNotification(image, title, $('
').html(msg.replace(/<\/?[^>]+>/gi, '')).text());
                notification.show();
                return notification;
            }
        },

        displayTemplate:function(selector, show) {
            if (show) {
                $(selector + " .text-template").show();
                $("#displayTemplate").val('1');
            } else {
                $(selector + " .text-template").hide();
                $("#displayTemplate").val('0');
            }
        },

        displayView:function() {
            $('#menu-display-list').find('.content').html('');
        },

        selectableAction:function(action, doNotConfirm, idParam, onSuccess) {
            var elements = jQuery('.ui-selected, .table-row-focus');
            if (elements.length && !this.o.deleting) {
                this.o.deleting = true;
                if (!doNotConfirm && !confirm(this.o.deleteConfirmMessage)) {
                    this.o.deleting = false;
                    return false;
                }
                var dataToSend = '';
                dataToSend = $.icescrum.postit.ids(elements, idParam);

                if (!action || (action && action.indexOf('/') == -1)) {
                    action = $.icescrum.o.currentOpenedWindow.data('id') + '/' + (action ? action : 'delete');
                }

                jQuery.ajax({type:'POST', data:dataToSend,
                            url: $.icescrum.o.baseUrlProduct + action,
                            success:function(data, textStatus) {
                                $.icescrum.o.deleting = false;
                                if (data.dialog){
                                    $(document.body).append(data.dialog);
                                    return;
                                }
                                if (onSuccess) {
                                    onSuccess(data);
                                }
                            },
                            error:function(XMLHttpRequest, textStatus, errorThrown) {
                                elements.css('background-color', '');
                                $.icescrum.o.deleting = false;
                            }
                        });
                return false;
            }
            return true;
        },

        changeRank:function(container, source, ui, name, call) {
            var postitid = ui.attr('id');
            if (!postitid){
                 return;
            }
            var idmoved = postitid.substring(postitid.lastIndexOf("-") + 1, postitid.length);
            var newPosition = $(container).index(ui) + 1;
            //finally we send the update to server
            var params = {id:idmoved};
            params[name] = newPosition;
            call(params, source);
        },

        dblclickSelectable:function(obj, delay, callback, force) {
            if (force != undefined && force) {
                if ($.icescrum.o.selectedObject.obj != "" && $('#' + $.icescrum.o.selectedObject.obj.selected.id).hasClass('ui-selected')) {
                    if ($.icescrum.o.selectedObject.callback) {
                        callback($.icescrum.o.selectedObject.obj);
                    }
                }
                return false;
            }
            if ($.icescrum.o.selectedObject.obj == "" || (obj.selected.id != $.icescrum.o.selectedObject.obj.selected.id)) {
                $.icescrum.o.selectedObject.obj = obj;
                $.icescrum.o.selectedObject.time = new Date().getTime();
                $.icescrum.o.selectedObject.callback = callback;
            }
            else {
                var c = new Date().getTime();
                var d = c - $.icescrum.o.selectedObject.time;
                if (d <= delay) {
                    if ($.icescrum.o.selectedObject.callback) {
                        $.icescrum.o.selectedObject.callback(obj);
                    }
                    $.icescrum.o.selectedObject = {obj:'',time:'',callback:''};
                }
                else {
                    $.icescrum.o.selectedObject = {obj:'',time:'',callback:''};
                    $.icescrum.dblclickSelectable(obj, delay, callback);
                }
            }
            return false;
        },

        openCommentTab:function(relation) {
            $('#commentEditorContainer').show();
            $.icescrum.openTab(relation, true);
        },

        openTab:function(relation, scrollBottom) {
            $('.panel-box a[rel=' + relation + ']').click();
            if (scrollBottom) {
                var contentWindow = jQuery('div .window-content');
                contentWindow.scrollTop(contentWindow.outerHeight());
            }
        },

        displayChart:function(container, url, save) {
            jQuery.ajax({
                        type:'GET',
                        global:false,
                        cache:true,
                        url:$.icescrum.o.baseUrlProduct + url,
                        data:'withButtonBar=false',
                        beforeSend:function() {
                            $(container).addClass('loading').html('');
                        },
                        success:function(data, textStatus) {
                            $(container).height(300);
                            $(container)
                                    .removeClass('loading')
                                    .html(data);
                            $('.save-chart', $(container)).remove();
                            if (typeof save != 'undefined' && save) {
                                $.cookie(container + $.icescrum.product.id, url);
                            }
                            var test = /\/(.*)/;
                            var match = test.exec(url);
                            if (match[1]) {
                                if (match[1].endsWith('/')){
                                    match[1] = match[1].substr(0, match[1].length - 1)
                                }
                                $('#panel-chart').find('.right').removeClass('selected');
                                $('#chart-' + match[1]).addClass('selected');
                            }
                        },
                        error:function(XMLHttpRequest, textStatus, errorThrown) {
                            var data = $.parseJSON(XMLHttpRequest.responseText);
                            $(container).css("height", null);
                            $(container)
                                    .removeClass('loading')
                                    .addClass('error-loading')
                                    .html(data.notice.text);
                        }
                    });
        },

        displayChartFromCookie:function(container, url, save) {
            var saveChartType = $.cookie(container + $.icescrum.product.id);
            if (saveChartType) {
                this.displayChart(container, saveChartType, false);
            } else {
                this.displayChart(container, url, save);
            }
        },

        listenServer:function() {
            if (!$.icescrum.o.push.websocket){
                 $.stream.options.type = 'http';
            }

            $.stream.setup({
                enableXDR: true,
                handleOpen: function(text, message) {
                        if (!$.icescrum.o.push.websocket){
                            message.index = text.indexOf("") + 12;
                        }
                },
                handleSend: function(type) {
                        if (type !== "send") {
                                return false;
                        }
                }
            });

            $.stream($.icescrum.o.streamUrl, {
                        dataType: "json",
                        openData: {useWebSocket: ($.icescrum.o.push.websocket && (window.MozWebSocket || window.WebSocket)) ? "true" : "false"},
                        throbber: {type:'lazy',delay:0},
                        open: function() {
                            $("#is-logo").removeClass().addClass('connected');
                            if ($.icescrum.o.timeout){
                                $.icescrum.o.timeout = null;
                                //reload widgets
                                if ($.icescrum.getWidgetsList().length > 0) {
                                    var tmp = $.icescrum.getWidgetsList();
                                    $.icescrum.saveWidgetsList([]);
                                    for (i = 0; i < tmp.length; i++) {
                                        $.icescrum.addToWidgetBar(tmp[i]);
                                    }
                                }
                                //reload window
                                if ($.icescrum.o.currentOpenedWindow){
                                    var hash = document.location.hash;
                                    document.location.hash = '';
                                    setTimeout(function(){
                                        document.location.hash = hash;
                                    },50);
                                }
                            }
                        },
                        error: function() {
                            $("#is-logo").removeClass().addClass('disconnected');
                        },
                        close: function(event, stream) {
                            if (!stream.options.reconnect) {
                                $("#is-logo").removeClass().addClass('disconnected');
                                $.icescrum.o.timeout = $.icescrum.o.timeout ? ($.icescrum.o.timeout + 60 > 300 ? 300 : $.icescrum.o.timeout + 60) : 10;
                                $.icescrum.renderNotice('Connection lost, retry in '+$.icescrum.o.timeout+'sec', 'error', '');
                                setTimeout(function(){
                                    $.icescrum.listenServer();
                                }, $.icescrum.o.timeout * 1000);
                            }
                        },
                        message: function(event) {
                            try {
                                $(event.data).each(function() {
                                    if (this.call && this.object) {
                                        if (this.object['class']) {
                                            var type = this.object['class'].substring(this.object['class'].lastIndexOf('.') + 1).toLowerCase();
                                            this.call = (this.call == 'delete') ? 'remove' : this.call;
                                            $.event.trigger(this.call + '_' + type + '.stream', this.object);
                                        } else{
                                            $.event.trigger(this.call + '.stream', this.object);
                                        }
                                    } else if(this.broadcaster) {
                                        if(this.broadcaster.users && this.broadcaster.users.length > 1){
                                            var users = [];
                                            $(this.broadcaster.users).each(function(){
                                                users.push(this.fullName);
                                            });
                                            $('#menu-project').find('.content').attr('title',users.length+' users online ('+users.join(', ')+')');
                                        }else{
                                            $('#menu-project').find('.content').attr('title', 'Do you feel lonely?');
                                        }
                                    }
                                });
                            } catch(e) {
                            }
                        }
                    });
        },

        formattedTaskEstimation:function(estimation, defaultChar) {
            if(estimation == null && defaultChar)
                return '?';
            else if(estimation == null)
                return null;
            else
                return estimation.toString().indexOf('.') == -1 ? estimation.toString().concat('.0') : estimation;
        },

        redirectOnLogin:function(data){
            var refVar = "?ref=";
            var ref = '';
            if (window.location.href.indexOf(refVar) > 0){
                ref = window.location.href.slice(window.location.href.indexOf(refVar) + refVar.length);
            }
            var hash = ref ? ref : data ? data.url : '';
            if(hash.indexOf('#') == 0){
                var url = document.location.toString();
                document.location = url.substring(0,url.indexOf('login')) + hash;
            }else {
                document.location = hash;
            }
        },

        showAndHideOnClickAnywhere:function(selector){
            var element = $(selector);
            if(element.css('display') == 'none') {
                element.show();
                var handler = function(event){
                    element.hide();
                    $(document).off("click", handler);
                }
                setTimeout(function () {
                    $(document).on("click", handler)
                ;}, 10);
            }
        },

        showUpgrade:function(){
            if (this.o.showUpgrade){
                var upgrade = $('.upgrade');
                if (upgrade.length && $.cookie('hide_upgrade') != "true"){
                    upgrade.show();
                    upgrade.find('.close').click(function(){
                        upgrade.remove();
                        $.cookie('hide_upgrade', true, { expires: 30 });
                    });
                }else if(upgrade.length){
                    upgrade.remove();
                }
            }
        }
    }

})(jQuery);

$.fn.icescrum = function(options) {
    if ((typeof options) == "string") {
        //TODO : refactor icescrum('toolbar')
        if (options == 'toolbar')
            return $.icescrum[options](this, arguments);
        else
            return $.icescrum[options].apply(this, arguments);
    }
    return $.icescrum;
};

$(document).ready(function($) {
    $.ajaxSetup({ timeout:45000 });
    $(document).ajaxSend(function(event, xhr){
        xhr.setRequestHeader("If-Modified-Since",new Date(1970,1,1).toUTCString());
        xhr.setRequestHeader("Pragma","no-cache");
    });
    $.icescrum.showUpgrade();
    $.fn.editable.defaults.placeholder = " ";
});
<|endoftext|>


<commit_before>/**
 * This file is part of the DreamFactory Services Platform(tm) (DSP)
 *
 * DreamFactory Services Platform(tm) <http://github.com/dreamfactorysoftware/dsp-core>
 * Copyright 2012-2014 DreamFactory Software, Inc. <support@dreamfactory.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var SchemaCtrl = function( dfLoadingScreen, $scope, DSP_URL, DB, $http, getSchemaServices ) {

    dfLoadingScreen.stop();

    Scope = $scope;
    $scope.typeOptions = [
        {value: "id"},
        {value: "string"},
        {value: "integer"},
        {value: "text"},
        {value: "boolean"},
        {value: "binary"},
        {value: "float"},
        {value: "decimal"},
        {value: "datetime"},
        {value: "date"},
        {value: "time"},
        {value: "reference"},
        {value: "user_id"},
        {value: "user_id_on_create"},
        {value: "user_id_on_update"},
        {value: "timestamp"},
        {value: "timestamp_on_create"},
        {value: "timestamp_on_update"}
    ];
    var editor;
    $scope.table = {};
    //$scope.service = {;
    $scope.advanced = false;
    //var service;
    editor = ace.edit("schema-editor");
    editor.getSession().setMode("ace/mode/json");
    $scope.dbServices = getSchemaServices.data.record;
    //console.log($scope.dbServices);
    $scope.loadServices = function(current){
        $scope.currentTables = [];
        if(current){
            $http.get(DSP_URL + "/rest/" + $scope.service + "/_schema").then(function(response){
                //console.log( $scope.dbServices[$scope.service]);
                $scope.dbServices[$scope.service_index].tables = [];
                response.data.resource.forEach(function(table){

                    $scope.dbServices[$scope.service_index].tables.push(table);
                    $scope.currentTables.push(table.name);
                })
            });
        }else{
            $scope.dbServices.forEach(function(service, index){
                $http.get(DSP_URL + "/rest/" + service.api_name + "/_schema").then(function(response){

                    service.tables = [];

                    service.service_index = index;
                    response.data.resource.forEach(function(table){

                        service.tables.push(table);
                        $scope.currentTables.push(table.name);

                    })
                });
            })
        }

    }
    $scope.loadServices();
    $scope.referenceFields  = [];
    $scope.loadNewSchema = function(table){
        $scope.import = false;
        $scope.table = table;
        $http.get(CurrentServer + "/rest/" + $scope.service + "/_schema/" + $scope.table.name)
            .then(function(response){
                $scope.table.schema = response;
            })
    }
    $scope.loadSchema = function(advanced){
        $scope.import = false;
        $scope.table = this.table;
        $scope.currentTable = $scope.table.name;
        $scope.service = this.service.api_name;
        $scope.service_index = this.service.service_index;
        $http.get(CurrentServer + "/rest/" + this.service.api_name + "/_schema/" + this.table.name)
            .then(function(response){
                $scope.table.schema = response;
                //console.log(table.schema);
                if(advanced){
                    $scope.advanced = true;
                    editor.setValue(angular.toJson($scope.table.schema.data, true), -1);
                }else{
                    $scope.advanced = false;
                }

            })
    }
    $scope.loadReferenceFields = function(){
        var refTable = this.column.ref_table;
        $http.get(CurrentServer + "/rest/" + $scope.service + "/_schema/" + refTable)
            .then(function(response){
                //console.log(response);
                $scope.referenceFields = response.data.field;

            })
    }
    $scope.showImport = function(){
        editor.setValue("");
        $scope.advanced = true;
        $scope.import = true;
    }
    $scope.toggleJSON = function(){
        if($scope.advanced){
            $scope.table.schema.data = JSON.parse(editor.getValue());
            $scope.advanced = false;
        }else{
            $scope.advanced = true;
            editor.setValue(angular.toJson($scope.table.schema.data, true), -1);
        }
    }
    $scope.addColumn = function(){
        $scope.newColumn = {
            name : "New_Column",
            is_new : true
        };
        //$scope.currentField = "New_Column";
        $scope.table.schema.data.field.unshift($scope.newColumn)
    }
    $scope.deleteColumn = function(){
        //console.log(this);
        if ( !confirm( "Are you sure you want to delete " + this.column.name) ) {
            return;
        }
        $scope.table.schema.data.field.splice(this.$index, 1);
        $(function(){
            new PNotify({
                title: 'Schema',
                text: 'Column removed from working set, press Update Schema to save changes',
                type: 'warning'
            });
        });

    }
    $scope.updateJSONSchema = function(){
        $http.put(CurrentServer + "/rest/" + $scope.service + "/_schema?return_schema=true" , editor.getValue()).then(function(response){
            $(function(){
                new PNotify({
                    title: 'Schema',
                    text: 'Updated Successfully',
                    type: 'success'
                });
            });
        })
    }
    $scope.updateSchema = function(){
        $http.put(CurrentServer + "/rest/" + $scope.service + "/_schema?return_schema=true" , {table:$scope.table.schema.data}).then(function(response){
            $(function(){
                new PNotify({
                    title: 'Schema',
                    text: 'Updated Successfully',
                    type: 'success'
                });
            });
        })
    }
    $scope.postJSONSchema = function(){
        $http.post(CurrentServer + "/rest/" + $scope.service + "/_schema?return_schema=true", editor.getValue()).then(function(response){
            $(function(){
                new PNotify({
                    title: 'Schema',
                    text: 'Posted Successfully',
                    type: 'success'
                });
            });
            $scope.loadServices(true);

        })
    }
    $scope.createTable = function(){
        var name = this.newTableName;
        this.newTableName = "";
        var requestObject = {
            name : name,
            field: [
                {name : "id",
                    type : "id"
                }
            ]
        };
        $http.post(CurrentServer + "/rest/" + this.service.api_name + "/_schema?return_schema=true" , {table:requestObject}).then(function(response){
            //$scope.loadServices();
            $scope.currentTable = name;
            $scope.currentTables.unshift(name);
            $scope.dbServices[$scope.service_index].tables.unshift(requestObject);
            $scope.loadNewSchema(requestObject);

        })
    }

    $scope.dropTable = function(){
        var table = this;
        if ( !confirm( "Are you sure you want to delete " + this.table.name) ) {
            return;
        }
        $http.delete(CurrentServer + "/rest/" + this.service.api_name + "/_schema/" + this.table.name)
            .then(function(response){
                $scope.service_index = table.service_index;
                $scope.loadServices(true);
                $scope.table = {};
            })
    }
    $scope.setService = function(){
        $scope.service = this.service.api_name;
        $scope.service_index = this.service.service_index;
    }
    $scope.validateJSON = function() {
        try {
            var result = JSON.parse( editor.getValue() );
            if ( result ) {
                $(function(){
                    new PNotify({
                        title: 'Schema',
                        text: 'JSON is valid',
                        type: 'success'
                    });
                });
                editor.setValue(angular.toJson(result, true), -1);
            }
        }
        catch ( e ) {
            $(function(){
                new PNotify({
                    title: 'Schema',
                    text: e,
                    type: 'error'
                });
            });
        }
    };
};

<commit_msg>removing refresh call<commit_after>/**
 * This file is part of the DreamFactory Services Platform(tm) (DSP)
 *
 * DreamFactory Services Platform(tm) <http://github.com/dreamfactorysoftware/dsp-core>
 * Copyright 2012-2014 DreamFactory Software, Inc. <support@dreamfactory.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var SchemaCtrl = function( dfLoadingScreen, $scope, DSP_URL, DB, $http, getSchemaServices ) {

    dfLoadingScreen.stop();

    Scope = $scope;
    $scope.typeOptions = [
        {value: "id"},
        {value: "string"},
        {value: "integer"},
        {value: "text"},
        {value: "boolean"},
        {value: "binary"},
        {value: "float"},
        {value: "decimal"},
        {value: "datetime"},
        {value: "date"},
        {value: "time"},
        {value: "reference"},
        {value: "user_id"},
        {value: "user_id_on_create"},
        {value: "user_id_on_update"},
        {value: "timestamp"},
        {value: "timestamp_on_create"},
        {value: "timestamp_on_update"}
    ];
    var editor;
    $scope.table = {};
    //$scope.service = {;
    $scope.advanced = false;
    //var service;
    editor = ace.edit("schema-editor");
    editor.getSession().setMode("ace/mode/json");
    $scope.dbServices = getSchemaServices.data.record;
    //console.log($scope.dbServices);
    $scope.loadServices = function(current){
        $scope.currentTables = [];
        if(current){
            $http.get(DSP_URL + "/rest/" + $scope.service + "/_schema").then(function(response){
                //console.log( $scope.dbServices[$scope.service]);
                $scope.dbServices[$scope.service_index].tables = [];
                response.data.resource.forEach(function(table){
                    $scope.dbServices[$scope.service_index].tables.push(table);
                    $scope.currentTables.push(table.name);
                })
            });
        }else{
            $scope.dbServices.forEach(function(service, index){
                $http.get(DSP_URL + "/rest/" + service.api_name + "/_schema").then(function(response){

                    service.tables = [];

                    service.service_index = index;
                    response.data.resource.forEach(function(table){

                        service.tables.push(table);
                        $scope.currentTables.push(table.name);

                    })
                });
            })
        }

    }
    $scope.loadServices();
    $scope.referenceFields  = [];
    $scope.loadNewSchema = function(table){
        $scope.import = false;
        $scope.table = table;
        $http.get(CurrentServer + "/rest/" + $scope.service + "/_schema/" + $scope.table.name)
            .then(function(response){
                $scope.table.schema = response;
            })
    }
    $scope.loadSchema = function(advanced){
        $scope.import = false;
        $scope.table = this.table;
        $scope.currentTable = $scope.table.name;
        $scope.service = this.service.api_name;
        $scope.service_index = this.service.service_index;
        $http.get(CurrentServer + "/rest/" + this.service.api_name + "/_schema/" + this.table.name)
            .then(function(response){
                $scope.table.schema = response;
                //console.log(table.schema);
                if(advanced){
                    $scope.advanced = true;
                    editor.setValue(angular.toJson($scope.table.schema.data, true), -1);
                }else{
                    $scope.advanced = false;
                }

            })
    }
    $scope.loadReferenceFields = function(){
        var refTable = this.column.ref_table;
        $http.get(CurrentServer + "/rest/" + $scope.service + "/_schema/" + refTable)
            .then(function(response){
                //console.log(response);
                $scope.referenceFields = response.data.field;

            })
    }
    $scope.showImport = function(){
        editor.setValue("");
        $scope.advanced = true;
        $scope.import = true;
    }
    $scope.toggleJSON = function(){
        if($scope.advanced){
            $scope.table.schema.data = JSON.parse(editor.getValue());
            $scope.advanced = false;
        }else{
            $scope.advanced = true;
            editor.setValue(angular.toJson($scope.table.schema.data, true), -1);
        }
    }
    $scope.addColumn = function(){
        $scope.newColumn = {
            name : "New_Column",
            is_new : true
        };
        //$scope.currentField = "New_Column";
        $scope.table.schema.data.field.unshift($scope.newColumn)
    }
    $scope.deleteColumn = function(){
        //console.log(this);
        if ( !confirm( "Are you sure you want to delete " + this.column.name) ) {
            return;
        }
        $scope.table.schema.data.field.splice(this.$index, 1);
        $(function(){
            new PNotify({
                title: 'Schema',
                text: 'Column removed from working set, press Update Schema to save changes',
                type: 'warning'
            });
        });

    }
    $scope.updateJSONSchema = function(){
        $http.put(CurrentServer + "/rest/" + $scope.service + "/_schema?return_schema=true" , editor.getValue()).then(function(response){
            $(function(){
                new PNotify({
                    title: 'Schema',
                    text: 'Updated Successfully',
                    type: 'success'
                });
            });
        })
    }
    $scope.updateSchema = function(){
        $http.put(CurrentServer + "/rest/" + $scope.service + "/_schema?return_schema=true" , {table:$scope.table.schema.data}).then(function(response){
            $(function(){
                new PNotify({
                    title: 'Schema',
                    text: 'Updated Successfully',
                    type: 'success'
                });
            });
        })
    }
    $scope.postJSONSchema = function(){
        $http.post(CurrentServer + "/rest/" + $scope.service + "/_schema?return_schema=true", editor.getValue()).then(function(response){
            $(function(){
                new PNotify({
                    title: 'Schema',
                    text: 'Posted Successfully',
                    type: 'success'
                });
            });
            $scope.loadServices(true);

        })
    }
    $scope.createTable = function(){
        var name = this.newTableName;
        this.newTableName = "";
        var requestObject = {
            name : name,
            field: [
                {name : "id",
                    type : "id"
                }
            ]
        };
        $http.post(CurrentServer + "/rest/" + this.service.api_name + "/_schema?return_schema=true" , {table:requestObject}).then(function(response){
            //$scope.loadServices();
            $scope.currentTable = name;
            $scope.currentTables.unshift(name);
            $scope.dbServices[$scope.service_index].tables.unshift(requestObject);
            $scope.loadNewSchema(requestObject);

        })
    }

    $scope.dropTable = function(){
        var table_index = this.$index;
        if ( !confirm( "Are you sure you want to delete " + this.table.name) ) {
            return;
        }
        $http.delete(CurrentServer + "/rest/" + this.service.api_name + "/_schema/" + this.table.name)
            .then(function(response){
                $scope.dbServices[$scope.service_index].tables.splice(table_index , 1);
                $scope.table = {};
            })
    }
    $scope.setService = function(){
        $scope.service = this.service.api_name;
        $scope.service_index = this.service.service_index;
    }
    $scope.validateJSON = function() {
        try {
            var result = JSON.parse( editor.getValue() );
            if ( result ) {
                $(function(){
                    new PNotify({
                        title: 'Schema',
                        text: 'JSON is valid',
                        type: 'success'
                    });
                });
                editor.setValue(angular.toJson(result, true), -1);
            }
        }
        catch ( e ) {
            $(function(){
                new PNotify({
                    title: 'Schema',
                    text: e,
                    type: 'error'
                });
            });
        }
    };
};

<|endoftext|>


<commit_before>'use strict';

var test = require('tape');
var postcss = require('postcss');
var plugin = require('../src/');
var name = require('../package.json').name;

var processTest = function (css, options) {
  return postcss(plugin(options)).process(css);
};

var sortOrderTests = [
  {
    message: 'Keep same order for identical properties.',
    fixture: 'a{flex: 0;flex: 2;}',
    expected: 'a{flex: 0;flex: 2;}'
  },
  {
    message: 'Sort alphabetically with no order defined.',
    fixture: 'a{flex: 0;border: 0;}',
    expected: 'a{border: 0;flex: 0;}'
  },
  {
    message: 'Sort alphabetically with a defined order.',
    fixture: 'a{flex: 0;border: 0;}',
    expected: 'a{border: 0;flex: 0;}',
    options: { order: 'alphabetically' }
  },
  {
    message: 'Sort according to custom order.',
    fixture: 'a{border: 0;z-index: 0;}',
    expected: 'a{z-index: 0;border: 0;}',
    options: { customOrder: 'tests/custom-order.json' }
  },
  {
    message: 'Sort according to SMACSS.',
    fixture: 'a{border: 0;flex: 0;}',
    expected: 'a{flex: 0;border: 0;}',
    options: { order: 'smacss' }
  },
  {
    message: 'Sort according to Concentric CSS.',
    fixture: 'a{border: 0;flex: 0;}',
    expected: 'a{flex: 0;border: 0;}',
    options: { order: 'concentric-css' }
  }
];

var commentOrderTests = [
  {
    message: 'Keep comment intact.',
    fixture: 'a{flex: 0;/*flex*/}',
    expected: 'a{flex: 0;/*flex*/}'
  },
  {
    message: 'Keep dangling comment intact.',
    fixture: 'a{flex: 0;\n/*end*/}',
    expected: 'a{flex: 0;\n/*end*/}'
  },
  {
    message: 'Keep multiple comments intact.',
    fixture: 'a{flex: 0;\n/*flex*/\n/*flex 2*/}',
    expected: 'a{flex: 0;\n/*flex*/\n/*flex 2*/}'
  },
  {
    message: 'Keep newline comment above declaration.',
    fixture: 'a{flex: 0;\n/*border*/\nborder: 0;}',
    expected: 'a{\n/*border*/\nborder: 0;flex: 0;}'
  },
  {
    message: 'Keep inline comment beside declaration.',
    fixture: 'a{flex: 0;\nborder: 0; /*border*/}',
    expected: 'a{\nborder: 0; /*border*/flex: 0;}'
  }
];

test('Should order CSS declarations.', function (t) {
  // Set amount of assertions by setting two assertions per sort order test
  t.plan(sortOrderTests.length * 2);

  sortOrderTests.forEach(function (test) {
    var options = test.options || {};

    processTest(test.fixture, options).then(function (result) {
      t.equal(result.css, test.expected, test.message);
      t.equal(result.warnings().length, 0);
    });
  });
});

test('Should retain comments.', function (t) {
  // Set amount of assertions by setting two assertions per sort order test
  t.plan(commentOrderTests.length * 2);

  commentOrderTests.forEach(function (test) {
    var options = test.options || {};

    processTest(test.fixture, options).then(function (result) {
      t.equal(result.css, test.expected, test.message);
      t.equal(result.warnings().length, 0);
    });
  });
});

test('Should use the PostCSS plugin API.', function (t) {
  t.plan(2);

  t.ok(plugin().postcssVersion, 'Able to access version.');
  t.equal(plugin().postcssPlugin, name, 'Able to access name.');
});
<commit_msg>Add nested declaration handling tests<commit_after>'use strict';

var test = require('tape');
var postcss = require('postcss');
var plugin = require('../src/');
var name = require('../package.json').name;

var processTest = function (css, options) {
  return postcss(plugin(options)).process(css);
};

var sortOrderTests = [
  {
    message: 'Keep same order for identical properties.',
    fixture: 'a{flex: 0;flex: 2;}',
    expected: 'a{flex: 0;flex: 2;}'
  },
  {
    message: 'Sort alphabetically with no order defined.',
    fixture: 'a{flex: 0;border: 0;}',
    expected: 'a{border: 0;flex: 0;}'
  },
  {
    message: 'Sort alphabetically with a defined order.',
    fixture: 'a{flex: 0;border: 0;}',
    expected: 'a{border: 0;flex: 0;}',
    options: { order: 'alphabetically' }
  },
  {
    message: 'Sort according to custom order.',
    fixture: 'a{border: 0;z-index: 0;}',
    expected: 'a{z-index: 0;border: 0;}',
    options: { customOrder: 'tests/custom-order.json' }
  },
  {
    message: 'Sort according to SMACSS.',
    fixture: 'a{border: 0;flex: 0;}',
    expected: 'a{flex: 0;border: 0;}',
    options: { order: 'smacss' }
  },
  {
    message: 'Sort according to Concentric CSS.',
    fixture: 'a{border: 0;flex: 0;}',
    expected: 'a{flex: 0;border: 0;}',
    options: { order: 'concentric-css' }
  }
];

var commentOrderTests = [
  {
    message: 'Keep comment intact.',
    fixture: 'a{flex: 0;/*flex*/}',
    expected: 'a{flex: 0;/*flex*/}'
  },
  {
    message: 'Keep dangling comment intact.',
    fixture: 'a{flex: 0;\n/*end*/}',
    expected: 'a{flex: 0;\n/*end*/}'
  },
  {
    message: 'Keep multiple comments intact.',
    fixture: 'a{flex: 0;\n/*flex*/\n/*flex 2*/}',
    expected: 'a{flex: 0;\n/*flex*/\n/*flex 2*/}'
  },
  {
    message: 'Keep newline comment above declaration.',
    fixture: 'a{flex: 0;\n/*border*/\nborder: 0;}',
    expected: 'a{\n/*border*/\nborder: 0;flex: 0;}'
  },
  {
    message: 'Keep inline comment beside declaration.',
    fixture: 'a{flex: 0;\nborder: 0; /*border*/}',
    expected: 'a{\nborder: 0; /*border*/flex: 0;}'
  }
];

var nestedDeclarationTests = [
  {
    message: 'Sort nested declarations.',
    fixture: 'a{a{flex: 0;border: 0;}}',
    expected: 'a{a{border: 0;flex: 0;}}',
  },
  {
    message: 'Sort nested at-rule declarations.',
    fixture: 'a{@media(){flex: 0;border: 0;}}',
    expected: 'a{@media(){border: 0;flex: 0;}}'
  }
];

test('Should order CSS declarations.', function (t) {
  // Set amount of assertions by setting two assertions per sort order test
  t.plan(sortOrderTests.length * 2);

  sortOrderTests.forEach(function (test) {
    var options = test.options || {};

    processTest(test.fixture, options).then(function (result) {
      t.equal(result.css, test.expected, test.message);
      t.equal(result.warnings().length, 0);
    });
  });
});

test('Should retain comments.', function (t) {
  // Set amount of assertions by setting two assertions per sort order test
  t.plan(commentOrderTests.length * 2);

  commentOrderTests.forEach(function (test) {
    var options = test.options || {};

    processTest(test.fixture, options).then(function (result) {
      t.equal(result.css, test.expected, test.message);
      t.equal(result.warnings().length, 0);
    });
  });
});

test('Should order nested CSS declarations.', function (t) {
  // Set amount of assertions by setting two assertions per sort order test
  t.plan(nestedDeclarationTests.length * 2);

  nestedDeclarationTests.forEach(function (test) {
    var options = test.options || {};

    processTest(test.fixture, options).then(function (result) {
      t.equal(result.css, test.expected, test.message);
      t.equal(result.warnings().length, 0);
    });
  });
});

test('Should use the PostCSS plugin API.', function (t) {
  t.plan(2);

  t.ok(plugin().postcssVersion, 'Able to access version.');
  t.equal(plugin().postcssPlugin, name, 'Able to access name.');
});
<|endoftext|>