text:
package org.apache.sling.hc.samples.impl;

import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.hc.annotations.SlingHealthCheck;
import org.apache.sling.hc.api.HealthCheck;
import org.apache.sling.hc.api.Result;
import org.apache.sling.hc.util.FormattingResultLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Sample Health Check that uses a cron expression to
 *  schedule regular async execution.
 */

@SlingHealthCheck(
        configurationFactory=true,
        configurationPolicy=ConfigurationPolicy.REQUIRE,
        metatype=true)
@Service
public class AsyncHealthCheckSample implements HealthCheck {

    private final Logger log = LoggerFactory.getLogger(AsyncHealthCheckSample.class);

    // static because for factories, not always the same instance is returned for
    // the same service reference
    private static final AtomicInteger counter = new AtomicInteger();

    public static final int PERIOD_SECONDS = 5;

    @Override
    public Result execute() {

        final long toWait = (long)(Math.random() * 2 * PERIOD_SECONDS);
        log.info("{} - Waiting {} seconds to simulate an expensive operation...", this, toWait);
        try {
            Thread.sleep(toWait * 1000L);
        } catch(InterruptedException iex) {
            log.warn("Sleep interrupted", iex);
        }

        final int value =  counter.incrementAndGet();
        log.info("{} - counter set to {}", this, value);

        final FormattingResultLog resultLog = new FormattingResultLog();

        resultLog.info("{} - counter value set to {} at {}", this, value, new Date());
        if(value % 2 != 0) {
            resultLog.warn("Counter value ({}) is not even", value);
        }
        return new Result(resultLog);
    }

}


text:
FROM scratch

LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1
LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/
LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/
LABEL operators.operatorframework.io.bundle.package.v1=mongodb-enterprise
LABEL operators.operatorframework.io.bundle.channels.v1=stable
LABEL operators.operatorframework.io.bundle.channel.default.v1=stable

COPY 1.2.1/manifests /manifests/
COPY 1.2.1/metadata /metadata/
LABEL com.redhat.openshift.versions="v4.5,v4.6"
LABEL com.redhat.delivery.backport=true
LABEL com.redhat.delivery.operator.bundle=true

redpajama_set_name: RedPajamaGithub


text:
module.exports = function(sequelize, DataTypes){
  return sequelize.define('Quiz',{
    pregunta:{
      type: DataTypes.STRING,
      validate:{
        notEmpty:{
          msg: 'Falta la pregunta'
        }
      }
    },
    respuesta:{
      type: DataTypes.STRING,
      validate:{
        notEmpty:{
          msg: 'Falta la respuesta'
        }
      }
    },
    tematica:{
      type: DataTypes.STRING,
      validate:{
        notEmpty:{
          msg: 'Falta la temática'
        }
      }
    }
  });
}


text:
cask 'buttercup' do
  version '0.22.0'
  sha256 'e7944cf5eb785c5032e318540d74b09a782d345fb3239df5c1bb9e920709ea02'

  # github.com/buttercup/buttercup-desktop was verified as official when first introduced to the cask
  url "https://github.com/buttercup/buttercup-desktop/releases/download/v#{version}/buttercup-desktop-#{version}-mac.zip"
  appcast 'https://github.com/buttercup/buttercup-desktop/releases.atom',
          checkpoint: '40327d93aac63afd3204f645d970668f309ca375086d4cd8488d2f9ee948ef26'
  name 'Buttercup'
  homepage 'https://buttercup.pw/'

  app 'Buttercup.app'
end


text:
package com.cmcm.greendamexplorer.core.common;

import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.LruCache;

public class DrawableLoder {

    private static LruCache<String, Drawable> mMemoryCache;

    private static DrawableLoder mImageLoader;

    private DrawableLoder() {
        // 获取应用程序最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        // 设置图片缓存大小为程序最大可用内存的1/8
        mMemoryCache = new LruCache<String, Drawable>(cacheSize);
    }

    public synchronized static DrawableLoder getInstance() {
        if (mImageLoader == null) {
            synchronized (DrawableLoder.class) {
                mImageLoader = new DrawableLoder();
            }
        }
        return mImageLoader;
    }

    public void addDrawableToMemoryCache(String url, Drawable bitmap) {
        if (getDrawableFromMemoryCache(url) == null) {
            synchronized (mMemoryCache) {
                if (url != null && bitmap != null) {
                    mMemoryCache.put(url, bitmap);
                }
            }
        }
    }

    public Drawable getDrawableFromMemoryCache(String key) {
        return mMemoryCache.get(key);
    }

}


text:
// @see https://sharp.pixelplumbing.com/api-operation#rotate

// Strict mode.
'use strict'

// Local modules.
const queue = require('../../lib/queue')

// Configure.
const options = {
  angle: {
    default: 'auto',
    desc: 'Angle of rotation',
    type: 'number'
  },
  background: {
    desc: 'String parsed by the color module to extract values for red, green, blue and alpha',
    type: 'string'
  }
}

// Command builder.
const builder = (yargs) => {
  const optionNames = Object.keys(options)
  return yargs
    .strict()
    .example('$0 rotate', 'The output will be auto-rotated using EXIF Orientation tag')
    .epilog('For more information on available options, please visit https://sharp.pixelplumbing.com/api-operation#rotate')
    .options(options)
    .global(optionNames, false)
    .group(optionNames, 'Command Options')
}

// Command handler.
const handler = (args) => {
  if (args.angle === 'auto') args.angle = undefined // Cast.
  return queue.push(['rotate', (sharp) => {
    return sharp.rotate(args.angle, {
      background: args.background
    })
  }])
}

// Exports.
module.exports = {
  command: 'rotate [angle]',
  describe: 'Rotate the output image',
  builder,
  handler
}


text:
package com.adaptris.core;

import javax.validation.constraints.NotBlank;
import org.apache.commons.lang3.BooleanUtils;
import com.adaptris.annotation.AdapterComponent;
import com.adaptris.annotation.AdvancedConfig;
import com.adaptris.annotation.ComponentProfile;
import com.adaptris.annotation.DisplayOrder;
import com.adaptris.annotation.InputFieldDefault;
import com.adaptris.core.util.LifecycleHelper;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.Getter;
import lombok.Setter;

/**
 *

 * A Service instance that references a Service made available via {@link SharedComponentList}.
 *

 *

 * By default the looked-up service is deep cloned before being loaded into your workflows.

 * You can turn off cloning by simply setting "clone-service=true".
 *

 *
 * @config shared-service
 * @author amcgrath
 *
 */
@XStreamAlias("shared-service")
@AdapterComponent
@ComponentProfile(summary = "A Service that refers to another Service configured elsewhere",
    tag = "service")
@DisplayOrder(order = {"lookupName", "cloneService"})
public class SharedService extends SharedServiceImpl {

  @AdvancedConfig
  @InputFieldDefault(value="true")
  private Boolean cloneService;
  /**
   * Set the name of the service that will be looked up from
   * {@link SharedComponentList#getServices()},
   *
   */
  @NotBlank
  @Getter
  @Setter
  private String lookupName;

  private transient Service clonedService;

  public SharedService() {
  }

  public SharedService(String lookupName) {
    this();
    setLookupName(lookupName);
  }

  private Service getProxiedService() {
    try {
      if (clonedService == null) {
        Service lookedUpService = (Service) triggerJndiLookup(getLookupName());
        if(cloneService())
          clonedService = deepClone(lookedUpService);
        else
          clonedService = lookedUpService;
      }
    }
    catch (CoreException e) {
      throw new RuntimeException(e);
    }
    return clonedService;
  }

  @Override
  public void init() throws CoreException {
    LifecycleHelper.init(getProxiedService());
  }

  @Override
  public void start() throws CoreException {
    LifecycleHelper.start(getProxiedService());
  }

  @Override
  public void stop() {
    LifecycleHelper.stop(getProxiedService());
  }

  @Override
  public void close() {
    LifecycleHelper.close(getProxiedService());
  }

  @Override
  public void prepare() throws CoreException {
    LifecycleHelper.registerEventHandler(getProxiedService(), eventHandler);
    LifecycleHelper.prepare(getProxiedService());
  }

  @Override
  public void doService(AdaptrisMessage msg) throws ServiceException {
    applyService(getProxiedService(), msg);
  }

  @Override
  public boolean isBranching() {
    return getProxiedService().isBranching();
  }

  boolean cloneService() {
    return BooleanUtils.toBooleanDefaultIfNull(getCloneService(), true);
  }

  public Boolean getCloneService() {
    return cloneService;
  }

  public void setCloneService(Boolean cloneService) {
    this.cloneService = cloneService;
  }

  Service getClonedService() {
    return clonedService;
  }

  void setClonedService(Service clonedService) {
    this.clonedService = clonedService;
  }

}


text:
#include "tsound.h"

#ifndef TNZCORE_LIGHT
#include "tthread.h"
#endif
#include "tsop.h"
#include <set>
#include "tsystem.h"

#include <mmsystem.h>

//=========================================================

// forward declarations
class TSoundOutputDeviceImp;
class TSoundInputDeviceImp;

//=========================================================

namespace {
void CALLBACK recordCB(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1,
                       DWORD dwParam2);

bool setRecordLine(TSoundInputDevice::Source typeInput);

MMRESULT getLineInfo(HMIXEROBJ hMixer, DWORD dwComponentType, MIXERLINE &mxl);

MMRESULT getLineControl(MIXERCONTROL &mxc, HMIXEROBJ hMixer, DWORD dwLineID,
                        DWORD dwControlType);

MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue);

MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue);

MMRESULT isaFormatSupported(int sampleRate, int channelCount, int bitPerSample,
                            bool input);

DWORD WINAPI MyWaveOutCallbackThread(LPVOID lpParameter);
void getAmplitude(int &amplitude, const TSoundTrackP st, TINT32 sample);
}

//==============================================================================
//     Class to send the message that a playback is completed
//==============================================================================
#ifndef TNZCORE_LIGHT
class EndPlayMsg final : public TThread::Message {
public:
  EndPlayMsg(TSoundOutputDeviceListener *notifier) { m_listener = notifier; }

  TThread::Message *clone() const override { return new EndPlayMsg(*this); }

  void onDeliver() override { m_listener->onPlayCompleted(); }

private:
  TSoundOutputDeviceListener *m_listener;
};
#endif

/*
static int makeDWORD(const short lo, const short hi) {
  int dw = hi << 16;
  dw |= lo;
  return dw;
}
*/

//==============================================================================
class WavehdrQueue;

class TSoundOutputDeviceImp {
public:
  HWAVEOUT m_wout;
  WavehdrQueue *m_whdrQueue;
  TSoundTrackFormat m_currentFormat;
  std::set<int> m_supportedRate;

  TThread::Mutex m_mutex;

  bool m_stopped;
  bool m_isPlaying;
  bool m_looped;
  bool m_scrubbing;

  std::set<TSoundOutputDeviceListener *> m_listeners;
  DWORD m_notifyThreadId;

  HANDLE m_closeDevice;

  TSoundOutputDeviceImp();
  ~TSoundOutputDeviceImp();

  bool doOpenDevice(const TSoundTrackFormat &format);
  bool doPlay(WAVEHDR *whdr, const TSoundTrackFormat format);
  bool doCloseDevice();

  bool verifyRate();
  void insertAllRate();
};

//==============================================================================

class WavehdrQueue {
public:
  WavehdrQueue(TSoundOutputDeviceImp *devImp, int slotCount)
      : m_devImp(devImp)
      , m_items()
      , m_queuedItems()
      , m_slotCount(slotCount)
      , m_mutex()
      , m_lastOffset(0) {}

  ~WavehdrQueue() {}

  void put(TSoundTrackP &subTrack);
  WAVEHDR *get();
  bool popFront(int count);
  void pushBack(WAVEHDR *whdr, TSoundTrackP st);
  int size();
  void clear();
  bool isAllQueuedItemsPlayed();

private:
  std::list<std::pair<WAVEHDR *, TSoundTrackP>> m_items;
  std::list<WAVEHDR *> m_queuedItems;

  TThread::Mutex m_mutex;
  int m_slotCount;
  int m_lastOffset;
  TSoundOutputDeviceImp *m_devImp;
  TSoundTrackP m_lastTrack;
};

//==============================================================================

static WAVEHDR *prepareWaveHeader(HWAVEOUT wout, const TSoundTrackP &subTrack,
                                  ULONG &count) {
  WAVEHDR *whdr = new WAVEHDR;
  memset(whdr, 0, sizeof(WAVEHDR));
  whdr->dwBufferLength = subTrack->getSampleSize() * subTrack->getSampleCount();
  whdr->lpData         = new char[whdr->dwBufferLength];
  whdr->dwFlags        = 0;
  whdr->dwUser         = count;
  memcpy(whdr->lpData, subTrack->getRawData(), whdr->dwBufferLength);

  MMRESULT ret = waveOutPrepareHeader(wout, whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) {
    delete[] whdr->lpData;
    delete whdr;
    return 0;
  }
  ++count;
  return whdr;
}

//==============================================================================

void WavehdrQueue::put(TSoundTrackP &subTrack) {
  assert(subTrack->getRawData());
  static ULONG count = 1;

  // codice messo per tab: facendo il play al rilascio del mouse e su piu'
  // colonne in cui le traccie potrebbe avere diversi formati siccome qui in
  // alcune situazioni si fa subito waveOutWrite c'e' bisogno di controllare
  // se il formato con cui e' stato aperto in precedenza il device e' uguale
  // a quello della traccia
  if (m_devImp->m_wout && m_devImp->m_currentFormat != subTrack->getFormat()) {
    m_devImp->doCloseDevice();
    TSystem::sleep(300);
    m_devImp->doOpenDevice(subTrack->getFormat());
  }

  TThread::MutexLocker sl(&m_mutex);
  if (!m_devImp->m_scrubbing) {
    WAVEHDR *whdr2 = 0;

    // traccia
    whdr2 = prepareWaveHeader(m_devImp->m_wout, subTrack, count);
    getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);

    MMRESULT ret = MMSYSERR_NOERROR;

    if (whdr2 && whdr2->dwFlags & WHDR_PREPARED) {
      ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr2, subTrack);
        getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);
        TThread::MutexLocker sl(&m_devImp->m_mutex);
        m_devImp->m_isPlaying = true;
        m_devImp->m_stopped   = false;
      }
    }
    return;
  }

  if (m_queuedItems.size() == 0) {
    WAVEHDR *whdr1 = 0;
    WAVEHDR *whdr2 = 0;
    WAVEHDR *whdr3 = 0;

    MMRESULT ret;
    TSoundTrackP riseTrack, decayTrack;
    int sampleSize = subTrack->getSampleSize();

    // cresce
    riseTrack = TSop::fadeIn(subTrack, 0.9);
    whdr1     = prepareWaveHeader(m_devImp->m_wout, riseTrack, count);

    // traccia
    whdr2 = prepareWaveHeader(m_devImp->m_wout, subTrack, count);

    getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);

    // decresce
    decayTrack = 0;
    if (m_lastOffset) {
      decayTrack = TSop::fadeOut(subTrack, 0.9);
      whdr3      = prepareWaveHeader(m_devImp->m_wout, decayTrack, count);
    }

    if (whdr1 && (whdr1->dwFlags & WHDR_PREPARED)) {
      ret = waveOutWrite(m_devImp->m_wout, whdr1, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr1, riseTrack);
        getAmplitude(m_lastOffset, riseTrack, riseTrack->getSampleCount() - 1L);
      }
    }
    if (whdr2 && (whdr2->dwFlags & WHDR_PREPARED)) {
      ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr2, subTrack);
        getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);
        TThread::MutexLocker sl(&m_devImp->m_mutex);
        m_devImp->m_isPlaying = true;
        m_devImp->m_stopped   = false;
      }
    }
    if (whdr3 && (whdr3->dwFlags & WHDR_PREPARED)) {
      ret = waveOutWrite(m_devImp->m_wout, whdr3, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr3, decayTrack);
        if (decayTrack->isSampleSigned())
          m_lastOffset = 0;
        else
          m_lastOffset = 127;
      }
    }
    return;
  }

  if (m_queuedItems.size() < 10) {
    WAVEHDR *whdr1 = 0;
    WAVEHDR *whdr2 = 0;
    WAVEHDR *whdr  = new WAVEHDR;
    memset(whdr, 0, sizeof(WAVEHDR));
    whdr->dwBufferLength =
        subTrack->getSampleSize() * subTrack->getSampleCount();
    whdr->lpData  = new char[whdr->dwBufferLength];
    whdr->dwFlags = 0;
    memcpy(whdr->lpData, subTrack->getRawData(), whdr->dwBufferLength);
    int sampleSize         = subTrack->getSampleSize();
    TSoundTrackP riseTrack = 0;

    if (m_lastOffset)  /// devo fare ilcross fade
    {
      int offset;
      getAmplitude(offset, subTrack, 0L);

      offset = m_lastOffset - offset;
      if (offset) {
        TSoundTrackP st = TSop::crossFade(m_lastTrack, subTrack, 0.3);
        memcpy(whdr->lpData, st->getRawData(),
               st->getSampleCount() * sampleSize);
      }
    } else  // e' zero ma ne metto uno che cresce faccio il fadeIn
    {
      riseTrack = TSop::fadeIn(subTrack, 0.3);
      whdr1     = prepareWaveHeader(m_devImp->m_wout, riseTrack, count);
    }

    whdr->dwUser = count;
    ++count;
    MMRESULT ret =
        waveOutPrepareHeader(m_devImp->m_wout, whdr, sizeof(WAVEHDR));
    if (ret != MMSYSERR_NOERROR) {
      delete[] whdr->lpData;
      delete whdr;
      return;
    }

    getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);

    TSoundTrackP decayTrack = 0;
    if (m_queuedItems.size() <= 7) {
      if (m_lastOffset)  // devo fare il fadeOut
      {
        decayTrack = TSop::fadeOut(subTrack, 0.3);
        whdr2      = prepareWaveHeader(m_devImp->m_wout, decayTrack, count);
      }
    }

    if (whdr1 && whdr1->dwFlags & WHDR_PREPARED) {
      ret = waveOutWrite(m_devImp->m_wout, whdr1, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr1, riseTrack);
        getAmplitude(m_lastOffset, riseTrack, riseTrack->getSampleCount() - 1L);
      }
    }

    if (whdr && whdr->dwFlags & WHDR_PREPARED) {
      ret = waveOutWrite(m_devImp->m_wout, whdr, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr, subTrack);
        getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);
        {
          TThread::MutexLocker sl(&m_devImp->m_mutex);
          m_devImp->m_isPlaying = true;
          m_devImp->m_stopped   = false;
        }
      }
    }

    if (whdr2 && whdr2->dwFlags & WHDR_PREPARED) {
      ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr2, decayTrack);
        if (decayTrack->isSampleSigned())
          m_lastOffset = 0;
        else
          m_lastOffset = 127;
      }
    }
    return;
  }

  if ((int)m_items.size() == m_slotCount) {
    WAVEHDR *item = m_items.front().first;
    MMRESULT ret =
        waveOutUnprepareHeader(m_devImp->m_wout, item, sizeof(WAVEHDR));

    if (ret == MMSYSERR_NOERROR) {
      delete[] item->lpData;
      delete item;
    }

    m_items.pop_front();

    if (m_queuedItems.size() != 0) {
      WAVEHDR *item  = m_items.front().first;
      int sampleSize = m_items.front().second->getSampleSize();
      int offset;
      getAmplitude(offset, m_items.front().second, 0L);

      offset = m_lastOffset - offset;
      if (offset) {
        TSoundTrackP st =
            TSop::crossFade(m_lastTrack, m_items.front().second, 0.3);
        memcpy(item->lpData, st->getRawData(),
               st->getSampleCount() * sampleSize);
      }
    }
  }

  WAVEHDR *whdr = prepareWaveHeader(m_devImp->m_wout, subTrack, count);

  assert(whdr && whdr->dwFlags & WHDR_PREPARED);
  m_items.push_back(std::make_pair(whdr, subTrack));
  assert((int)m_items.size() <= m_slotCount);
}

//----------------------------------------------------------------------------

// restituisce il piu' vecchio WAVEHDR il cui stato e' prepared && !done
WAVEHDR *WavehdrQueue::get() {
  TThread::MutexLocker sl(&m_mutex);
  if (m_items.size() == 0) return 0;

  WAVEHDR *whdr = m_items.front().first;
  assert(whdr->dwFlags & WHDR_PREPARED);

  pushBack(whdr, m_items.front().second);
  getAmplitude(m_lastOffset, m_items.front().second,
               m_items.front().second->getSampleCount() - 1L);
  m_items.pop_front();

  return whdr;
}

//-----------------------------------------------------------------------------

// rimuove dalla coda il piu' vecchio WAVEHDR il cui stato e' done
bool WavehdrQueue::popFront(int count) {
  TThread::MutexLocker sl(&m_mutex);
  // assert(m_queuedItems.size() > 0);
  if (m_queuedItems.size() <= 0) return false;
  WAVEHDR *whdr = m_queuedItems.front();
  // controllo introdotto pr via che su win2k si perde alcune
  // notifiche di WHDR_DONE
  while ((DWORD)count > whdr->dwUser) {
    MMRESULT ret =
        waveOutUnprepareHeader(m_devImp->m_wout, whdr, sizeof(WAVEHDR));
    if (ret == MMSYSERR_NOERROR) {
      m_queuedItems.pop_front();
      delete[] whdr->lpData;
      delete whdr;
      whdr = m_queuedItems.front();
    }
  }
  assert(whdr->dwFlags & WHDR_DONE);
  m_queuedItems.pop_front();
  delete[] whdr->lpData;
  delete whdr;
  return true;
}

//-----------------------------------------------------------------------------

void WavehdrQueue::pushBack(WAVEHDR *whdr, TSoundTrackP st) {
  TThread::MutexLocker sl(&m_mutex);
  m_queuedItems.push_back(whdr);
  m_lastTrack = st;
}

//-----------------------------------------------------------------------------

int WavehdrQueue::size() {
  TThread::MutexLocker sl(&m_mutex);
  int size = m_queuedItems.size();
  return size;
}

//-----------------------------------------------------------------------------

void WavehdrQueue::clear() {
  TThread::MutexLocker sl(&m_mutex);
  m_items.clear();
  m_lastTrack = TSoundTrackP();
  std::list<WAVEHDR *>::iterator it;
  for (it = m_queuedItems.begin(); it != m_queuedItems.end(); it++) {
    WAVEHDR *wvhdr = *it;
    delete[] wvhdr->lpData;
    delete wvhdr;
  }
  m_queuedItems.clear();
}

//-----------------------------------------------------------------------------

bool WavehdrQueue::isAllQueuedItemsPlayed() {
  std::list<WAVEHDR *>::iterator it;
  bool finished = true;
  for (it = m_queuedItems.begin(); it != m_queuedItems.end(); it++) {
    WAVEHDR *wvhdr = *it;
    finished       = finished && (wvhdr->dwFlags & WHDR_DONE);
  }
  return finished;
}

//==============================================================================

TSoundOutputDeviceImp::TSoundOutputDeviceImp()
    : m_stopped(true)
    , m_isPlaying(false)
    , m_looped(false)
    , m_scrubbing(false)
    , m_wout(0) {
  m_whdrQueue = new WavehdrQueue(this, 4);

  insertAllRate();
  if (!verifyRate())
    throw TSoundDeviceException(TSoundDeviceException::FailedInit,
                                "Unable to verify supported rates");

  m_closeDevice = CreateEvent(NULL,   // no security attributes
                              FALSE,  // auto-reset event
                              FALSE,  // initial state is not signaled
                              NULL);  // object not named
}

//----------------------------------------------------------------------------

TSoundOutputDeviceImp::~TSoundOutputDeviceImp() { delete m_whdrQueue; }

//----------------------------------------------------------------------------

bool TSoundOutputDeviceImp::doOpenDevice(const TSoundTrackFormat &format) {
  WAVEFORMATEX wf;
  wf.wFormatTag      = WAVE_FORMAT_PCM;
  wf.nChannels       = format.m_channelCount;
  wf.nSamplesPerSec  = format.m_sampleRate;
  wf.wBitsPerSample  = format.m_bitPerSample;
  wf.nBlockAlign     = (wf.nChannels * wf.wBitsPerSample) >> 3;
  wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
  wf.cbSize          = 0;

  TThread::MutexLocker sl(&m_mutex);
  CloseHandle(CreateThread(NULL, 0, MyWaveOutCallbackThread, (LPVOID)this, 0,
                           &m_notifyThreadId));

  MMRESULT ret;
  if ((ret = waveOutOpen(&m_wout, WAVE_MAPPER, &wf, (DWORD)m_notifyThreadId,
                         (DWORD)this, CALLBACK_THREAD)) != MMSYSERR_NOERROR) {
    while (!PostThreadMessage(m_notifyThreadId, WM_QUIT, 0, 0))
      ;
  }
  if (ret != MMSYSERR_NOERROR) return false;

  if (ret != MMSYSERR_NOERROR) return false;
  m_currentFormat = format;
  return (ret == MMSYSERR_NOERROR);
}

//----------------------------------------------------------------------------

bool TSoundOutputDeviceImp::doPlay(WAVEHDR *whdr,
                                   const TSoundTrackFormat format) {
  TThread::MutexLocker sl(&m_mutex);
  if (!m_wout || (m_wout && m_currentFormat != format)) doOpenDevice(format);

  MMRESULT ret;
  ret = waveOutWrite(m_wout, whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) return false;
  m_stopped   = false;
  m_isPlaying = true;

  return true;
}

//----------------------------------------------------------------------------

bool TSoundOutputDeviceImp::doCloseDevice() {
  TThread::MutexLocker sl(&m_mutex);
  if (m_wout) {
    MMRESULT ret = waveOutReset(m_wout);
    if (ret != MMSYSERR_NOERROR) return false;
    ret = waveOutClose(m_wout);
    if (ret != MMSYSERR_NOERROR) return false;
    m_wout      = 0;
    m_stopped   = true;
    m_isPlaying = false;
    m_looped    = false;
  }

  PostThreadMessage(m_notifyThreadId, WM_QUIT, 0, 0);
  return true;
}

//----------------------------------------------------------------------------

void TSoundOutputDeviceImp::insertAllRate() {
  m_supportedRate.insert(8000);
  m_supportedRate.insert(11025);
  m_supportedRate.insert(16000);
  m_supportedRate.insert(22050);
  m_supportedRate.insert(32000);
  m_supportedRate.insert(44100);
  m_supportedRate.insert(48000);
}

//----------------------------------------------------------------------------

bool TSoundOutputDeviceImp::verifyRate() {
  std::set<int>::iterator it;

  for (it = m_supportedRate.begin(); it != m_supportedRate.end();) {
    MMRESULT ret;
    WAVEFORMATEX wf;

    wf.wFormatTag      = WAVE_FORMAT_PCM;
    wf.nChannels       = 1;
    wf.nSamplesPerSec  = *it;
    wf.wBitsPerSample  = 8;
    wf.nBlockAlign     = (wf.nChannels * wf.wBitsPerSample) >> 3;
    wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
    wf.cbSize          = 0;

    ret = waveOutOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY);

    if (ret == MMSYSERR_NOERROR) {
      ++it;
      continue;
    }
    if (ret == WAVERR_BADFORMAT)
      it = m_supportedRate.erase(it);
    else
      return false;
  }
  if (m_supportedRate.end() == m_supportedRate.begin()) return false;

  return true;
}

//==============================================================================

TSoundOutputDevice::TSoundOutputDevice() : m_imp(new TSoundOutputDeviceImp()) {}

//------------------------------------------------------------------------------

TSoundOutputDevice::~TSoundOutputDevice() {
  close();
  WaitForSingleObject(m_imp->m_closeDevice, INFINITE);
  CloseHandle(m_imp->m_closeDevice);
}

//------------------------------------------------------------------------------

namespace {
DWORD WINAPI MyWaveOutCallbackThread(LPVOID lpParameter) {
  TSoundOutputDeviceImp *devImp = (TSoundOutputDeviceImp *)lpParameter;
  if (!devImp) return 0;

  MSG msg;
  BOOL bRet;
  while (bRet = GetMessage(&msg, NULL, 0, 0)) {
    if (bRet == -1) {
      // si e' verificato un errore
      break;
    }

    switch (msg.message) {
    case MM_WOM_DONE: {
      WAVEHDR *pWaveHdr = ((WAVEHDR *)msg.lParam);
      {
        TThread::MutexLocker sl(&devImp->m_mutex);
        if (devImp->m_looped) {
          devImp->doPlay(pWaveHdr, devImp->m_currentFormat);
          continue;
        }
      }

      WAVEHDR *whdr = 0;
      if (devImp->m_whdrQueue->popFront(pWaveHdr->dwUser)) {
        whdr = devImp->m_whdrQueue->get();
        if (whdr) devImp->doPlay(whdr, devImp->m_currentFormat);

        WaitForSingleObject(devImp->m_closeDevice, INFINITE);
        CloseHandle(devImp->m_closeDevice);

        MMRESULT ret =
            waveOutUnprepareHeader(devImp->m_wout, pWaveHdr, sizeof(WAVEHDR));
        if (ret == MMSYSERR_NOERROR) {
          delete pWaveHdr->lpData;
          delete pWaveHdr;
        }
      }

      if (!whdr && devImp->m_whdrQueue->size() == 0) {
        std::set<TSoundOutputDeviceListener *>::iterator it =
            devImp->m_listeners.begin();
        for (; it != devImp->m_listeners.end(); ++it) {
#ifndef TNZCORE_LIGHT
          EndPlayMsg *event = new EndPlayMsg(*it);
          event->send();
#else
          assert(false);
#endif
        }
        devImp->doCloseDevice();
      }

      continue;
    }

    case MM_WOM_CLOSE:
      break;

    default:
      continue;
    }
  }

  SetEvent(devImp->m_closeDevice);
  return 0;
}

void getAmplitude(int &amplitude, const TSoundTrackP st, TINT32 sample) {
  TSoundTrackP snd = st;
  amplitude        = 0;
  int k            = 0;
  for (k = 0; k < snd->getChannelCount(); ++k)
    amplitude += (int)snd->getPressure(sample, k);
  amplitude /= k;
}
}

//------------------------------------------------------------------------------

bool TSoundOutputDevice::installed() {
  int ndev = waveOutGetNumDevs();
  if (ndev <= 0) return false;
  return true;
}

//------------------------------------------------------------------------------

bool TSoundOutputDevice::open(const TSoundTrackP &st) {
  return m_imp->doOpenDevice(st->getFormat());
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::attach(TSoundOutputDeviceListener *listener) {
  m_imp->m_listeners.insert(listener);
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::detach(TSoundOutputDeviceListener *listener) {
  m_imp->m_listeners.erase(listener);
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::play(const TSoundTrackP &st, TINT32 s0, TINT32 s1,
                              bool loop, bool scrubbing) {
  assert((scrubbing && !loop) || !scrubbing);
  if (!st->getSampleCount()) return;
  TSoundTrackFormat format;
  TSoundTrackP subTrack;

  {
    TThread::MutexLocker sl(&m_imp->m_mutex);
    if (m_imp->m_looped)
      throw TSoundDeviceException(
          TSoundDeviceException::Busy,
          "Unable to queue another playback when the sound player is looping");

    format = st->getFormat();
    try {
      TSoundTrackFormat fmt = getPreferredFormat(format);
      if (fmt != format) {
        throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                    "Unsupported Format");
      }
    } catch (TSoundDeviceException &e) {
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  e.getMessage());
    }

    assert(s1 >= s0);
    subTrack           = st->extract(s0, s1);
    m_imp->m_looped    = loop;
    m_imp->m_scrubbing = scrubbing;
  }

  if (!m_imp->m_wout) m_imp->doOpenDevice(format);

  m_imp->m_whdrQueue->put(subTrack);
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::stop() {
  if ((m_imp->m_wout) && m_imp->m_isPlaying) {
    MMRESULT ret = waveOutReset(m_imp->m_wout);
    if (ret != MMSYSERR_NOERROR) return;

    TThread::MutexLocker sl(&m_imp->m_mutex);
    m_imp->m_looped    = false;
    m_imp->m_stopped   = true;
    m_imp->m_isPlaying = false;
    m_imp->m_whdrQueue->clear();
  }
}

//------------------------------------------------------------------------------

bool TSoundOutputDevice::close() { return m_imp->doCloseDevice(); }

//------------------------------------------------------------------------------

bool TSoundOutputDevice::isPlaying() const { return m_imp->m_isPlaying; }

//------------------------------------------------------------------------------

bool TSoundOutputDevice::isAllQueuedItemsPlayed() {
  return m_imp->m_whdrQueue->isAllQueuedItemsPlayed();
}

//------------------------------------------------------------------------------

bool TSoundOutputDevice::isLooping() {
  TThread::MutexLocker sl(&m_imp->m_mutex);
  return m_imp->m_looped;
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::setLooping(bool loop) {
  TThread::MutexLocker sl(&m_imp->m_mutex);
  m_imp->m_looped = loop;
}

//------------------------------------------------------------------------------

TSoundTrackFormat TSoundOutputDevice::getPreferredFormat(TUINT32 sampleRate,
                                                         int channelCount,
                                                         int bitPerSample) {
  TSoundTrackFormat fmt;

  // avvvicinarsi al sample rate => dovrebbe esser OK avendo selezionato i piu'
  // vicini
  std::set<int>::iterator it = m_imp->m_supportedRate.lower_bound(sampleRate);
  if (it == m_imp->m_supportedRate.end()) {
    it = std::max_element(m_imp->m_supportedRate.begin(),
                          m_imp->m_supportedRate.end());
    if (it != m_imp->m_supportedRate.end())
      sampleRate = *(m_imp->m_supportedRate.rbegin());
    else
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  "There isn't a supported format");
  } else
    sampleRate = *it;

  if (bitPerSample <= 8)
    bitPerSample = 8;
  else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16)
    bitPerSample = 16;

  if (bitPerSample >= 16)
    fmt.m_signedSample = true;
  else
    fmt.m_signedSample = false;

  // switch mono/stereo
  if (channelCount <= 1)
    channelCount = 1;
  else
    channelCount = 2;

  fmt.m_bitPerSample = bitPerSample;
  fmt.m_channelCount = channelCount;
  fmt.m_sampleRate   = sampleRate;

  return fmt;
}

//------------------------------------------------------------------------------

TSoundTrackFormat TSoundOutputDevice::getPreferredFormat(
    const TSoundTrackFormat &format) {
  try {
    return getPreferredFormat(format.m_sampleRate, format.m_channelCount,
                              format.m_bitPerSample);
  } catch (TSoundDeviceException &e) {
    throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                e.getMessage());
  }
}

//==============================================================================
//==============================================================================
// Classi per la gestione della registrazione
//==============================================================================
//==============================================================================

//==============================================================================

class WaveFormat final : public WAVEFORMATEX {
public:
  WaveFormat(){};
  WaveFormat(unsigned char channelCount, TUINT32 sampleRate,
             unsigned char bitPerSample);

  ~WaveFormat(){};
};

WaveFormat::WaveFormat(unsigned char channelCount, TUINT32 sampleRate,
                       unsigned char bitPerSample) {
  wFormatTag      = WAVE_FORMAT_PCM;
  nChannels       = channelCount;
  nSamplesPerSec  = sampleRate;
  wBitsPerSample  = bitPerSample;
  nBlockAlign     = (channelCount * bitPerSample) >> 3;
  nAvgBytesPerSec = nBlockAlign * sampleRate;
  cbSize          = 0;
}

//==============================================================================

class WinSoundInputDevice {
public:
  WinSoundInputDevice();
  ~WinSoundInputDevice();

  void open(const WaveFormat &wf);
  void close();

  void prepareHeader(char *sampleBuffer, TUINT32 sampleBufferSize,
                     WAVEHDR &whdr);

  void unprepareHeader(WAVEHDR &whdr);

  void addBlock(WAVEHDR &whdr);

  void start();

  void reset();
  void stop();

  HANDLE m_hBlockDone;

private:
  HWAVEIN m_hWaveIn;
};

//--------------------------------------------------------------------

WinSoundInputDevice::WinSoundInputDevice() : m_hWaveIn(0) {
  m_hBlockDone = CreateEvent(NULL,   // no security attributes
                             FALSE,  // auto-reset event
                             FALSE,  // initial state is not signaled
                             NULL);  // object not named
}

//--------------------------------------------------------------------

WinSoundInputDevice::~WinSoundInputDevice() { CloseHandle(m_hBlockDone); }

//--------------------------------------------------------------------

void WinSoundInputDevice::open(const WaveFormat &wf) {
  if (m_hWaveIn) close();

  MMRESULT ret = waveInOpen(&m_hWaveIn, WAVE_MAPPER, &wf, (DWORD)recordCB,
                            (DWORD)m_hBlockDone, CALLBACK_FUNCTION);

  if (ret != MMSYSERR_NOERROR) {
    throw TException("Error to open the input device");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::close() {
  if (!m_hWaveIn) return;

  MMRESULT ret = waveInClose(m_hWaveIn);

  if (ret != MMSYSERR_NOERROR) {
    throw TException("Error to close the input device");
  }
  m_hWaveIn = 0;
}

//--------------------------------------------------------------------

void WinSoundInputDevice::prepareHeader(char *sampleBuffer,
                                        TUINT32 sampleBufferSize,
                                        WAVEHDR &whdr) {
  whdr.lpData         = sampleBuffer;
  whdr.dwBufferLength = sampleBufferSize;  // numero di byte
  whdr.dwFlags        = 0;
  whdr.dwLoops        = 0;

  MMRESULT ret = waveInPrepareHeader(m_hWaveIn, &whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to prepare a wave header");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::unprepareHeader(WAVEHDR &whdr) {
  MMRESULT ret = waveInUnprepareHeader(m_hWaveIn, &whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to unprepare a wave header");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::addBlock(WAVEHDR &whdr) {
  MMRESULT ret = waveInAddBuffer(m_hWaveIn, &whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to add a waveheader");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::start() {
  int ret = waveInStart(m_hWaveIn);
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to add a waveheader");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::reset() {
  if (!m_hWaveIn) return;

  MMRESULT ret = waveInReset(m_hWaveIn);
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to add a waveheader");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::stop() {
  MMRESULT ret = waveInStop(m_hWaveIn);
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to add a waveheader");
  }
}

//====================================================================

#ifndef TNZCORE_LIGHT
class RecordTask final : public TThread::Runnable {
public:
  RecordTask(std::shared_ptr<TSoundInputDeviceImp> dev)
      : Runnable(), m_dev(std::move(dev)) {}

  ~RecordTask() {}

  void run() override;

  std::shared_ptr<TSoundInputDeviceImp> m_dev;
};

#endif

//====================================================================

class TSoundInputDeviceImp final : public WinSoundInputDevice {
public:
  bool m_allocateBuff;
  bool m_isRecording;
  bool m_supportVolume;
  int m_index;
  TINT32 m_byteRecorded;

  TSoundTrackP m_st;
  TSoundTrackFormat m_format;

#ifndef TNZCORE_LIGHT
  TThread::Executor m_executor;
#endif
  std::vector<WAVEHDR> m_whdr;
  std::vector<char *> m_recordedBlocks;
  std::set<int> m_supportedRate;

  HANDLE m_hLastBlockDone;

  TSoundInputDeviceImp();
  ~TSoundInputDeviceImp();

  void insertAllRate();
  bool verifyRate();
};

//------------------------------------------------------------------------------

TSoundInputDeviceImp::TSoundInputDeviceImp()
    : m_allocateBuff(false)
    , m_isRecording(false)
    , m_supportVolume(false)
    , m_index(0)
    , m_byteRecorded(0)
    , m_format()
    , m_whdr(3)
    , m_recordedBlocks()
    , m_supportedRate() {
  m_hLastBlockDone = CreateEvent(NULL,   // no security attributes
                                 FALSE,  // is manual-reset event?
                                 FALSE,  // is signaled?
                                 NULL);  // object not named
}

//------------------------------------------------------------------------------

TSoundInputDeviceImp::~TSoundInputDeviceImp() {
  if (m_isRecording) {
    try {
      reset();
      WaitForSingleObject(m_hLastBlockDone, INFINITE);
      int i;
      for (i = 0; i < (int)m_recordedBlocks.size(); ++i)
        delete[] m_recordedBlocks[i];
      close();
    } catch (TException &) {
    }
  }
  CloseHandle(m_hLastBlockDone);
}

//----------------------------------------------------------------------------

void TSoundInputDeviceImp::insertAllRate() {
  m_supportedRate.insert(8000);
  m_supportedRate.insert(11025);
  m_supportedRate.insert(16000);
  m_supportedRate.insert(22050);
  m_supportedRate.insert(32000);
  m_supportedRate.insert(44100);
  m_supportedRate.insert(48000);
}

//----------------------------------------------------------------------------

bool TSoundInputDeviceImp::verifyRate() {
  std::set<int>::iterator it;

  for (it = m_supportedRate.begin(); it != m_supportedRate.end();) {
    MMRESULT ret;
    WAVEFORMATEX wf;

    wf.wFormatTag      = WAVE_FORMAT_PCM;
    wf.nChannels       = 1;
    wf.nSamplesPerSec  = *it;
    wf.wBitsPerSample  = 8;
    wf.nBlockAlign     = (wf.nChannels * wf.wBitsPerSample) >> 3;
    wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
    wf.cbSize          = 0;

    ret = waveInOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY);

    if (ret == MMSYSERR_NOERROR) {
      ++it;
      continue;
    }
    if (ret == WAVERR_BADFORMAT)
      it = m_supportedRate.erase(it);
    else
      return false;
  }
  if (m_supportedRate.end() == m_supportedRate.begin()) return false;

  return true;
}

//====================================================================
namespace {
void CALLBACK recordCB(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1,
                       DWORD dwParam2) {
  WAVEHDR *whdr     = (WAVEHDR *)dwParam1;
  HANDLE *blockDone = (HANDLE *)dwInstance;

  if (uMsg != MM_WIM_DATA) return;
  SetEvent(blockDone);
}
}

//==============================================================================

TSoundInputDevice::TSoundInputDevice() : m_imp(new TSoundInputDeviceImp()) {
  m_imp->insertAllRate();
  if (!m_imp->verifyRate())
    throw TSoundDeviceException(TSoundDeviceException::FailedInit,
                                "Unable to verify supported rates");
  if (supportsVolume()) m_imp->m_supportVolume = true;
}

//------------------------------------------------------------------------------

TSoundInputDevice::~TSoundInputDevice() {}

//------------------------------------------------------------------------------

bool TSoundInputDevice::installed() {
  int ndev = waveInGetNumDevs();
  if (ndev <= 0) return false;
  return true;
}

//------------------------------------------------------------------------------

#ifndef TNZCORE_LIGHT

void TSoundInputDevice::record(const TSoundTrackFormat &format,
                               Source devtype) {
  if (m_imp->m_isRecording)
    throw TSoundDeviceException(TSoundDeviceException::Busy,
                                "Just another recoding is in progress");

  /*if ((format.m_bitPerSample == 8 && format.m_signedSample) ||
(format.m_bitPerSample == 24))
throw TException("This format is not supported for recording");*/
  try {
    TSoundTrackFormat fmt = getPreferredFormat(format);
    if (fmt != format) {
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  "Unsupported Format");
    }
  } catch (TSoundDeviceException &e) {
    throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                e.getMessage());
  }

  if (!setRecordLine(devtype))
    throw TSoundDeviceException(TSoundDeviceException::UnableSetDevice,
                                "Problems to set input source line to record");

  m_imp->m_format = format;
  m_imp->m_st     = 0;
  ResetEvent(m_imp->m_hLastBlockDone);
  ResetEvent(m_imp->m_hBlockDone);
  m_imp->m_allocateBuff = true;
  m_imp->m_isRecording  = true;
  m_imp->m_index        = 0;
  m_imp->m_recordedBlocks.clear();
  m_imp->m_byteRecorded = 0;
  TINT32 bytePerSec     = format.m_sampleRate *
                      ((format.m_bitPerSample * format.m_channelCount) >> 3);

  try {
    WaveFormat wf(m_imp->m_format.m_channelCount, m_imp->m_format.m_sampleRate,
                  m_imp->m_format.m_bitPerSample);

    m_imp->open(wf);
  } catch (TException &e) {
    m_imp->m_isRecording = false;
    throw TSoundDeviceException(TSoundDeviceException::UnableOpenDevice,
                                e.getMessage());
  }
  for (; m_imp->m_index < (int)(m_imp->m_whdr.size() - 1); ++m_imp->m_index) {
    try {
      m_imp->prepareHeader(new char[bytePerSec], bytePerSec,
                           m_imp->m_whdr[m_imp->m_index]);
      m_imp->addBlock(m_imp->m_whdr[m_imp->m_index]);
    } catch (TException &e) {
      m_imp->m_isRecording = false;
      for (int j = 0; j < (int)(m_imp->m_whdr.size() - 1); ++j) {
        if (m_imp->m_whdr[j].dwFlags & WHDR_PREPARED) {
          try {
            m_imp->unprepareHeader(m_imp->m_whdr[j]);
          } catch (TException &e) {
            throw TSoundDeviceException(
                TSoundDeviceException::UnableCloseDevice, e.getMessage());
          }
          delete[] m_imp->m_whdr[j].lpData;
        } else if (j == m_imp->m_index)
          delete[] m_imp->m_whdr[j].lpData;
      }
      throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
                                  e.getMessage());
    }
  }

  m_imp->m_executor.addTask(new RecordTask(m_imp));
}

//------------------------------------------------------------------------------

void TSoundInputDevice::record(const TSoundTrackP &st, Source devtype) {
  if (m_imp->m_isRecording)
    throw TSoundDeviceException(TSoundDeviceException::Busy,
                                "Just another recoding is in progress");

  m_imp->m_format = st->getFormat();
  /*if ((m_imp->m_format.m_bitPerSample == 8 && m_imp->m_format.m_signedSample)
||
(m_imp->m_format.m_bitPerSample == 24))
throw TException("This format is not supported for recording");*/
  try {
    TSoundTrackFormat fmt = getPreferredFormat(st->getFormat());
    if (fmt != st->getFormat()) {
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  "Unsupported Format");
    }
  } catch (TSoundDeviceException &e) {
    throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                e.getMessage());
  }

  if (!setRecordLine(devtype))
    throw TSoundDeviceException(TSoundDeviceException::UnableSetDevice,
                                "Problems to set input source line to record");

  m_imp->m_st           = st;
  m_imp->m_allocateBuff = false;
  m_imp->m_isRecording  = true;
  ResetEvent(m_imp->m_hLastBlockDone);
  ResetEvent(m_imp->m_hBlockDone);
  m_imp->m_index = 0;
  m_imp->m_recordedBlocks.clear();
  m_imp->m_byteRecorded = 0;
  try {
    WaveFormat wf(m_imp->m_format.m_channelCount, m_imp->m_format.m_sampleRate,
                  m_imp->m_format.m_bitPerSample);

    m_imp->open(wf);
    m_imp->prepareHeader(
        (char *)st->getRawData(),
        st->getSampleCount() *
            ((st->getBitPerSample() * st->getChannelCount()) >> 3),
        m_imp->m_whdr[m_imp->m_index]);
    m_imp->addBlock(m_imp->m_whdr[m_imp->m_index]);
  } catch (TException &e) {
    m_imp->m_isRecording = false;
    if (m_imp->m_whdr[m_imp->m_index].dwFlags & WHDR_PREPARED)
      m_imp->unprepareHeader(m_imp->m_whdr[m_imp->m_index]);

    throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
                                e.getMessage());
  }

  m_imp->m_executor.addTask(new RecordTask(m_imp));
}
#endif

//------------------------------------------------------------------------------

TSoundTrackP TSoundInputDevice::stop() {
  if (!m_imp->m_isRecording) return 0;

  m_imp->m_isRecording = false;
  try {
    m_imp->reset();
  } catch (TException &e) {
    for (int j = 0; j < (int)m_imp->m_whdr.size(); ++j) {
      if (m_imp->m_whdr[j].dwFlags & WHDR_PREPARED) {
        try {
          m_imp->unprepareHeader(m_imp->m_whdr[j]);
        } catch (TException &e) {
          throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
                                      e.getMessage());
        }
        delete[] m_imp->m_whdr[j].lpData;
      }
    }
    throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice,
                                e.getMessage());
  }

  if (m_imp->m_allocateBuff) {
    WaitForSingleObject(m_imp->m_hLastBlockDone, INFINITE);

    TSoundTrackP st = TSoundTrack::create(
        m_imp->m_format,
        m_imp->m_byteRecorded / ((m_imp->m_format.m_bitPerSample *
                                  m_imp->m_format.m_channelCount) >>
                                 3));

    TINT32 bytePerSec =
        m_imp->m_format.m_sampleRate *
        ((m_imp->m_format.m_bitPerSample * m_imp->m_format.m_channelCount) >>
         3);

    int i;
    for (i = 0; i < (int)(m_imp->m_recordedBlocks.size() - 1); ++i) {
      memcpy((void *)(st->getRawData() + bytePerSec * i),
             m_imp->m_recordedBlocks[i], bytePerSec);
      delete[] m_imp->m_recordedBlocks[i];
    }

    TINT32 lastBlockSize = m_imp->m_byteRecorded - (bytePerSec * i);

    if (lastBlockSize != 0) {
      memcpy((void *)(st->getRawData() + bytePerSec * i),
             m_imp->m_recordedBlocks[i], lastBlockSize);
      delete[] m_imp->m_recordedBlocks[i];
    }

    try {
      m_imp->close();
    } catch (TException &e) {
      throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice,
                                  e.getMessage());
    }
    return st;
  } else {
    WaitForSingleObject(m_imp->m_hLastBlockDone, INFINITE);
    try {
      m_imp->close();
    } catch (TException &e) {
      throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice,
                                  e.getMessage());
    }
    return m_imp->m_st;
  }
}

//------------------------------------------------------------------------------
#ifndef TNZCORE_LIGHT

void RecordTask::run() {
  m_dev->start();

  if (m_dev->m_allocateBuff) {
    TINT32 bytePerSec =
        m_dev->m_format.m_sampleRate *
        ((m_dev->m_format.m_bitPerSample * m_dev->m_format.m_channelCount) >>
         3);

    while (m_dev->m_whdr[(m_dev->m_index + 1) % m_dev->m_whdr.size()].dwFlags &
           WHDR_PREPARED) {
      if (m_dev->m_isRecording)
        WaitForSingleObject(m_dev->m_hBlockDone, INFINITE);
      int indexToPrepare = m_dev->m_index;

      // calcolo l'indice successivo per far l'unprepare
      m_dev->m_index = (m_dev->m_index + 1) % m_dev->m_whdr.size();
      if (m_dev->m_whdr[m_dev->m_index].dwFlags & WHDR_DONE) {
        TINT32 byteRecorded = m_dev->m_whdr[m_dev->m_index].dwBytesRecorded;
        if (byteRecorded) {
          m_dev->m_recordedBlocks.push_back(
              m_dev->m_whdr[m_dev->m_index].lpData);
          m_dev->m_byteRecorded += byteRecorded;
        }

        try {
          m_dev->unprepareHeader(m_dev->m_whdr[m_dev->m_index]);
        } catch (TException &) {
          for (int i = 0; i < (int)m_dev->m_recordedBlocks.size(); ++i)
            delete[] m_dev->m_recordedBlocks[i];
          return;
        }

        if (byteRecorded == 0) {
          delete[] m_dev->m_whdr[m_dev->m_index].lpData;
        }

        // con questo controllo si evita che vengano accodati nuovi blocchi
        // dopo che e' stata chiamata la waveInReset
        if (m_dev->m_isRecording) {
          try {
            m_dev->prepareHeader(new char[bytePerSec], bytePerSec,
                                 m_dev->m_whdr[indexToPrepare]);

            m_dev->addBlock(m_dev->m_whdr[indexToPrepare]);
          } catch (TException &) {
            m_dev->m_isRecording = false;
            for (int i = 0; i < (int)m_dev->m_recordedBlocks.size(); ++i)
              delete[] m_dev->m_recordedBlocks[i];
            return;
          }
        }

      } else
        m_dev->m_index = indexToPrepare;
    }
  } else {
    if (m_dev->m_isRecording)
      WaitForSingleObject(m_dev->m_hBlockDone, INFINITE);
    try {
      m_dev->unprepareHeader(m_dev->m_whdr[m_dev->m_index]);
      m_dev->m_isRecording = false;
    } catch (TException &) {
      m_dev->m_isRecording = false;
      return;
    }
  }

  SetEvent(m_dev->m_hLastBlockDone);
  return;
}

//------------------------------------------------------------------------------
#endif

double TSoundInputDevice::getVolume() {
  DWORD dwVolumeControlID;

  UINT nNumMixers;
  MMRESULT ret;
  MIXERLINE mxl;

  nNumMixers = mixerGetNumDevs();

  if (nNumMixers == 0)
    throw TSoundDeviceException(TSoundDeviceException::NoMixer,
                                "Doesn't exist a audio mixer device");

  // get dwLineID
  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  // get dwControlID
  MIXERCONTROL mxc;
  ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                       MIXERCONTROL_CONTROLTYPE_VOLUME);
  if (ret == MIXERR_INVALCONTROL)
    throw TSoundDeviceException(
        TSoundDeviceException::UnableVolume,
        "Is not possible to obtain info of volume by mixer");

  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  dwVolumeControlID = mxc.dwControlID;

  MIXERCONTROLDETAILS_UNSIGNED mxcdVolume;

  ret = getControlDetails((HMIXEROBJ)0, dwVolumeControlID, mxc.cMultipleItems,
                          &mxcdVolume);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  DWORD dwVal = mxcdVolume.dwValue;

  return (double)dwVal * 10.0 / (double)mxc.Bounds.dwMaximum;
}

//------------------------------------------------------------------------------

bool TSoundInputDevice::setVolume(double value) {
  DWORD dwVolumeControlID, dwMaximum;

  UINT nNumMixers;
  MMRESULT ret;
  MIXERLINE mxl;

  nNumMixers = mixerGetNumDevs();

  if (nNumMixers == 0)
    throw TSoundDeviceException(TSoundDeviceException::NoMixer,
                                "Doesn't exist a audio mixer device");

  // get dwLineID
  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    throw TException("Error to obtain info by mixer");

  // get dwControlID
  MIXERCONTROL mxc;
  ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                       MIXERCONTROL_CONTROLTYPE_VOLUME);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  dwMaximum         = mxc.Bounds.dwMaximum;
  dwVolumeControlID = mxc.dwControlID;

  int newValue;
  double fattProp = ((double)(mxc.Metrics.cSteps - 1) * value) / 10;
  double delta    = (double)(dwMaximum / (mxc.Metrics.cSteps - 1));
  newValue        = (int)(tround(fattProp) * delta);

  MIXERCONTROLDETAILS_UNSIGNED mxcdVolume = {newValue};
  ret = setControlDetails((HMIXEROBJ)0, dwVolumeControlID, mxc.cMultipleItems,
                          &mxcdVolume);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");
  return true;
}

//------------------------------------------------------------------------------

bool TSoundInputDevice::supportsVolume() {
  UINT nNumMixers;
  MMRESULT ret;
  MIXERLINE mxl;

  nNumMixers = mixerGetNumDevs();

  if (nNumMixers == 0)
    throw TSoundDeviceException(TSoundDeviceException::NoMixer,
                                "Doesn't exist a audio mixer device");

  // get dwLineID
  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  // get dwControlID
  MIXERCONTROL mxc;
  ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                       MIXERCONTROL_CONTROLTYPE_VOLUME);
  if (ret == MIXERR_INVALCONTROL) return false;

  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  return true;
}

//------------------------------------------------------------------------------

bool TSoundInputDevice::isRecording() { return m_imp->m_isRecording; }

//------------------------------------------------------------------------------

/*TSoundTrackFormat TSoundInputDevice::getPreferredFormat(
      ULONG sampleRate, int channelCount, int bitPerSample)
{
  MMRESULT ret;
  TSoundTrackFormat fmt;

  ret = isaFormatSupported(sampleRate, channelCount, bitPerSample, true);

  if (ret == MMSYSERR_NOERROR)
  {
    fmt.m_bitPerSample = bitPerSample;
    fmt.m_channelCount = channelCount;
    fmt.m_sampleRate = sampleRate;

    if (bitPerSample >= 16)
      fmt.m_signedSample = true;
    else
      fmt.m_signedSample = false;
    return fmt;
  }
  if (ret == WAVERR_BADFORMAT)
  {
    //avvvicinarsi al sample rate => dovrebbe esser OK avendo selezionato i piu'
vicini
    std::set<int>::iterator it = m_imp->m_supportedRate.lower_bound(sampleRate);
    if (it == m_imp->m_supportedRate.end())
    {
                  it = std::max_element(m_imp->m_supportedRate.begin(),
                                         m_imp->m_supportedRate.end());
                        if (it != m_imp->m_supportedRate.end())
                  sampleRate = *(m_imp->m_supportedRate.rbegin());
      else
        throw TSoundDeviceException(
          TSoundDeviceException::UnsupportedFormat,
          "There isn't a supported format");
    }
    else
      sampleRate = *it;
    ret = isaFormatSupported(sampleRate, channelCount, bitPerSample, true);
    if (ret == MMSYSERR_NOERROR)
    {
      fmt.m_bitPerSample = bitPerSample;
      fmt.m_channelCount = channelCount;
      fmt.m_sampleRate = sampleRate;

      if (bitPerSample >= 16)
        fmt.m_signedSample = true;
      else
        fmt.m_signedSample = false;
      return fmt;
    }
    if (ret == WAVERR_BADFORMAT)
    {
      //cambiare bps
      if (bitPerSample <= 8)
        bitPerSample = 8;
      else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16)
        bitPerSample = 16;

      ret = isaFormatSupported(sampleRate, channelCount, bitPerSample, true);
      if (ret == MMSYSERR_NOERROR)
      {
        fmt.m_bitPerSample = bitPerSample;
        fmt.m_channelCount = channelCount;
        fmt.m_sampleRate = sampleRate;

        if (bitPerSample >= 16)
          fmt.m_signedSample = true;
        else
          fmt.m_signedSample = false;
        return fmt;
      }
      if (ret == WAVERR_BADFORMAT)
      {
        //switch mono/stereo
        if (channelCount <= 1)
          channelCount = 1;
        else
          channelCount = 2;

        ret = isaFormatSupported(sampleRate, channelCount, bitPerSample, true);
        if (ret == MMSYSERR_NOERROR)
        {
          fmt.m_bitPerSample = bitPerSample;
          fmt.m_channelCount = channelCount;
          fmt.m_sampleRate = sampleRate;

          if (bitPerSample >= 16)
            fmt.m_signedSample = true;
          else
            fmt.m_signedSample = false;
          return fmt;
        }
        if (ret == WAVERR_BADFORMAT)
        {
          throw TSoundDeviceException(
            TSoundDeviceException::UnsupportedFormat,
            "Doesn't exist a preferred format");
        }
      }
    }
  }
  throw TSoundDeviceException(
    TSoundDeviceException::UnsupportedFormat,
    "Error to query supported format");
}
*/
TSoundTrackFormat TSoundInputDevice::getPreferredFormat(TUINT32 sampleRate,
                                                        int channelCount,
                                                        int bitPerSample) {
  TSoundTrackFormat fmt;

  // avvvicinarsi al sample rate => dovrebbe esser OK avendo selezionato i piu'
  // vicini
  std::set<int>::iterator it = m_imp->m_supportedRate.lower_bound(sampleRate);
  if (it == m_imp->m_supportedRate.end()) {
    it = std::max_element(m_imp->m_supportedRate.begin(),
                          m_imp->m_supportedRate.end());
    if (it != m_imp->m_supportedRate.end())
      sampleRate = *(m_imp->m_supportedRate.rbegin());
    else
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  "There isn't a supported format");
  } else
    sampleRate = *it;

  if (bitPerSample <= 8)
    bitPerSample = 8;
  else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16)
    bitPerSample = 16;

  if (bitPerSample >= 16)
    fmt.m_signedSample = true;
  else
    fmt.m_signedSample = false;

  // switch mono/stereo
  if (channelCount <= 1)
    channelCount = 1;
  else
    channelCount = 2;

  fmt.m_bitPerSample = bitPerSample;
  fmt.m_channelCount = channelCount;
  fmt.m_sampleRate   = sampleRate;

  return fmt;
}

//------------------------------------------------------------------------------

TSoundTrackFormat TSoundInputDevice::getPreferredFormat(
    const TSoundTrackFormat &format) {
  try {
    return getPreferredFormat(format.m_sampleRate, format.m_channelCount,
                              format.m_bitPerSample);
  } catch (TSoundDeviceException &e) {
    throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                e.getMessage());
  }
}

//==============================================================================
//==============================================================================
// Funzioni per l'interazione con il mixer device
//==============================================================================
//==============================================================================

namespace {

// restituisce dentro la struttura mxc le informazioni relative
// al controllo di tipo dwControlType associato alla linea
// identificata da dwLineID
MMRESULT getLineControl(MIXERCONTROL &mxc, HMIXEROBJ hMixer, DWORD dwLineID,
                        DWORD dwControlType) {
  MIXERLINECONTROLS mxlc;
  mxlc.cbStruct      = sizeof(MIXERLINECONTROLS);
  mxlc.dwLineID      = dwLineID;
  mxlc.dwControlType = dwControlType;
  mxlc.cControls     = 1;
  mxlc.cbmxctrl      = sizeof(MIXERCONTROL);
  mxlc.pamxctrl      = &mxc;
  MMRESULT ret       = mixerGetLineControls(
      (HMIXEROBJ)hMixer, &mxlc,
      MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE);
  return ret;
}

//------------------------------------------------------------------------------

// restituisce nella struttura mxl le informazioni relative alla linea
// sorgente individuata dagli estremi destination e source
MMRESULT getLineInfo(HMIXEROBJ hMixer, MIXERLINE &mxl, DWORD destination,
                     DWORD source) {
  MMRESULT ret;

  mxl.cbStruct      = sizeof(mxl);
  mxl.dwDestination = destination;
  mxl.dwSource      = source;
  ret               = mixerGetLineInfo(0, &mxl,
                         MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_SOURCE);
  return ret;
}

//------------------------------------------------------------------------------

// restituisce nella struttura mxl le informazioni relative alla linea
// individuata da dwLineID
MMRESULT getLineInfo(HMIXEROBJ hMixer, MIXERLINE &mxl, DWORD dwLineID) {
  MMRESULT ret;

  mxl.cbStruct = sizeof(mxl);
  mxl.dwLineID = dwLineID;
  ret          = mixerGetLineInfo((HMIXEROBJ)hMixer, &mxl,
                         MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_LINEID);
  return ret;
}

//------------------------------------------------------------------------------

// restituisce nella struttura mxl le informazioni relative alla linea
// individuata dal tipo specificato in dwComponentType
MMRESULT getLineInfo(HMIXEROBJ hMixer, DWORD dwComponentType, MIXERLINE &mxl) {
  MMRESULT ret;

  mxl.cbStruct        = sizeof(MIXERLINE);
  mxl.dwComponentType = dwComponentType;
  ret =
      mixerGetLineInfo((HMIXEROBJ)hMixer, &mxl,
                       MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE);
  return ret;
}

//------------------------------------------------------------------------------

// consente di settare il valore booleano specificato in mxcdSelectValue
// relativo al controllo specificato in dwSelectControlID
MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_BOOLEAN *mxcdSelectValue) {
  MMRESULT ret;
  MIXERCONTROLDETAILS mxcd;
  mxcd.cbStruct       = sizeof(MIXERCONTROLDETAILS);
  mxcd.dwControlID    = dwSelectControlID;
  mxcd.cChannels      = 1;
  mxcd.cMultipleItems = dwMultipleItems;
  mxcd.cbDetails      = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
  mxcd.paDetails      = mxcdSelectValue;
  ret                 = mixerSetControlDetails(
      (HMIXEROBJ)hMixer, &mxcd,
      MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
  return ret;
}

//------------------------------------------------------------------------------

// consente di settare il valore UNSIGNED specificato in mxcdSelectValue
// relativo al controllo specificato in dwSelectControlID
MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue) {
  MMRESULT ret;
  MIXERCONTROLDETAILS mxcd;
  mxcd.cbStruct       = sizeof(MIXERCONTROLDETAILS);
  mxcd.dwControlID    = dwSelectControlID;
  mxcd.cChannels      = 1;
  mxcd.cMultipleItems = dwMultipleItems;
  mxcd.cbDetails      = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
  mxcd.paDetails      = mxcdSelectValue;
  ret                 = mixerSetControlDetails(
      (HMIXEROBJ)hMixer, &mxcd,
      MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
  return ret;
}

//------------------------------------------------------------------------------

// consente di ottenere il valore UNSIGNED specificato in mxcdSelectValue
// relativo al controllo specificato in dwSelectControlID
MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue) {
  MMRESULT ret;
  MIXERCONTROLDETAILS mxcd;
  mxcd.cbStruct       = sizeof(MIXERCONTROLDETAILS);
  mxcd.dwControlID    = dwSelectControlID;
  mxcd.cChannels      = 1;
  mxcd.cMultipleItems = dwMultipleItems;
  mxcd.cbDetails      = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
  mxcd.paDetails      = mxcdSelectValue;
  ret                 = mixerGetControlDetails(
      (HMIXEROBJ)hMixer, &mxcd,
      MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
  return ret;
}
//------------------------------------------------------------------------------

// consente di ottenere la lista di informazioni in pmxcdSelectText
// relativo al controllo specificato in dwSelectControlID
MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_LISTTEXT *pmxcdSelectText) {
  MMRESULT ret;
  MIXERCONTROLDETAILS mxcd;

  mxcd.cbStruct       = sizeof(MIXERCONTROLDETAILS);
  mxcd.dwControlID    = dwSelectControlID;
  mxcd.cChannels      = 1;
  mxcd.cMultipleItems = dwMultipleItems;
  mxcd.cbDetails      = sizeof(MIXERCONTROLDETAILS_LISTTEXT);
  mxcd.paDetails      = pmxcdSelectText;
  ret                 = mixerGetControlDetails((HMIXEROBJ)0, &mxcd,
                               MIXER_GETCONTROLDETAILSF_LISTTEXT);
  return ret;
}

//------------------------------------------------------------------------------

// restituiscei l nome della linea identificata da lineID
std::string getMixerLineName(DWORD lineID) {
  MIXERLINE mxl;
  MMRESULT ret;

  ret = getLineInfo((HMIXEROBJ)0, mxl, lineID);
#ifdef TNZCORE_LIGHT
  assert(false);
  return "";
#else
  return std::string(mxl.szName);
#endif
}

//------------------------------------------------------------------------------

// restituisce la lista degli identificativi delle linee sorgente associate
// alla destinazione di tipo dstComponentType
std::list<DWORD> getMixerSrcLines(DWORD dstComponentType) {
  std::list<DWORD> srcList;
  MMRESULT ret;
  MIXERLINE mxl;

  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    // forse bisognerebbe lanciare un'eccezione
    return srcList;  // non ha linea di dst per la registrazione

  int v;
  for (v = 0; v < (int)mxl.cConnections; v++) {
    MIXERLINE mxl1;

    ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v);
    if (ret == MMSYSERR_NOERROR) srcList.push_back(mxl1.dwLineID);
  }
  return srcList;
}

//------------------------------------------------------------------------------

// restituisce la lista degli identificativi delle linee sorgente di tipo
// srcComponentType associate alla destinazione di tipo dstComponentType
std::list<DWORD> getMixerSrcLines(DWORD dstComponentType,
                                  DWORD srcComponentType) {
  std::list<DWORD> srcList;
  MMRESULT ret;
  MIXERLINE mxl;

  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    // forse bisognerebbe lanciare un'eccezione
    return srcList;  // non ha linea di dst per la registrazione

  int v;
  for (v = 0; v < (int)mxl.cConnections; v++) {
    MIXERLINE mxl1;

    ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v);
    if (ret == MMSYSERR_NOERROR)
      if (mxl1.dwComponentType == srcComponentType)
        srcList.push_back(mxl1.dwLineID);
  }
  return srcList;
}

//------------------------------------------------------------------------------

// restituisce true sse la linea destinazione di tipo dstComponentType
// supporta una linea sorgente di tipo srcComponentType
bool isSrcLineSupported(DWORD dstComponentType, DWORD srcComponentType) {
  // ci possono essere piu' linee sorgente dello stesso tipo in
  // corrispondenza di una data linea destinazione ?
  MMRESULT ret;
  MIXERLINE mxl;

  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    return false;  // non ha linea di dst per la registrazione

  int v;
  for (v = 0; v < (int)mxl.cConnections; v++) {
    MIXERLINE mxl1;

    ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v);
    if (ret == MMSYSERR_NOERROR)
      if (mxl1.dwComponentType == srcComponentType) return true;
  }

  return false;
}

//------------------------------------------------------------------------------

bool activateSrcLine(const MIXERLINE &mxlDst, DWORD componentTypeSrc) {
  if (!isSrcLineSupported(mxlDst.dwComponentType, componentTypeSrc))
    return false;

  bool bRetVal = true;

  for (DWORD v = 0; v < mxlDst.cConnections; v++) {
    MIXERLINE mxlSrc;
    MMRESULT ret = getLineInfo((HMIXEROBJ)0, mxlSrc, mxlDst.dwDestination, v);

    if (ret == MMSYSERR_NOERROR) {
      // chiedo il controllo di tipo MUTE della linea sorgente
      MIXERCONTROL mxc;
      ret = getLineControl(mxc, (HMIXEROBJ)0, mxlSrc.dwLineID,
                           MIXERCONTROL_CONTROLTYPE_MUTE);

      if (ret == MMSYSERR_NOERROR) {
        MIXERCONTROLDETAILS_BOOLEAN mxcdSelectValue;
        mxcdSelectValue.fValue =
            mxlSrc.dwComponentType == componentTypeSrc ? 0L : 1L;

        ret = setControlDetails((HMIXEROBJ)0, mxc.dwControlID,
                                mxc.cMultipleItems, &mxcdSelectValue);
        if (ret != MMSYSERR_NOERROR) bRetVal = false;
      }
    }
  }
  return bRetVal;
}

//------------------------------------------------------------------------------

bool setSrcMixMuxControl(MIXERCONTROL mxc, DWORD componentTypeSrc) {
  MMRESULT ret;
  DWORD dwIndexLine;
  bool found = false;

  // mantengo nota del ID del controllo dsst individuato e
  // del numero di linee src ad esso associate
  DWORD dwSelectControlID = mxc.dwControlID;
  DWORD dwMultipleItems   = mxc.cMultipleItems;

  if (dwMultipleItems == 0) return false;

  // determino l'indice dell'item corrispondente alla linea sorgente
  // di tipo componentTypeSrc

  std::unique_ptr<MIXERCONTROLDETAILS_LISTTEXT[]> pmxcdSelectText(
      new MIXERCONTROLDETAILS_LISTTEXT[dwMultipleItems]);

  if (pmxcdSelectText) {
    // estraggo le info su tutte le linee associate al controllo
    ret = getControlDetails((HMIXEROBJ)0, dwSelectControlID, dwMultipleItems,
                            pmxcdSelectText.get());

    if (ret == MMSYSERR_NOERROR) {
      for (DWORD dwi = 0; dwi < dwMultipleItems; dwi++) {
        // prendo le info su ogni linea e verifico se e' del giusto tipo
        MIXERLINE mxl;
        ret = getLineInfo((HMIXEROBJ)0, mxl, pmxcdSelectText[dwi].dwParam1);
        if (ret == MMSYSERR_NOERROR &&
            mxl.dwComponentType == componentTypeSrc) {
          dwIndexLine = dwi;
          found       = true;
          break;
        }
      }
    }

    if (!found) return false;
  }

  if (dwIndexLine >= dwMultipleItems) return false;

  bool bRetVal = false;

  std::unique_ptr<MIXERCONTROLDETAILS_BOOLEAN[]> pmxcdSelectValue(
      new MIXERCONTROLDETAILS_BOOLEAN[dwMultipleItems]);

  if (pmxcdSelectValue) {
    ::ZeroMemory(pmxcdSelectValue.get(),
                 dwMultipleItems * sizeof(MIXERCONTROLDETAILS_BOOLEAN));

    // impostazione del valore
    pmxcdSelectValue[dwIndexLine].fValue =
        (TINT32)1;  // lVal; //dovrebbe esser uno

    ret = setControlDetails((HMIXEROBJ)0, dwSelectControlID, dwMultipleItems,
                            pmxcdSelectValue.get());
    if (ret == MMSYSERR_NOERROR) bRetVal = true;
  }
  return bRetVal;
}

//------------------------------------------------------------------------------

bool setRecordLine(TSoundInputDevice::Source typeInput) {
  DWORD dwComponentTypeSrc;

  UINT nNumMixers;
  MMRESULT ret;
  MIXERLINE mxl = {0};

  switch (typeInput) {
  case TSoundInputDevice::LineIn:
    dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_LINE /*|
                             MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY |
                             MIXERLINE_COMPONENTTYPE_SRC_ANALOG*/;
    break;
  case TSoundInputDevice::DigitalIn:
    dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_DIGITAL;
    break;
  case TSoundInputDevice::CdAudio:
    dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
    break;
  default:
    dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
  }

  nNumMixers = mixerGetNumDevs();

  if (nNumMixers == 0) return false;

  // utilizziamo il MIXER di default identificato dall'indice 0
  // vedo se il device ha una linea dst per il wave_input
  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    return false;  // non ha linea di dst per la registrazione

  // vediamo che tipo controllo ha questa linea dst

  // sara' un MIXER?
  MIXERCONTROL mxc = {0};

  ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                       MIXERCONTROL_CONTROLTYPE_MIXER);

  if (ret != MMSYSERR_NOERROR) {
    // no mixer, try MUX
    ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                         MIXERCONTROL_CONTROLTYPE_MUX);

    if (ret != MMSYSERR_NOERROR) {
      // vediamo se e' uno di quei device ne' MIXER ne' MUX
      return activateSrcLine(mxl, dwComponentTypeSrc);
    } else {
      // la linea ha un controllo di tipo MUX
      return setSrcMixMuxControl(mxc, dwComponentTypeSrc);
    }
  } else {
    // la linea ha un controllo di tipo MIXER
    return setSrcMixMuxControl(mxc, dwComponentTypeSrc);
  }
}

//------------------------------------------------------------------------------

MMRESULT isaFormatSupported(int sampleRate, int channelCount, int bitPerSample,
                            bool input) {
  WAVEFORMATEX wf;
  MMRESULT ret;

  wf.wFormatTag      = WAVE_FORMAT_PCM;
  wf.nChannels       = channelCount;
  wf.nSamplesPerSec  = sampleRate;
  wf.wBitsPerSample  = bitPerSample;
  wf.nBlockAlign     = (wf.nChannels * wf.wBitsPerSample) >> 3;
  wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
  wf.cbSize          = 0;

  if (input)
    ret = waveInOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY);
  else
    ret = waveOutOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY);
  return ret;
}
}


text:
<!DOCTYPE html>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">

<title>clientes</title>

<?php
include('menu.html');
include_once ('conecta/conecta.php');
$query="SELECT * FROM Cliente ORDER BY nome";
$result= mysql_query($query, $con);
?>

	Nome
	Telefone
	Email

<?php
while(mysql_fetch_row($result)){
	echo'
';
	echo''.mysql_result($result, 'nome').'	';
	echo''.mysql_result($result, 'telefone').'	';
	echo''.mysql_result($result, 'email').'	';
	echo '<button type="button" class="btn btn-danger" onclick="window.location.href='.'deletarCliente.php?id='.mysql_result($result, 'idCliente')'">Deletar</button>';
}

?>


text:
@implementation FileInfoCenter
- (void)setSendStauts:(FileSendStatus)status
{
    if (_sendStauts!=status) {
        _sendStauts=status;
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFileSendStatuChanged object:self];
    }
}
@end


text:
# 概述

本项目基于Spring技术栈, 提供了一套快速的JAVA后端应用的开发模板, 我们区别与其他的开发模板的特点是:

* 高度模块化
* 可扩展的插件机制
* Spring最佳实践
* 不夹带私货(部分代码闭源并收费)

目前我们覆盖Java后端应用的最基础和最常见的功能:

* 身份管理
* 审计系统
* 安全设施
* 文件管理
* 消息通知
* 基于角色的权限管理

同时我们提供了部分示例,展现如何快速的增加业务模块,特别是最常见的增删改查操作.

我们的定位是提供基于Spring (Boot)框架的快速开发模板和最佳实践, 让用户关注核心业务, 而不是纠结于搭建开发环境.
当然, 使用者需要掌握本框架的设计思路和实现方式. 接下来, 我们会逐一进行解释.

[User Guide - English Version](./README.md)

# 快速上手

> 首先请提前在自己开发机上准备开发环境, 包括 Java 8, Gradle 2.x 和 Mongodb Server等.

## 代码构建

目前只支持gradle

```
gradle clean build
```

## 启动 - docker

* 记得先用gradle进行代码构建
* 确保的你的开发环境已经安装了docker和docker comose

```
cd openapi
docker-compose build
docker-compose up -d
```

## 启动 - 传统方式

下面我们介绍怎么用传统的方式启动服务

### 启动API服务器

API服务器负责将业务服务通过REST API的形式发布出来, 首先我们演示一下如何启动API服务器.

在此我们提供了启动API服务器需要的一个最小配置的`application.properties`(该配置文件是Spring Boot启动时需要的)

```ini
app.name=spring-backend-boilerplate
app.description=spring-backend-boilerplate

in.clouthink.daas.sbb.account.password.salt=@account.sbb.daas.clouthink.in
in.clouthink.daas.sbb.account.administrator.email=changeit@example.com
in.clouthink.daas.sbb.account.administrator.username=administrator
in.clouthink.daas.sbb.account.administrator.cellphone=13000000000
in.clouthink.daas.sbb.account.administrator.password=Please_change_the_pwd

in.clouthink.daas.sbb.setting.system.name=spring-backend-boilerplate
in.clouthink.daas.sbb.setting.system.contactEmail=support-team@example.com
in.clouthink.daas.sbb.setting.system.contactPhone=13000000000

logging.file=/var/sbb/log/server.log
logging.level.*=INFO
logging.level.in.clouthink.daas=DEBUG

server.port=8081
server.address=127.0.0.1
server.session-timeout=360000

spring.mvc.date-format=yyyy-MM-dd
spring.mvc.favicon.enabled=false

multipart.enabled=true
multipart.max-file-size=20Mb
multipart.max-request-size=20Mb

spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true

spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss.sss'Z'

spring.data.mongodb.uri=mongodb://localhost:27017/spring-backend-boilerplate

```

将`application.properties`放在任意可访问的目录,然后按照下面的方式启动,记得将`spring.config.location`的值替换为`application.properties`全路径名.

```sh
> cd openapi/server
> gradle clean bootRun  -PjvmArgs="-Dspring.config.location=the_full_path_of_the_application.properties"
```

### 启动API文档服务器

我们使用swagger2来生成API说明文档,下面是启动API文档服务器的最小配置`application.properties`

```ini
app.name=spring-backend-boilerplate-api-doc
app.description=spring backend boilerplate api doc

server.port=8082
server.address=127.0.0.1
server.session-timeout=360000
```

启动方式和API服务器类似,请按照相同的思路来操作:

```sh
> cd openapi/doc
> gradle clean bootRun  -PjvmArgs="-Dspring.config.location=the_full_path_of_the_application.properties"
```

当API文档服务器启动完成后,打开浏览器,访问以下地址即可:

```
http://127.0.0.1:8082/swagger-ui.html
```

# 功能特征

## 模块化

Spring Boot和Gradle的功能已经非常强,在这个基础上,我们去设计一个完全模块化的系统就变得很容易了.
我们的目标是不需要修改业务代码的情况下, 可以非常轻松的添加或者移除一个模块(当然被整个框架依赖的基础设施模块除外).

要达到这个目标,需要依赖以下技术:

* Spring Boot Starter (这是自动化注入模块和启动模块的解决方案)
* Gradle Build(我们需要根据Gradle的使用规范,设计一个多模块的Gradle工程)

Spring Boot Starter是最关键的, 因为除了Gradle, 我们还可以选择Maven作为构建工具.

* 为每个模块提供一个自动配置的文件 (`@Configuration`)
* 告诉Spring Boot 启动我们定义的自动配置文件 (`META-INF/spring.factories`)

目前我们提供了以下快速启动模块

* :account/starter
* :audit/starter
* :menu/starter
* :rbac/starter
* :sample/news/starter
* :sample/attachment/starter
* :sample/setting/starter
* :message/sms/starter
* :storage/starter

### 代码示例

我们以本项目中的消息模块 (位于`$RROJECT_ROOT/message/sms`目录下) 为例:

Spring的配置文件如下:

```
package in.clouthink.daas.sbb.sms;

@Configuration
@Import({MockSmsModuleConfiguration.class, SmsHistoryModuleConfiguration.class})
public class DummySmsRestModuleConfiguration {

}
```

对应的`starter/src/main/resources/META-INF/spring.factories`文件内容如下:

```ini
#message/sms/starter/src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
in.clouthink.daas.sbb.sms.DummySmsRestModuleConfiguration
```

接下来我们演示一下怎么样实现动态添加和移除该模块:

### 最佳实践

我们前面提到了我们的目标是不修改业务代码的情况下可以做到快速的添加或者移除一个模块, 做法很简单, 在gradle的build文件里面添加或者拿掉业务模块对应的starter就可以了.

1.添加新闻管理(news)模块

> 不需要修改业务代码,添加该模块后,新闻管理模块自动注册到整个应用,API Server启动后, 自动发布和新闻管理有关的所有REST API, 菜单中自动出现新闻管理的菜单项.

```gradle
//openapi/server/build.gradle
dependencies {

    ...
    compile project(':sample/news/starter')
    ...
}

```

2.移除新闻管理(news)模块

> 不需要修改业务代码,移除该模块后,API Server启动后, 新闻管理有关的所有REST API下架, 菜单中不会出现新闻管理的菜单项

```gradle
//openapi/server/build.gradle
dependencies {

    ...
    //after
    //compile project(':sample/news/starter')
    ...
}
```

### 手工注入(不采用spring boot starter)

Spring Boot Starter非常强大, 但是对于那些不熟悉或者不喜欢Spring Boot Starter的使用者, 我们也支持编程式的方式来增加新的业务模块.

首先我们强制分离模块的接口和实现, 这样才可能在不同的实现版本之间轻松切换. 这是模块化的最佳实践.

以本项目提供的文件存储服务(接口定义在`:storage/core`)为例, 我们提供了好几个版本的实现:

* 本地文件系统
* Mongodb的Gridfs系统
* 阿里云存储

下面这段代码表示采用Mongodb提供的Gridfs系统来进行存储

```java
@Import({GridfsModuleConfiguration.class})
public class StorageRestModuleConfiguration {

}

```

如果你只需要把文件存储在本地( 和你的服务器放在一起 ), 只需要注入一个实现了本地文件存储的模块即可, 示例如下:

```java
@Import({LocalStorageModuleConfiguration.class})
public class StorageRestModuleConfiguration {

}
```

## 安全

### 基础

`Spring Security`是一个功能全面而且非常强大的安全框架, 在设计上充分考虑了定制化和扩展性.
基于`Spring Security`提供的弹性设计, 我们在此基础上增加了一些非常有趣的特征:

* 用户管理
* 设备管理
* 多因子认证
* 审计系统
* 可插拔的账户系统

### 安全上下文

`Spring Security`通过 `org.springframework.security.core.context.SecurityContext` 和 `org.springframework.security.core.context.SecurityContextHolder`
为使用者提供安全上下文(也就是获取当前的认证信息).

**为什么我们又重复发明轮子 - 重新设计了一个新的安全上下文?**

首先我们看看一个常见的代码:

```java
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//now check the authentication
if (authentication == null) {
    throw some exception here?
}

```

如果基于`Spring Security`提供的上下文, 我们每次获取了认证信息后, 还需要去检查认证信息 , 基本上是一次编写, 四处拷贝粘贴.

我们希望的是, 如果获取的上下文不满足我们的要求, 自动抛出异常 , 而不是每次都要手工编写代码去判断.

我们对这个需求抽象成代码(该代码在`:security/core`模块中), 如下:

```java
package in.clouthink.daas.sbb.security;

public interface SecurityContext<T> {

	/**
	 * @return current user , or null if not authenticated
	 */
	T currentUser();

	/**
	 * @return the current user
	 * @throw AuthenticationRequiredException if not authenticated
	 */
	T requireUser();

}
```

这个接口定义了两个方法

方法 | 描述
---|---
currentUser() | 保留了对Spring Security Context的支持
requireUser() | 这个方法非常有趣, 表示在应用里面我需要一个用户, 它的返回结果和`currentUser()`完全一样, 但是如果当前上下文没有已认证的用户,就会抛出一个`AuthenticationRequiredException`异常

下面我们给出一段使用示例代码:

```java
User user = (User)SecurityContexts.getContext().requireUser();
```

`SecurityContexts` 是一个抽象接口, 只要遵循Java SPI规范, 就可以很轻松提供用户自己的实现(当然, 我们提供了一套默认实现)

> META-INF/services/in.clouthink.daas.sbb.security.SecurityContext

内容如下:

```
#spring-backend-boilerplate/security/spring/src/main/resources/META-INF/services/in.clouthink.daas.sbb.security.SecurityContext
in.clouthink.daas.sbb.security.impl.spring.SecurityContextImpl
```

### 认证与授权

首先,我们列出我们扩展的`Spring Security`的清单:

**User - 用户**

* org.springframework.security.authentication.AuthenticationProvider
    * org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider
* org.springframework.security.core.userdetails.User
* org.springframework.security.core.userdetails.UserDetailsService

**Login & Logout - 登录和登出**

* org.springframework.security.web.AuthenticationEntryPoint
* org.springframework.security.web.authentication.AuthenticationFailureHandler
* org.springframework.security.web.authentication.AuthenticationSuccessHandler
* org.springframework.security.web.authentication.logout.LogoutSuccessHandler

**Access Control - 访问控制**

* org.springframework.security.web.access.AccessDeniedHandler
    * org.springframework.security.web.access.AccessDeniedHandlerImpl
* org.springframework.security.access.expression.SecurityExpressionHandler
    * org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler
* org.springframework.security.web.access.expression.WebSecurityExpressionRoot

我们对后端的定义是作为REST服务提供者. 因此安全基础设置必须保护所有的REST请求, 而不是传统的控制到页面菜单,按钮一级.

对于前面提到的`Spring Security`扩展, 我们提供的实现如下:

**User - 用户**

* in.clouthink.daas.sbb.security.impl.spring.UserDetailsAuthenticationProviderImpl
* in.clouthink.daas.sbb.security.impl.spring.UserDetails
* in.clouthink.daas.sbb.security.impl.spring.UserDetailsServiceImpl

**Login & Logout - 登录和登出**

* in.clouthink.daas.sbb.security.impl.spring.rest.AuthenticationEntryPointRestImpl
* in.clouthink.daas.sbb.security.impl.spring.rest.AuthenticationFailureHandlerRestImpl
* in.clouthink.daas.sbb.security.impl.spring.rest.AuthenticationSuccessHandlerRestImpl
* in.clouthink.daas.sbb.security.impl.spring.rest.LogoutSuccessHandlerRestImpl

**Access Control - 访问控制**

* in.clouthink.daas.sbb.security.impl.spring.rest.AccessDeniedHandlerRestImpl
* in.clouthink.daas.sbb.rbac.impl.spring.security.RbacWebSecurityExpressionHandler
* in.clouthink.daas.sbb.rbac.impl.spring.security.RbacWebSecurityExpressionRoot

> 即使你不需要这么完整的扩展, 这里面的实现方式应该也具有一定的参考价值.

#### 配置`Spring Security`

详情请参考实现 `in.clouthink.daas.sbb.openapi.OpenApiSecurityConfigurer`.

首先, 我们需要导出我们的`Spring Security`实现( 以Spring Bean的形式 ).

```java

	@Bean
	public AuthenticationProvider authenticationProvider() {
		return new UserDetailsAuthenticationProviderImpl();
	}

	@Bean
	public UserDetailsService userDetailsService() {
		return new UserDetailsServiceImpl();
	}

	@Bean
	public AuthenticationSuccessHandler authenticationSuccessHandlerImpl() {
		return new AuthenticationSuccessHandlerRestImpl();
	}

	@Bean
	public AuthenticationFailureHandler authenticationFailureHandlerImpl() {
		return new AuthenticationFailureHandlerRestImpl();
	}

	@Bean
	public AccessDeniedHandler accessDeniedHandlerImpl() {
		return new AccessDeniedHandlerRestImpl();
	}

	@Bean
	public LogoutSuccessHandler logoutSuccessHandlerImpl() {
		return new LogoutSuccessHandlerRestImpl();
	}

	@Bean
	public AuthenticationEntryPoint authenticationEntryPointImpl() {
		return new AuthenticationEntryPointRestImpl();
	}

	@Bean
	public AccessDecisionManager accessDecisionManager() {
		List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
		decisionVoters.add(new RoleVoter());
		decisionVoters.add(new AuthenticatedVoter());
		decisionVoters.add(webExpressionVoter());
		return new AffirmativeBased(decisionVoters);
	}

	@Bean
	public WebExpressionVoter webExpressionVoter() {
		WebExpressionVoter result = new WebExpressionVoter();
		result.setExpressionHandler(rbacWebSecurityExpressionHandler());
		return result;
	}

	@Bean
	public SecurityExpressionHandler rbacWebSecurityExpressionHandler() {
		return new RbacWebSecurityExpressionHandler();
	}

	@Override
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.authenticationProvider(authenticationProvider())
			.eraseCredentials(true)
			.userDetailsService(userDetailsService());
	}

```

接下来在`Spring Security`中集成这些扩展实现.

认证示例:

```java
	private void configLogin(HttpSecurity http) throws Exception {
		http.csrf()
			.disable()
			.formLogin()
			.loginPage("/login")
			.permitAll()
			.successHandler(authenticationSuccessHandlerImpl())
			.failureHandler(authenticationFailureHandlerImpl())
			.loginProcessingUrl("/login")
			.usernameParameter("username")
			.passwordParameter("password")
			.and()
			.logout()
			.logoutUrl("/logout")
			.logoutSuccessHandler(logoutSuccessHandlerImpl())
			.invalidateHttpSession(true)
			.deleteCookies("JSESSIONID")
			.permitAll()
			.and()
			.rememberMe()
			.key("PLEASE_CHANGE_THIS");
	}

```

授权示例:

```java
	private void configAccess(HttpSecurity http) throws Exception {
		http.headers().frameOptions().disable();

		http.authorizeRequests()
			.accessDecisionManager(accessDecisionManager())
			.antMatchers("/", "/static/**", "/login**", "/guest/**")
			.permitAll()
			.antMatchers("/api/shared/**")
			.hasRole("USER")
			.antMatchers("/api/_devops_/**")
			.hasRole("ADMIN")
			.antMatchers("/api/**")
			.access("passRbacCheck")
			.and()
			.exceptionHandling()
			.authenticationEntryPoint(authenticationEntryPointImpl())
			.accessDeniedHandler(accessDeniedHandlerImpl());
	}

```

## 审计

[daas-audit](https://github.com/melthaw/spring-mvc-audit) 是一个简单易用的审计库, 专门对Spring Mvc的HTTP请求进行审计.
我们在本项目中集成了该审计库, 关于如何集成, 请查看 https://github.com/melthaw/spring-mvc-audit , 里面有详细的使用说明.

在本项目中, `:audit/impl`模块完整实现了[daas-audit](https://github.com/melthaw/spring-mvc-audit) 的`AuditEvent` 审计API:

* in.clouthink.daas.audit.core.MutableAuditEvent
    * in.clouthink.daas.sbb.audit.domain.model.AuditEvent
* in.clouthink.daas.audit.spi.AuditEventPersister
    * in.clouthink.daas.sbb.audit.spiImpl.AuditEventPersisterImpl

[daas-audit](https://github.com/melthaw/spring-mvc-audit) 并不支持对用户登录和登出历史的审计, 我们也进行了扩展.

* in.clouthink.daas.sbb.audit.domain.model.AuthEvent
* in.clouthink.daas.sbb.audit.service.AuthEventService

下面的代码示例演示了如何启用审计功能 (使用`@EnableAudit`注解):

```java
@EnableAudit
public class SpringBootApplication extends SpringBootServletInitializer {

	@Bean
	public AuditEventPersister auditEventPersisterImpl() {
		return new AuditEventPersisterImpl();
	}

	@Bean
	public AuditConfigurer auditConfigurer() {
		return result -> {
			result.setSecurityContext(new SecurityContextAuditImpl());
			result.setAuditEventPersister(auditEventPersisterImpl());
			result.setErrorDetailRequired(true);
		};
	}

    public static void main(String[] args) {
        ...
    }

}
```

## 文件存储

[daas-fss](https://github.com/melthaw/spring-file-storage-service) 是一个简单易用的文件存储库 ,
我们在本项目中集成了文件存储功能, 关于如何集成, 请查看 https://github.com/melthaw/spring-file-storage-service , 里面有详细的使用说明.

接下来我们解释一下我们基于[daas-fss](https://github.com/melthaw/spring-file-storage-service)所做的扩展和定制.

目前我们提供了三种文件存储实现:

* aliyun oss (:storage/alioss)
* mongodb gridfs (:storage/gridfs)
* local file system (:storage/localfs)

> 不同的文件存储服务依赖于不同的底层服务, 因此最终导致的下载链接也不一样, 这个可能对你的业务系统造成一定影响, 例如你刚开始选择了一种实现 , 后来又切换到另外一种实现 , 就需要进行数据迁移.

下面是我们提供的下载链接的抽象接口

* in.clouthink.daas.sbb.storage.spi.DownloadUrlProvider

例如: 如果你选择使用`:storage/localfs` 本地文件系统来存储文件, 对应的下载地址如下:

```java
public class LocalfsDownloadUrlProvider implements DownloadUrlProvider {

	@Autowired
	private LocalfsConfigureProperties localfsConfigureProperties;

	@Autowired
	private FileObjectService fileObjectService;

	@Override
	public String getDownloadUrl(String id) {
		FileObject fileObject = fileObjectService.findById(id);
		if (fileObject == null) {
			throw new FileNotFoundException(id);
		}

		return localfsConfigureProperties.getDowloadUrlPrefix() + fileObject.getFinalFilename();
	}

}

```

下面是如何启用文件存储模块(在前面的模块化章节有更详细的描述)

```java
@Import(StorageModuleConfiguration.class)
public class SpringBootApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        ...
    }
}
```

## 基于事件的消息

[daas-edm](https://github.com/melthaw/spring-event-driven-message) 是一个基于spring reactor实现的事件驱动消息的框架 ,
我们在本项目中集成了该框架, 关于如何集成, 请查看 https://github.com/melthaw/spring-event-driven-message , 里面有详细的使用说明.

### 短信通知

> 在本模板中我们用[阿里云短消息](https://www.aliyun.com/product/sms) 作为示例.

我们对短信通知进行抽象, 只需要实现下面这个接口就可以集成你的短信通知服务.

```
in.clouthink.daas.edm.sms.SmsSender
```

我们也提供了一个模拟实现(`:message/sms/mock`) , 该实现可以用于开发环境, 接受所有的短信发送请求, 只是简单的在console打印出已发送, 这样可以避免在开发过程中消耗你的实际流量.

```java
//for development
@Import({SmsAliyunModuleConfiguration.class})
//for production
@Import({DummySmsModuleConfiguration.class})
```

如果你导入短消息发送历史模块(`:message/sms/history`), 所有的短消息发送记录将会记录在案.
默认情况下, 我们启用了短消息历史纪录功能, 如果你不需要, 注释掉或者删除对应的`Import`代码即可.

```java
@Configuration
@Import({SmsHistoryModuleConfiguration.class})
public class SmsRestModuleConfiguration {

}

```

下面是手工启用短消息服务模块, 我们推荐的方式是使用`Spring Boot Starter`, 请参考模块化章节.

```java
@Import(SmsRestModuleConfiguration.class)
public class SpringBootApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        ...
    }
}
```

# 附录 - 配置示例

## application.properties

### account

```ini
in.clouthink.daas.sbb.account.password.salt=
in.clouthink.daas.sbb.account.administrator.email=
in.clouthink.daas.sbb.account.administrator.username=
in.clouthink.daas.sbb.account.administrator.cellphone=
in.clouthink.daas.sbb.account.administrator.password=
```

### storage

```ini
#alioss
in.clouthink.daas.sbb.storage.alioss.keyId=
in.clouthink.daas.sbb.storage.alioss.secret=
in.clouthink.daas.sbb.storage.alioss.ossDomain=
in.clouthink.daas.sbb.storage.alioss.imgDomain=
in.clouthink.daas.sbb.storage.alioss.defaultBucket=
in.clouthink.daas.sbb.storage.alioss.buckets.key1=
in.clouthink.daas.sbb.storage.alioss.buckets.key2=
```

### sms

```ini
in.clouthink.daas.sbb.sms.aliyun.area=
in.clouthink.daas.sbb.sms.aliyun.accessKey=
in.clouthink.daas.sbb.sms.aliyun.accessSecret=
in.clouthink.daas.sbb.sms.aliyun.signature=
in.clouthink.daas.sbb.sms.aliyun.smsEndpoint=
in.clouthink.daas.sbb.sms.aliyun.templateId=
```

### setting

```ini
in.clouthink.daas.sbb.setting.system.name=
in.clouthink.daas.sbb.setting.system.contactEmail=
in.clouthink.daas.sbb.setting.system.contactPhone=
```

## resource - 资源

在RBAC(基于角色的访问控制)中, 我们把要保护的对象叫做resource(资源), resource可能(但是不限于)包括以下类型:

* REST服务
* 菜单
* 按钮
* 某个页面

为此我们设计了一个资源服务提供者的API :

* in.clouthink.daas.sbb.rbac.spi.ResourceProvider

所有的ResourceProvider(资源服务提供者)最终需要以`Spring Bean`的形式暴露给框架(和我们常用的Component, Service, Controller类似).
当应用启动的时候会自动扫描并注册这些resource bean.

最常见的资源莫过于菜单, 在大量的应用中几乎都会包括一个菜单授权功能, 一般是将菜单授权给某个角色或者某个用户组.
我们设计了一个插拔式的菜单模块, 并提供了一整套注解(annotation) , 有了这套注解, 定义菜单和扩展菜单就很轻松了.

```java
@EnableMenu(pluginId = "plugin:menu:sample",
			extensionPointId = Menus.ROOT_EXTENSION_POINT_ID,
			menu = {@Menu(virtual = true,
						  code = "menu:dashboard:sample",
						  name = "sample",
						  order = 100,
						  metadata = {@Metadata(key = "icon", value = "fa fa-gear")},
						  extensionPoint = {@ExtensionPoint(id = "extension:menu:sample")}),

					@Menu(virtual = true,
						  code = "menu:dashboard:system",
						  name = "system",
						  order = 200,
						  metadata = {@Metadata(key = "icon", value = "fa fa-gear")},
						  extensionPoint = {@ExtensionPoint(id = "extension:menu:system")}),

			})
```

# 示例 - 快速开发业务模块

`TODO`

# 附录 - 开发环境

## IDEA - 如何导入

```sh
> gradle cleanIdea
> gradle idea
```

## IDEA - 怎么调试

新增类型为Gradle的调试配置, 对应的值如下:

配置项 | 值
-----|-----
Gradle Project | spring-backend-boilerplate:openapi:server
Tasks | clean bootRun
VM Options | 保留为空
Script parameters | -PjvmArgs="-Dspring.config.location=/var/sbb/etc/openapi/application.properties"


text:
This readme contains instructions to build a basic Ubuntu Desktop Virtual Machine for TouchDevelop tool.

# Instructions to build VM
1. Make sure that you have vagrant installed on your machine. You can get it [here.](https://www.vagrantup.com/downloads.html)

2. Create a directory where VM should be built. Open a command prompt and navigate to it. Let's call it VM directory.

3. Execute command 'vagrant init'. It should prompt that Vagrantfile has been created in this VM directory.

4. Replace Vagrantfile in VM directory by Vagrantfile in this git directory.

5. Copy bootstrap.sh file from this repository to the same VM directory.

6. Execute 'vagrant up' from command prompt in VM directory. This should build the VM.

7. If prompted for Login, Use Username/Password: vagrant/vagrant

8. Done

#References
1. http://askubuntu.com/questions/159008/how-to-add-startup-applications-in-lubuntu
2. https://help.ubuntu.com/community/AutoLogin
3. http://aruizca.com/steps-to-create-a-vagrant-base-box-with-ubuntu-14-04-desktop-gui-and-virtualbox/
4. http://askubuntu.com/questions/230358/problems-creating-a-desktop-entry-for-a-shell-script
5. https://github.com/Microsoft/TouchDevelop/blob/master/README.md#setup
6. https://github.com/SoftwareEngineeringToolDemos/ICSE-2012-CodeBubbles/edit/master/build-vm/
7. https://github.com/boxcutter/ubuntu


text:
<?php
return array (
  'Cancel' => 'Zrušit',
  'Please shortly introduce yourself, to become a approved member of this workspace.' => 'Stručně se prosím představte, aby bylo možné schválit vaše členství v tomto prostoru.',
  'Request workspace membership' => 'Žádost o členství v prostoru',
  'Send' => 'Odeslat',
);