<commit_before>//===========================================
//  PC-BSD source code
//  Copyright (c) 2016, PC-BSD Software/iXsystems
//  Available under the 3-clause BSD license
//  See the LICENSE file for full details
//===========================================
#include "sysadm-client.h"
#include <QSslConfiguration>
#include <QJsonArray>
#include <QProcess>
#include <QFile>
#include <QTimer>
#include <QSettings>
#include <QSslKey>
#include <QSslCertificate>

//SSL Stuff
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/err.h>

#define SERVERPIDFILE QString("/var/run/sysadm.pid")

extern QSettings *settings;
//Unencrypted SSL objects (after loading them by user passphrase)
extern QSslConfiguration SSL_cfg; //Check "isNull()" to see if the user settings have been loaded yet

// === PUBLIC ===
sysadm_client::sysadm_client(){
  SOCKET = new QWebSocket("sysadm-client", QWebSocketProtocol::VersionLatest, this);
    //SOCKET->setSslConfiguration(QSslConfiguration::defaultConfiguration());
    //SOCKET->ignoreSslErrors();
    //use the new Qt5 connection syntax for compile time checks that connections are valid
    connect(SOCKET, &QWebSocket::connected, this, &sysadm_client::socketConnected);
    connect(SOCKET, &QWebSocket::disconnected, this, &sysadm_client::socketClosed);
    connect(SOCKET, &QWebSocket::textMessageReceived, this, &sysadm_client::socketMessage);
    //Old connect syntax for the "error" signal (special note about issues in the docs)
    connect(SOCKET, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)) );
    connect(SOCKET, SIGNAL(sslErrors(const QList<QSslError>&)), this, SLOT(socketSslErrors(const QList<QSslError>&)) );
  keepActive=false; //not setup yet
}

sysadm_client::~sysadm_client(){

}

// Overall Connection functions (start/stop)
void sysadm_client::openConnection(QString user, QString pass, QString hostIP){
  cuser = user; cpass = pass; chost = hostIP;
  //qDebug() << "Client: Setup connection:" << user << pass << hostIP;
  setupSocket();
}

void sysadm_client::openConnection(QString authkey, QString hostIP){
  cauthkey = authkey; chost = hostIP;
  setupSocket();
}

void sysadm_client::openConnection(QString hostIP){
  chost = hostIP;
  setupSocket();
}

void sysadm_client::closeConnection(){
  keepActive = false;
  //de-authorize the current auth token
  if(!cauthkey.isEmpty()){
    cauthkey.clear();
    clearAuth();
  }
  //Now close the connection
  SOCKET->close(QWebSocketProtocol::CloseCodeNormal, "sysadm-client closed");
}

QString sysadm_client::currentHost(){
  return chost;
}

bool sysadm_client::isActive(){
  return ((SOCKET!=0) && SOCKET->isValid());
}

//Check if the sysadm server is running on the local system
bool sysadm_client::localhostAvailable(){
  #ifdef __FreeBSD__
  qDebug() << "Checking for Local Host:" << SERVERPIDFILE;
  //Check if the local socket can connect
  if(QFile::exists(SERVERPIDFILE)){
    //int ret = QProcess::execute("pgrep -f \""+SERVERPIDFILE+"\"");
    //return (ret==0 || ret>3);
    return true;
  }
  #endif
  return false;
}

// Register for Event Notifications (no notifications by default)
void sysadm_client::registerForEvents(EVENT_TYPE event, bool receive){
  bool set = events.contains(event);
  if( set && receive){ return; } //already registered
  else if(!set && !receive){ return; } //already unregistered
  else if(set){ events << event; }
  else{ events.removeAll(event); }
  //Since this can be setup before the socket is connected - see if we can send this message right away
  if(SOCKET->isValid()){
    sendEventSubscription(event, receive);
  }
}

//Register the custom SSL Certificate with the server
void sysadm_client::registerCustomCert(){
  if(SSL_cfg.isNull() || SOCKET==0 || !SOCKET->isValid()){ return; }
  //Get the custom cert
  QList<QSslCertificate> certs = SSL_cfg.localCertificateChain();
  QString pubkey, email, nickname;
  for(int i=0; i<certs.length(); i++){
    if(certs[i].issuerInfo(QSslCertificate::Organization).contains("SysAdm-client")){
      pubkey = QString(certs[i].publicKey().toPem().toBase64());
      email = certs[i].issuerInfo(QSslCertificate::EmailAddress).join("");
      nickname = certs[i].issuerInfo(QSslCertificate::Organization).join("");
      break;
    }
  }
  if(pubkey.isEmpty()){ return; } //no cert found
  //Now assemble the request JSON
  QJsonObject obj;
    obj.insert("action","register_ssl_cert");
    obj.insert("pub_key", pubkey);
    obj.insert("email",email);
    obj.insert("nickname",nickname);
  this->communicate("sysadm-auto-cert-register","sysadm","settings", obj);

}

// Messages which are still pending a response
QStringList sysadm_client::pending(){ return PENDING; } //returns only the "id" for each

// Fetch a message from the recent cache
QJsonObject sysadm_client::cachedRequest(QString id){
  if(SENT.contains(id)){ return SENT.value(id); }
  else{ return QJsonObject(); }
}

QJsonValue sysadm_client::cachedReply(QString id){
  if(BACK.contains(id)){ return BACK.value(id); }
  else{ return QJsonObject(); }
}

// === PRIVATE ===
//Functions to do the initial socket setup
void sysadm_client::setupSocket(){
  //qDebug() << "Setup Socket:" << SOCKET->isValid();
  if(SOCKET->isValid()){ return; }
  //Setup the SSL config as needed
  SOCKET->setSslConfiguration(QSslConfiguration::defaultConfiguration());
  //uses chost for setup
  // - assemble the host URL
  if(chost.contains("://")){ chost = chost.section("://",1,1); } //Chop off the custom http/ftp/other header (always need "wss://")
  QString url = "wss://"+chost;
  bool hasport = false;
  url.section(":",-1).toInt(&hasport); //check if the last piece of the url is a valid number
  //Could add a check for a valid port number as well - but that is a bit overkill right now
  if(!hasport){ url.append(":"+QString::number(WSPORTDEFAULT)); }
  qDebug() << " Open WebSocket:  URL:" << url;
  QTimer::singleShot(0,SOCKET, SLOT(ignoreSslErrors()) );
  SOCKET->open(QUrl(url));
    //QList<QSslError> ignored; ignored << QSslError(QSslError::SelfSignedCertificate) << QSslError(QSslError::HostNameMismatch);
    //SOCKET->ignoreSslErrors(ignored);
}

void sysadm_client::performAuth(QString user, QString pass){
  //uses cauthkey if empty inputs
  QJsonObject obj;
  obj.insert("namespace","rpc");
  obj.insert("id","sysadm-client-auth-auto");
  bool noauth = false;
  if(user.isEmpty()){
    if(cauthkey.isEmpty()){
      //SSL Authentication (Stage 1)
      obj.insert("name","auth_ssl");
      obj.insert("args","");
    }else{
      //Saved token authentication
      obj.insert("name","auth_token");
      QJsonObject arg;
	arg.insert("token",cauthkey);
      obj.insert("args", arg);
    }
  }else{
    //User/password authentication
    obj.insert("name","auth");
    QJsonObject arg;
      arg.insert("username",user);
      arg.insert("password",pass);
    obj.insert("args", arg);
  }
  sendSocketMessage(obj);
  if(noauth){ emit clientUnauthorized(); }
}

void sysadm_client::clearAuth(){
  QJsonObject obj;
  obj.insert("namespace","rpc");
  obj.insert("id","sysadm-client-auth-auto");
  obj.insert("name","auth_clear");
  obj.insert("args","");
  sendSocketMessage(obj);
  emit clientUnauthorized();
}

//Communication subroutines with the server (block until message comes back)
void sysadm_client::sendEventSubscription(EVENT_TYPE event, bool subscribe){
  QJsonObject obj;
  obj.insert("namespace","events");
  obj.insert("name", subscribe ? "subscribe" : "unsubscribe");
  obj.insert("id", "sysadm-client-event-auto");
  QString arg;
  if(event == DISPATCHER){ arg = "dispatcher"; }
  else if(event == LIFEPRESERVER){ arg = "life-preserver"; }
  obj.insert("args", arg);
  sendSocketMessage(obj);
}

void sysadm_client::sendSocketMessage(QJsonObject msg){
  QJsonDocument doc(msg);
  //qDebug() << "Send Socket Message:" << doc.toJson(QJsonDocument::Compact);
  SOCKET->sendTextMessage(doc.toJson(QJsonDocument::Compact));
}

//Simplification functions
QJsonObject sysadm_client::convertServerReply(QString reply){
  QJsonDocument doc = QJsonDocument::fromJson(reply.toUtf8());
  if(doc.isObject()){ return doc.object(); }
  else{ return QJsonObject(); }
}

QString sysadm_client::SSL_Encode_String(QString str){
  //Get the private key
  QByteArray privkey = SSL_cfg.privateKey().toPem();

  //Now use this private key to encode the given string
  unsigned char encode[4098] = {};
  RSA *rsa= NULL;
  BIO *keybio = NULL;
  keybio = BIO_new_mem_buf(privkey.data(), -1);
  if(keybio==NULL){ return ""; }
  rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa,NULL, NULL);
  bool ok = (-1 != RSA_private_encrypt(str.length(), (unsigned char*)(str.toLatin1().data()), encode, rsa, RSA_PKCS1_PADDING) );
  if(!ok){ return ""; }
  else{
    //Now return this as a base64 encoded string
    return QString( QByteArray( (char*)(encode) ).toBase64() );
  }

}
// === PUBLIC SLOTS ===
// Communications with server (send message, get response via signal later)
void sysadm_client::communicate(QString ID, QString namesp, QString name, QJsonValue args){
  //Overloaded function for a request which needs assembly
  QJsonObject obj;
  obj.insert("namespace",namesp);
  obj.insert("name", name);
  obj.insert("id", ID);
  obj.insert("args", args);
  //qDebug() << "Send Message:" << QJsonDocument(obj).toJson();
  communicate(QList<QJsonObject>() << obj);
}

void sysadm_client::communicate(QJsonObject request){
  //Overloaded function for a single JSON request
  communicate(QList<QJsonObject>() << request);
}

void sysadm_client::communicate(QList<QJsonObject> requests){
  for(int i=0; i<requests.length(); i++){
    QString ID = requests[i].value("id").toString();
    if(ID.isEmpty()){
      qDebug() << "Malformed JSON request:" << requests[i];
      continue;
    }
    //Save this into the cache
    SENT.insert(ID, requests[i]);
    if(BACK.contains(ID)){ BACK.remove(ID); }
    PENDING << ID;
    //Now send off the message
    if(SOCKET->isValid()){ sendSocketMessage(requests[i]); }
  }
}

// === PRIVATE SLOTS ===
//Socket signal/slot connections
void sysadm_client::socketConnected(){ //Signal: connected()
  keepActive = true; //got a valid connection - try to keep this open automatically
  num_fail = 0; //reset fail counter - got a valid connection
  emit clientConnected();
  performAuth(cuser, cpass);
  cuser.clear(); cpass.clear(); //just to ensure no trace left in memory
}

void sysadm_client::socketClosed(){ //Signal: disconnected()
  /*if(keepActive && num_fail < FAIL_MAX){
    //Socket closed due to timeout?
    // Go ahead and re-open it if possible with the last-used settings/auth
    num_fail++;
    setupSocket();
  }else{*/
    num_fail = 0; //reset back to nothing
    emit clientDisconnected();
    //Server cache is now invalid - completely lost connection
    SENT.clear(); BACK.clear(); PENDING.clear();
  //}
}

void sysadm_client::socketSslErrors(const QList<QSslError>&errlist){ //Signal: sslErrors()
  qWarning() << "SSL Errors Detected:" << errlist.length();
  QList<QSslError> ignored;
  for(int i=0; i< errlist.length(); i++){
    if(errlist[i].error()==QSslError::SelfSignedCertificate || errlist[i].error()==QSslError::HostNameMismatch ){
      qDebug() << " - (IGNORED) " << errlist[i].errorString();
      ignored << errlist[i];
    }else{
      qWarning() << " - " << errlist[i].errorString();
    }
  }
  if(ignored.length() != errlist.length()){
    SOCKET->close(); //SSL errors - close the connection
  }
}

void sysadm_client::socketError(QAbstractSocket::SocketError err){ //Signal:: error()
  qWarning() << "Socket Error detected:" << err;
  if(err==QAbstractSocket::SslHandshakeFailedError){qWarning() << " - SSL Handshake Failed"; }
  qWarning() << " - Final websocket error:" << SOCKET->errorString();
}
//void sysadm_client::socketProxyAuthRequired(const QNetworkProxy &proxy, QAuthenticator *auth); //Signal: proxyAuthenticationRequired()

// - Main message input parsing
void sysadm_client::socketMessage(QString msg){ //Signal: textMessageReceived()
  //qDebug() << "New Reply From Server:" << msg;
  //Convert this into a JSON object
  QJsonObject obj = convertServerReply(msg);
  QString ID = obj.value("id").toString();
  QString namesp = obj.value("namespace").toString();
  QString name = obj.value("name").toString();
  //Still need to add some parsing to the return message
  if(ID=="sysadm-client-auth-auto"){
    //Reply to automated auth system
    if(name=="error"){
      closeConnection();
      emit clientUnauthorized();
    }else{
      QJsonValue args = obj.value("args");
      if(args.isArray()){
	      cauthkey = args.toArray().first().toString();
	      emit clientAuthorized();
	      //Now automatically re-subscribe to events as needed
	      for(int i=0; i<events.length(); i++){ sendEventSubscription(events[i]); }
      }else if(args.isObject()){
        //SSL Auth Stage 2
        QString randomkey = args.toObject().value("test_string").toString();
        if(!randomkey.isEmpty()){
          QJsonObject obj;
          obj.insert("name","auth_ssl");
          obj.insert("namespace","rpc");
          obj.insert("id",ID); //re-use this special ID
          QJsonObject args;
          args.insert("encrypted_string", SSL_Encode_String(randomkey));
          obj.insert("args",args);
          this->communicate(obj);
        }
      }
    }
  }else if(ID=="sysadm-client-event-auto"){
    //Reply to automated event subscription - don't need to save this
  }else if(namesp=="events"){
    //Event notification - not tied to any particular request
    if(name=="dispatcher"){ emit NewEvent(DISPATCHER, obj.value("args")); }
    else if(name=="life-preserver"){ emit NewEvent(LIFEPRESERVER, obj.value("args")); }
  }else{
    //Now save this message into the cache for use later (if not an auth reply)
    if(!ID.isEmpty()){
      PENDING.removeAll(ID);
      BACK.insert(ID, obj);
    }
    emit newReply(ID, name, namesp, obj.value("args"));
  }
}
	<commit_msg>Add in some more debugging in the sysadm_client.<commit_after>//===========================================
//  PC-BSD source code
//  Copyright (c) 2016, PC-BSD Software/iXsystems
//  Available under the 3-clause BSD license
//  See the LICENSE file for full details
//===========================================
#include "sysadm-client.h"
#include <QSslConfiguration>
#include <QJsonArray>
#include <QProcess>
#include <QFile>
#include <QTimer>
#include <QSettings>
#include <QSslKey>
#include <QSslCertificate>

//SSL Stuff
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/err.h>

#define SERVERPIDFILE QString("/var/run/sysadm.pid")

#define DEBUG 0

extern QSettings *settings;
//Unencrypted SSL objects (after loading them by user passphrase)
extern QSslConfiguration SSL_cfg; //Check "isNull()" to see if the user settings have been loaded yet

// === PUBLIC ===
sysadm_client::sysadm_client(){
  SOCKET = new QWebSocket("sysadm-client", QWebSocketProtocol::VersionLatest, this);
    //SOCKET->setSslConfiguration(QSslConfiguration::defaultConfiguration());
    //SOCKET->ignoreSslErrors();
    //use the new Qt5 connection syntax for compile time checks that connections are valid
    connect(SOCKET, &QWebSocket::connected, this, &sysadm_client::socketConnected);
    connect(SOCKET, &QWebSocket::disconnected, this, &sysadm_client::socketClosed);
    connect(SOCKET, &QWebSocket::textMessageReceived, this, &sysadm_client::socketMessage);
    //Old connect syntax for the "error" signal (special note about issues in the docs)
    connect(SOCKET, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)) );
    connect(SOCKET, SIGNAL(sslErrors(const QList<QSslError>&)), this, SLOT(socketSslErrors(const QList<QSslError>&)) );
  keepActive=false; //not setup yet
}

sysadm_client::~sysadm_client(){

}

// Overall Connection functions (start/stop)
void sysadm_client::openConnection(QString user, QString pass, QString hostIP){
  cuser = user; cpass = pass; chost = hostIP;
  //qDebug() << "Client: Setup connection:" << user << pass << hostIP;
  setupSocket();
}

void sysadm_client::openConnection(QString authkey, QString hostIP){
  cauthkey = authkey; chost = hostIP;
  setupSocket();
}

void sysadm_client::openConnection(QString hostIP){
  chost = hostIP;
  setupSocket();
}

void sysadm_client::closeConnection(){
  keepActive = false;
  //de-authorize the current auth token
  if(!cauthkey.isEmpty()){
    cauthkey.clear();
    clearAuth();
  }
  //Now close the connection
  SOCKET->close(QWebSocketProtocol::CloseCodeNormal, "sysadm-client closed");
}

QString sysadm_client::currentHost(){
  return chost;
}

bool sysadm_client::isActive(){
  return ((SOCKET!=0) && SOCKET->isValid());
}

//Check if the sysadm server is running on the local system
bool sysadm_client::localhostAvailable(){
  #ifdef __FreeBSD__
  if(DEBUG){ qDebug() << "Checking for Local Host:" << SERVERPIDFILE; }
  //Check if the local socket can connect
  if(QFile::exists(SERVERPIDFILE)){
    //int ret = QProcess::execute("pgrep -f \""+SERVERPIDFILE+"\"");
    //return (ret==0 || ret>3);
    return true;
  }
  #endif
  return false;
}

// Register for Event Notifications (no notifications by default)
void sysadm_client::registerForEvents(EVENT_TYPE event, bool receive){
  bool set = events.contains(event);
  if( set && receive){ return; } //already registered
  else if(!set && !receive){ return; } //already unregistered
  else if(set){ events << event; }
  else{ events.removeAll(event); }
  //Since this can be setup before the socket is connected - see if we can send this message right away
  if(SOCKET->isValid()){
    sendEventSubscription(event, receive);
  }
}

//Register the custom SSL Certificate with the server
void sysadm_client::registerCustomCert(){
  if(SSL_cfg.isNull() || SOCKET==0 || !SOCKET->isValid()){ return; }
  //Get the custom cert
  QList<QSslCertificate> certs = SSL_cfg.localCertificateChain();
  QString pubkey, email, nickname;
  for(int i=0; i<certs.length(); i++){
    if(certs[i].issuerInfo(QSslCertificate::Organization).contains("SysAdm-client")){
      pubkey = QString(certs[i].publicKey().toPem().toBase64());
      email = certs[i].issuerInfo(QSslCertificate::EmailAddress).join("");
      nickname = certs[i].issuerInfo(QSslCertificate::Organization).join("");
      break;
    }
  }
  if(pubkey.isEmpty()){ return; } //no cert found
  //Now assemble the request JSON
  QJsonObject obj;
    obj.insert("action","register_ssl_cert");
    obj.insert("pub_key", pubkey);
    obj.insert("email",email);
    obj.insert("nickname",nickname);
  this->communicate("sysadm-auto-cert-register","sysadm","settings", obj);

}

// Messages which are still pending a response
QStringList sysadm_client::pending(){ return PENDING; } //returns only the "id" for each

// Fetch a message from the recent cache
QJsonObject sysadm_client::cachedRequest(QString id){
  if(SENT.contains(id)){ return SENT.value(id); }
  else{ return QJsonObject(); }
}

QJsonValue sysadm_client::cachedReply(QString id){
  if(BACK.contains(id)){ return BACK.value(id); }
  else{ return QJsonObject(); }
}

// === PRIVATE ===
//Functions to do the initial socket setup
void sysadm_client::setupSocket(){
  //qDebug() << "Setup Socket:" << SOCKET->isValid();
  if(SOCKET->isValid()){ return; }
  //Setup the SSL config as needed
  SOCKET->setSslConfiguration(QSslConfiguration::defaultConfiguration());
  //uses chost for setup
  // - assemble the host URL
  if(chost.contains("://")){ chost = chost.section("://",1,1); } //Chop off the custom http/ftp/other header (always need "wss://")
  QString url = "wss://"+chost;
  bool hasport = false;
  url.section(":",-1).toInt(&hasport); //check if the last piece of the url is a valid number
  //Could add a check for a valid port number as well - but that is a bit overkill right now
  if(!hasport){ url.append(":"+QString::number(WSPORTDEFAULT)); }
  qDebug() << " Open WebSocket:  URL:" << url;
  QTimer::singleShot(0,SOCKET, SLOT(ignoreSslErrors()) );
  SOCKET->open(QUrl(url));
    //QList<QSslError> ignored; ignored << QSslError(QSslError::SelfSignedCertificate) << QSslError(QSslError::HostNameMismatch);
    //SOCKET->ignoreSslErrors(ignored);
}

void sysadm_client::performAuth(QString user, QString pass){
  //uses cauthkey if empty inputs
  QJsonObject obj;
  obj.insert("namespace","rpc");
  obj.insert("id","sysadm-client-auth-auto");
  bool noauth = false;
  if(user.isEmpty()){
    if(cauthkey.isEmpty()){
      //SSL Authentication (Stage 1)
      obj.insert("name","auth_ssl");
      obj.insert("args","");
    }else{
      //Saved token authentication
      obj.insert("name","auth_token");
      QJsonObject arg;
	arg.insert("token",cauthkey);
      obj.insert("args", arg);
    }
  }else{
    //User/password authentication
    obj.insert("name","auth");
    QJsonObject arg;
      arg.insert("username",user);
      arg.insert("password",pass);
    obj.insert("args", arg);
  }
  sendSocketMessage(obj);
  if(noauth){ emit clientUnauthorized(); }
}

void sysadm_client::clearAuth(){
  QJsonObject obj;
  obj.insert("namespace","rpc");
  obj.insert("id","sysadm-client-auth-auto");
  obj.insert("name","auth_clear");
  obj.insert("args","");
  sendSocketMessage(obj);
  emit clientUnauthorized();
}

//Communication subroutines with the server (block until message comes back)
void sysadm_client::sendEventSubscription(EVENT_TYPE event, bool subscribe){
  QJsonObject obj;
  obj.insert("namespace","events");
  obj.insert("name", subscribe ? "subscribe" : "unsubscribe");
  obj.insert("id", "sysadm-client-event-auto");
  QString arg;
  if(event == DISPATCHER){ arg = "dispatcher"; }
  else if(event == LIFEPRESERVER){ arg = "life-preserver"; }
  obj.insert("args", arg);
  sendSocketMessage(obj);
}

void sysadm_client::sendSocketMessage(QJsonObject msg){
  QJsonDocument doc(msg);
  if(DEBUG){ qDebug() << "Send Socket Message:" << doc.toJson(QJsonDocument::Compact); }
  SOCKET->sendTextMessage(doc.toJson(QJsonDocument::Compact));
}

//Simplification functions
QJsonObject sysadm_client::convertServerReply(QString reply){
  QJsonDocument doc = QJsonDocument::fromJson(reply.toUtf8());
  if(doc.isObject()){ return doc.object(); }
  else{ return QJsonObject(); }
}

QString sysadm_client::SSL_Encode_String(QString str){
  //Get the private key
  QByteArray privkey = SSL_cfg.privateKey().toPem();

  //Now use this private key to encode the given string
  unsigned char encode[4098] = {};
  RSA *rsa= NULL;
  BIO *keybio = NULL;
  keybio = BIO_new_mem_buf(privkey.data(), -1);
  if(keybio==NULL){ return ""; }
  rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa,NULL, NULL);
  bool ok = (-1 != RSA_private_encrypt(str.length(), (unsigned char*)(str.toLatin1().data()), encode, rsa, RSA_PKCS1_PADDING) );
  if(!ok){ return ""; }
  else{
    //Now return this as a base64 encoded string
    return QString( QByteArray( (char*)(encode) ).toBase64() );
  }

}
// === PUBLIC SLOTS ===
// Communications with server (send message, get response via signal later)
void sysadm_client::communicate(QString ID, QString namesp, QString name, QJsonValue args){
  //Overloaded function for a request which needs assembly
  QJsonObject obj;
  obj.insert("namespace",namesp);
  obj.insert("name", name);
  obj.insert("id", ID);
  obj.insert("args", args);
  //qDebug() << "Send Message:" << QJsonDocument(obj).toJson();
  communicate(QList<QJsonObject>() << obj);
}

void sysadm_client::communicate(QJsonObject request){
  //Overloaded function for a single JSON request
  communicate(QList<QJsonObject>() << request);
}

void sysadm_client::communicate(QList<QJsonObject> requests){
  for(int i=0; i<requests.length(); i++){
    QString ID = requests[i].value("id").toString();
    if(ID.isEmpty()){
      qDebug() << "Malformed JSON request:" << requests[i];
      continue;
    }
    //Save this into the cache
    SENT.insert(ID, requests[i]);
    if(BACK.contains(ID)){ BACK.remove(ID); }
    PENDING << ID;
    //Now send off the message
    if(SOCKET->isValid()){ sendSocketMessage(requests[i]); }
  }
}

// === PRIVATE SLOTS ===
//Socket signal/slot connections
void sysadm_client::socketConnected(){ //Signal: connected()
  keepActive = true; //got a valid connection - try to keep this open automatically
  num_fail = 0; //reset fail counter - got a valid connection
  emit clientConnected();
  performAuth(cuser, cpass);
  cuser.clear(); cpass.clear(); //just to ensure no trace left in memory
}

void sysadm_client::socketClosed(){ //Signal: disconnected()
  qDebug() << " - Connection Closed:" << chost;
  /*if(keepActive && num_fail < FAIL_MAX){
    //Socket closed due to timeout?
    // Go ahead and re-open it if possible with the last-used settings/auth
    num_fail++;
    setupSocket();
  }else{*/
    num_fail = 0; //reset back to nothing
    emit clientDisconnected();
    //Server cache is now invalid - completely lost connection
    SENT.clear(); BACK.clear(); PENDING.clear();
  //}
}

void sysadm_client::socketSslErrors(const QList<QSslError>&errlist){ //Signal: sslErrors()
  qWarning() << "SSL Errors Detected:" << errlist.length();
  QList<QSslError> ignored;
  for(int i=0; i< errlist.length(); i++){
    if(errlist[i].error()==QSslError::SelfSignedCertificate || errlist[i].error()==QSslError::HostNameMismatch ){
      qDebug() << " - (IGNORED) " << errlist[i].errorString();
      ignored << errlist[i];
    }else{
      qWarning() << " - " << errlist[i].errorString();
    }
  }
  if(ignored.length() != errlist.length()){
    SOCKET->close(); //SSL errors - close the connection
  }
}

void sysadm_client::socketError(QAbstractSocket::SocketError err){ //Signal:: error()
  qWarning() << "Socket Error detected:" << err;
  if(err==QAbstractSocket::SslHandshakeFailedError){qWarning() << " - SSL Handshake Failed"; }
  qWarning() << " - Final websocket error:" << SOCKET->errorString();
}
//void sysadm_client::socketProxyAuthRequired(const QNetworkProxy &proxy, QAuthenticator *auth); //Signal: proxyAuthenticationRequired()

// - Main message input parsing
void sysadm_client::socketMessage(QString msg){ //Signal: textMessageReceived()
  if(DEBUG){ qDebug() << "New Reply From Server:" << msg; }
  //Convert this into a JSON object
  QJsonObject obj = convertServerReply(msg);
  QString ID = obj.value("id").toString();
  QString namesp = obj.value("namespace").toString();
  QString name = obj.value("name").toString();
  //Still need to add some parsing to the return message
  if(ID=="sysadm-client-auth-auto"){
    //Reply to automated auth system
    if(name=="error"){
      closeConnection();
      emit clientUnauthorized();
    }else{
      QJsonValue args = obj.value("args");
      if(args.isArray()){
	      cauthkey = args.toArray().first().toString();
	      emit clientAuthorized();
	      //Now automatically re-subscribe to events as needed
	      for(int i=0; i<events.length(); i++){ sendEventSubscription(events[i]); }
      }else if(args.isObject()){
        //SSL Auth Stage 2
        QString randomkey = args.toObject().value("test_string").toString();
        if(!randomkey.isEmpty()){
          QJsonObject obj;
          obj.insert("name","auth_ssl");
          obj.insert("namespace","rpc");
          obj.insert("id",ID); //re-use this special ID
          QJsonObject args;
          args.insert("encrypted_string", SSL_Encode_String(randomkey));
          obj.insert("args",args);
          this->communicate(obj);
        }
      }
    }
  }else if(ID=="sysadm-client-event-auto"){
    //Reply to automated event subscription - don't need to save this
  }else if(namesp=="events"){
    //Event notification - not tied to any particular request
    if(name=="dispatcher"){ emit NewEvent(DISPATCHER, obj.value("args")); }
    else if(name=="life-preserver"){ emit NewEvent(LIFEPRESERVER, obj.value("args")); }
  }else{
    //Now save this message into the cache for use later (if not an auth reply)
    if(!ID.isEmpty()){
      PENDING.removeAll(ID);
      BACK.insert(ID, obj);
    }
    emit newReply(ID, name, namesp, obj.value("args"));
  }
}
	<|endoftext|>


<commit_before>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "qmljseditorconstants.h"
#include "qmljsmodelmanager.h"
#include "qmljseditor.h"

#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/mimedatabase.h>
#include <qmljs/qmljsinterpreter.h>
#include <qmljs/qmljsbind.h>
#include <qmljs/parser/qmldirparser_p.h>
#include <texteditor/itexteditor.h>

#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLibraryInfo>
#include <QtConcurrentRun>
#include <qtconcurrent/runextensions.h>
#include <QTextStream>

#include <QDebug>

using namespace QmlJS;
using namespace QmlJSEditor;
using namespace QmlJSEditor::Internal;

static QStringList environmentImportPaths();

ModelManager::ModelManager(QObject *parent):
        ModelManagerInterface(parent),
        m_core(Core::ICore::instance())
{
    m_synchronizer.setCancelOnWait(true);

    qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr");
    qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo");

    connect(this, SIGNAL(documentUpdated(QmlJS::Document::Ptr)),
            this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr)));
    connect(this, SIGNAL(libraryInfoUpdated(QString,QmlJS::LibraryInfo)),
            this, SLOT(onLibraryInfoUpdated(QString,QmlJS::LibraryInfo)));

    loadQmlTypeDescriptions();

    m_defaultImportPaths << environmentImportPaths();
    m_defaultImportPaths << QLibraryInfo::location(QLibraryInfo::ImportsPath);
}

void ModelManager::loadQmlTypeDescriptions()
{
    const QString resourcePath = Core::ICore::instance()->resourcePath();
    const QDir typeFileDir(resourcePath + QLatin1String("/qml-type-descriptions"));
    const QStringList xmlExtensions = QStringList() << QLatin1String("*.xml");
    const QFileInfoList xmlFiles = typeFileDir.entryInfoList(xmlExtensions,
                                                             QDir::Files,
                                                             QDir::Name);

    const QStringList errors = Interpreter::MetaTypeSystem::load(xmlFiles);
    foreach (const QString &error, errors)
        qWarning() << qPrintable(error);
}

Snapshot ModelManager::snapshot() const
{
    QMutexLocker locker(&m_mutex);

    return _snapshot;
}

void ModelManager::updateSourceFiles(const QStringList &files)
{
    refreshSourceFiles(files);
}

QFuture<void> ModelManager::refreshSourceFiles(const QStringList &sourceFiles)
{
    if (sourceFiles.isEmpty()) {
        return QFuture<void>();
    }

    const QMap<QString, WorkingCopy> workingCopy = buildWorkingCopyList();

    QFuture<void> result = QtConcurrent::run(&ModelManager::parse,
                                              workingCopy, sourceFiles,
                                              this);

    if (m_synchronizer.futures().size() > 10) {
        QList<QFuture<void> > futures = m_synchronizer.futures();

        m_synchronizer.clearFutures();

        foreach (QFuture<void> future, futures) {
            if (! (future.isFinished() || future.isCanceled()))
                m_synchronizer.addFuture(future);
        }
    }

    m_synchronizer.addFuture(result);

    if (sourceFiles.count() > 1) {
        m_core->progressManager()->addTask(result, tr("Indexing"),
                        QmlJSEditor::Constants::TASK_INDEX);
    }

    return result;
}

QMap<QString, ModelManager::WorkingCopy> ModelManager::buildWorkingCopyList()
{
    QMap<QString, WorkingCopy> workingCopy;
    Core::EditorManager *editorManager = m_core->editorManager();

    foreach (Core::IEditor *editor, editorManager->openedEditors()) {
        const QString key = editor->file()->fileName();

        if (TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor)) {
            if (QmlJSTextEditor *ed = qobject_cast<QmlJSTextEditor *>(textEditor->widget())) {
                workingCopy[key].contents = ed->toPlainText();
                workingCopy[key].documentRevision = ed->document()->revision();
            }
        }
    }

    return workingCopy;
}

void ModelManager::emitDocumentUpdated(Document::Ptr doc)
{ emit documentUpdated(doc); }

void ModelManager::onDocumentUpdated(Document::Ptr doc)
{
    QMutexLocker locker(&m_mutex);

    _snapshot.insert(doc);
}

void ModelManager::emitLibraryInfoUpdated(const QString &path, const LibraryInfo &info)
{ emit libraryInfoUpdated(path, info); }

void ModelManager::onLibraryInfoUpdated(const QString &path, const LibraryInfo &info)
{
    QMutexLocker locker(&m_mutex);

    _snapshot.insertLibraryInfo(path, info);
}

static QStringList qmlFilesInDirectory(const QString &path)
{
    // ### It would suffice to build pattern once. This function needs to be thread-safe.
    Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase();
    Core::MimeType jsSourceTy = db->findByType(QmlJSEditor::Constants::JS_MIMETYPE);
    Core::MimeType qmlSourceTy = db->findByType(QmlJSEditor::Constants::QML_MIMETYPE);

    QStringList pattern;
    foreach (const QRegExp &glob, jsSourceTy.globPatterns())
        pattern << glob.pattern();
    foreach (const QRegExp &glob, qmlSourceTy.globPatterns())
        pattern << glob.pattern();

    QStringList files;

    const QDir dir(path);
    foreach (const QFileInfo &fi, dir.entryInfoList(pattern, QDir::Files))
        files += fi.absoluteFilePath();

    return files;
}

static void findNewImplicitImports(const Document::Ptr &doc, const Snapshot &snapshot,
                            QStringList *importedFiles, QSet<QString> *scannedPaths)
{
    // scan files that could be implicitly imported
    // it's important we also do this for JS files, otherwise the isEmpty check will fail
    if (snapshot.documentsInDirectory(doc->path()).isEmpty()) {
        if (! scannedPaths->contains(doc->path())) {
            *importedFiles += qmlFilesInDirectory(doc->path());
            scannedPaths->insert(doc->path());
        }
    }
}

static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapshot,
                        QStringList *importedFiles, QSet<QString> *scannedPaths)
{
    // scan files and directories that are explicitly imported
    foreach (const QString &fileImport, doc->bind()->fileImports()) {
        const QFileInfo importFileInfo(doc->path() + QLatin1Char('/') + fileImport);
        const QString &importFilePath = importFileInfo.absoluteFilePath();
        if (importFileInfo.isFile()) {
            if (! snapshot.document(importFilePath))
                *importedFiles += importFilePath;
        } else if (importFileInfo.isDir()) {
            if (snapshot.documentsInDirectory(importFilePath).isEmpty()) {
                if (! scannedPaths->contains(importFilePath)) {
                    *importedFiles += qmlFilesInDirectory(importFilePath);
                    scannedPaths->insert(importFilePath);
                }
            }
        }
    }
}

static void findNewLibraryImports(const Document::Ptr &doc, const Snapshot &snapshot,
                           ModelManager *modelManager,
                           QStringList *importedFiles, QSet<QString> *scannedPaths)
{
    // scan library imports
    foreach (const QString &libraryImport, doc->bind()->libraryImports()) {
        foreach (const QString &importPath, modelManager->importPaths()) {
            QDir dir(importPath);
            dir.cd(libraryImport);
            const QString targetPath = dir.absolutePath();

            // if we know there is a library, done
            if (snapshot.libraryInfo(targetPath).isValid())
                break;

            // if there is a qmldir file, we found a new library!
            if (dir.exists("qmldir")) {
                QFile qmldirFile(dir.filePath("qmldir"));
                qmldirFile.open(QFile::ReadOnly);
                QString qmldirData = QString::fromUtf8(qmldirFile.readAll());

                QmlDirParser qmldirParser;
                qmldirParser.setSource(qmldirData);
                qmldirParser.parse();

                modelManager->emitLibraryInfoUpdated(QFileInfo(qmldirFile).absolutePath(),
                                                     LibraryInfo(qmldirParser));

                // scan the qml files in the library
                foreach (const QmlDirParser::Component &component, qmldirParser.components()) {
                    if (! component.fileName.isEmpty()) {
                        QFileInfo componentFileInfo(dir.filePath(component.fileName));
                        const QString path = componentFileInfo.absolutePath();
                        if (! scannedPaths->contains(path)) {
                            *importedFiles += qmlFilesInDirectory(path);
                            scannedPaths->insert(path);
                        }
                    }
                }
            }
        }
    }
}

void ModelManager::parse(QFutureInterface<void> &future,
                            QMap<QString, WorkingCopy> workingCopy,
                            QStringList files,
                            ModelManager *modelManager)
{
    Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase();
    Core::MimeType jsSourceTy = db->findByType(QLatin1String("application/javascript"));
    Core::MimeType qmlSourceTy = db->findByType(QLatin1String("application/x-qml"));

    int progressRange = files.size();
    future.setProgressRange(0, progressRange);

    Snapshot snapshot = modelManager->_snapshot;

    // paths we have scanned for files and added to the files list
    QSet<QString> scannedPaths;

    for (int i = 0; i < files.size(); ++i) {
        future.setProgressValue(qreal(i) / files.size() * progressRange);

        const QString fileName = files.at(i);

        const QFileInfo fileInfo(fileName);
        Core::MimeType fileMimeTy = db->findByFile(fileInfo);

        bool isQmlFile = true;

        if (matchesMimeType(fileMimeTy, jsSourceTy))
            isQmlFile = false;

        else if (! matchesMimeType(fileMimeTy, qmlSourceTy))
            continue; // skip it. it's not a QML or a JS file.

        QString contents;
        int documentRevision = 0;

        if (workingCopy.contains(fileName)) {
            WorkingCopy wc = workingCopy.value(fileName);
            contents = wc.contents;
            documentRevision = wc.documentRevision;
        } else {
            QFile inFile(fileName);

            if (inFile.open(QIODevice::ReadOnly)) {
                QTextStream ins(&inFile);
                contents = ins.readAll();
                inFile.close();
            }
        }

        Document::Ptr doc = Document::create(fileName);
        doc->setDocumentRevision(documentRevision);
        doc->setSource(contents);
        doc->parse();

        // get list of referenced files not yet in snapshot or in directories already scanned
        QStringList importedFiles;
        findNewImplicitImports(doc, snapshot, &importedFiles, &scannedPaths);
        findNewFileImports(doc, snapshot, &importedFiles, &scannedPaths);
        findNewLibraryImports(doc, snapshot, modelManager, &importedFiles, &scannedPaths);

        // add new files to parse list
        foreach (const QString &file, importedFiles) {
            if (! files.contains(file))
                files.append(file);
        }

        modelManager->emitDocumentUpdated(doc);
    }

    future.setProgressValue(progressRange);
}

// Check whether fileMimeType is the same or extends knownMimeType
bool ModelManager::matchesMimeType(const Core::MimeType &fileMimeType, const Core::MimeType &knownMimeType)
{
    Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase();

    const QStringList knownTypeNames = QStringList(knownMimeType.type()) + knownMimeType.aliases();

    foreach (const QString knownTypeName, knownTypeNames)
        if (fileMimeType.matchesType(knownTypeName))
            return true;

    // recursion to parent types of fileMimeType
    foreach (const QString &parentMimeType, fileMimeType.subClassesOf()) {
        if (matchesMimeType(db->findByType(parentMimeType), knownMimeType))
            return true;
    }

    return false;
}

void ModelManager::setProjectImportPaths(const QStringList &importPaths)
{
    m_projectImportPaths = importPaths;

    // check if any file in the snapshot imports something new in the new paths
    Snapshot snapshot = _snapshot;
    QStringList importedFiles;
    QSet<QString> scannedPaths;
    foreach (const Document::Ptr &doc, snapshot)
        findNewLibraryImports(doc, snapshot, this, &importedFiles, &scannedPaths);

    updateSourceFiles(importedFiles);
}

QStringList ModelManager::importPaths() const
{
    QStringList paths;
    paths << m_projectImportPaths;
    paths << m_defaultImportPaths;
    return paths;
}

static QStringList environmentImportPaths()
{
    QStringList paths;

    QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");

#if defined(Q_OS_WIN)
    QLatin1Char pathSep(';');
#else
    QLatin1Char pathSep(':');
#endif
    foreach (const QString &path, QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts)) {
        QString canonicalPath = QDir(path).canonicalPath();
        if (!canonicalPath.isEmpty() && !paths.contains(canonicalPath))
            paths.append(canonicalPath);
    }

    return paths;
}
<commit_msg>QmlJSEditor: When looking for qml libraries scan the current doc's path.<commit_after>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "qmljseditorconstants.h"
#include "qmljsmodelmanager.h"
#include "qmljseditor.h"

#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/mimedatabase.h>
#include <qmljs/qmljsinterpreter.h>
#include <qmljs/qmljsbind.h>
#include <qmljs/parser/qmldirparser_p.h>
#include <texteditor/itexteditor.h>

#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLibraryInfo>
#include <QtConcurrentRun>
#include <qtconcurrent/runextensions.h>
#include <QTextStream>

#include <QDebug>

using namespace QmlJS;
using namespace QmlJSEditor;
using namespace QmlJSEditor::Internal;

static QStringList environmentImportPaths();

ModelManager::ModelManager(QObject *parent):
        ModelManagerInterface(parent),
        m_core(Core::ICore::instance())
{
    m_synchronizer.setCancelOnWait(true);

    qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr");
    qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo");

    connect(this, SIGNAL(documentUpdated(QmlJS::Document::Ptr)),
            this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr)));
    connect(this, SIGNAL(libraryInfoUpdated(QString,QmlJS::LibraryInfo)),
            this, SLOT(onLibraryInfoUpdated(QString,QmlJS::LibraryInfo)));

    loadQmlTypeDescriptions();

    m_defaultImportPaths << environmentImportPaths();
    m_defaultImportPaths << QLibraryInfo::location(QLibraryInfo::ImportsPath);
}

void ModelManager::loadQmlTypeDescriptions()
{
    const QString resourcePath = Core::ICore::instance()->resourcePath();
    const QDir typeFileDir(resourcePath + QLatin1String("/qml-type-descriptions"));
    const QStringList xmlExtensions = QStringList() << QLatin1String("*.xml");
    const QFileInfoList xmlFiles = typeFileDir.entryInfoList(xmlExtensions,
                                                             QDir::Files,
                                                             QDir::Name);

    const QStringList errors = Interpreter::MetaTypeSystem::load(xmlFiles);
    foreach (const QString &error, errors)
        qWarning() << qPrintable(error);
}

Snapshot ModelManager::snapshot() const
{
    QMutexLocker locker(&m_mutex);

    return _snapshot;
}

void ModelManager::updateSourceFiles(const QStringList &files)
{
    refreshSourceFiles(files);
}

QFuture<void> ModelManager::refreshSourceFiles(const QStringList &sourceFiles)
{
    if (sourceFiles.isEmpty()) {
        return QFuture<void>();
    }

    const QMap<QString, WorkingCopy> workingCopy = buildWorkingCopyList();

    QFuture<void> result = QtConcurrent::run(&ModelManager::parse,
                                              workingCopy, sourceFiles,
                                              this);

    if (m_synchronizer.futures().size() > 10) {
        QList<QFuture<void> > futures = m_synchronizer.futures();

        m_synchronizer.clearFutures();

        foreach (QFuture<void> future, futures) {
            if (! (future.isFinished() || future.isCanceled()))
                m_synchronizer.addFuture(future);
        }
    }

    m_synchronizer.addFuture(result);

    if (sourceFiles.count() > 1) {
        m_core->progressManager()->addTask(result, tr("Indexing"),
                        QmlJSEditor::Constants::TASK_INDEX);
    }

    return result;
}

QMap<QString, ModelManager::WorkingCopy> ModelManager::buildWorkingCopyList()
{
    QMap<QString, WorkingCopy> workingCopy;
    Core::EditorManager *editorManager = m_core->editorManager();

    foreach (Core::IEditor *editor, editorManager->openedEditors()) {
        const QString key = editor->file()->fileName();

        if (TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor)) {
            if (QmlJSTextEditor *ed = qobject_cast<QmlJSTextEditor *>(textEditor->widget())) {
                workingCopy[key].contents = ed->toPlainText();
                workingCopy[key].documentRevision = ed->document()->revision();
            }
        }
    }

    return workingCopy;
}

void ModelManager::emitDocumentUpdated(Document::Ptr doc)
{ emit documentUpdated(doc); }

void ModelManager::onDocumentUpdated(Document::Ptr doc)
{
    QMutexLocker locker(&m_mutex);

    _snapshot.insert(doc);
}

void ModelManager::emitLibraryInfoUpdated(const QString &path, const LibraryInfo &info)
{ emit libraryInfoUpdated(path, info); }

void ModelManager::onLibraryInfoUpdated(const QString &path, const LibraryInfo &info)
{
    QMutexLocker locker(&m_mutex);

    _snapshot.insertLibraryInfo(path, info);
}

static QStringList qmlFilesInDirectory(const QString &path)
{
    // ### It would suffice to build pattern once. This function needs to be thread-safe.
    Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase();
    Core::MimeType jsSourceTy = db->findByType(QmlJSEditor::Constants::JS_MIMETYPE);
    Core::MimeType qmlSourceTy = db->findByType(QmlJSEditor::Constants::QML_MIMETYPE);

    QStringList pattern;
    foreach (const QRegExp &glob, jsSourceTy.globPatterns())
        pattern << glob.pattern();
    foreach (const QRegExp &glob, qmlSourceTy.globPatterns())
        pattern << glob.pattern();

    QStringList files;

    const QDir dir(path);
    foreach (const QFileInfo &fi, dir.entryInfoList(pattern, QDir::Files))
        files += fi.absoluteFilePath();

    return files;
}

static void findNewImplicitImports(const Document::Ptr &doc, const Snapshot &snapshot,
                            QStringList *importedFiles, QSet<QString> *scannedPaths)
{
    // scan files that could be implicitly imported
    // it's important we also do this for JS files, otherwise the isEmpty check will fail
    if (snapshot.documentsInDirectory(doc->path()).isEmpty()) {
        if (! scannedPaths->contains(doc->path())) {
            *importedFiles += qmlFilesInDirectory(doc->path());
            scannedPaths->insert(doc->path());
        }
    }
}

static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapshot,
                        QStringList *importedFiles, QSet<QString> *scannedPaths)
{
    // scan files and directories that are explicitly imported
    foreach (const QString &fileImport, doc->bind()->fileImports()) {
        const QFileInfo importFileInfo(doc->path() + QLatin1Char('/') + fileImport);
        const QString &importFilePath = importFileInfo.absoluteFilePath();
        if (importFileInfo.isFile()) {
            if (! snapshot.document(importFilePath))
                *importedFiles += importFilePath;
        } else if (importFileInfo.isDir()) {
            if (snapshot.documentsInDirectory(importFilePath).isEmpty()) {
                if (! scannedPaths->contains(importFilePath)) {
                    *importedFiles += qmlFilesInDirectory(importFilePath);
                    scannedPaths->insert(importFilePath);
                }
            }
        }
    }
}

static void findNewLibraryImports(const Document::Ptr &doc, const Snapshot &snapshot,
                           ModelManager *modelManager,
                           QStringList *importedFiles, QSet<QString> *scannedPaths)
{
    // scan library imports
    QStringList importPaths = modelManager->importPaths();
    importPaths.prepend(doc->path());
    foreach (const QString &libraryImport, doc->bind()->libraryImports()) {
        foreach (const QString &importPath, importPaths) {
            QDir dir(importPath);
            dir.cd(libraryImport);
            const QString targetPath = dir.absolutePath();

            // if we know there is a library, done
            if (snapshot.libraryInfo(targetPath).isValid())
                break;

            // if there is a qmldir file, we found a new library!
            if (dir.exists("qmldir")) {
                QFile qmldirFile(dir.filePath("qmldir"));
                qmldirFile.open(QFile::ReadOnly);
                QString qmldirData = QString::fromUtf8(qmldirFile.readAll());

                QmlDirParser qmldirParser;
                qmldirParser.setSource(qmldirData);
                qmldirParser.parse();

                modelManager->emitLibraryInfoUpdated(QFileInfo(qmldirFile).absolutePath(),
                                                     LibraryInfo(qmldirParser));

                // scan the qml files in the library
                foreach (const QmlDirParser::Component &component, qmldirParser.components()) {
                    if (! component.fileName.isEmpty()) {
                        QFileInfo componentFileInfo(dir.filePath(component.fileName));
                        const QString path = componentFileInfo.absolutePath();
                        if (! scannedPaths->contains(path)) {
                            *importedFiles += qmlFilesInDirectory(path);
                            scannedPaths->insert(path);
                        }
                    }
                }
            }
        }
    }
}

void ModelManager::parse(QFutureInterface<void> &future,
                            QMap<QString, WorkingCopy> workingCopy,
                            QStringList files,
                            ModelManager *modelManager)
{
    Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase();
    Core::MimeType jsSourceTy = db->findByType(QLatin1String("application/javascript"));
    Core::MimeType qmlSourceTy = db->findByType(QLatin1String("application/x-qml"));

    int progressRange = files.size();
    future.setProgressRange(0, progressRange);

    Snapshot snapshot = modelManager->_snapshot;

    // paths we have scanned for files and added to the files list
    QSet<QString> scannedPaths;

    for (int i = 0; i < files.size(); ++i) {
        future.setProgressValue(qreal(i) / files.size() * progressRange);

        const QString fileName = files.at(i);

        const QFileInfo fileInfo(fileName);
        Core::MimeType fileMimeTy = db->findByFile(fileInfo);

        bool isQmlFile = true;

        if (matchesMimeType(fileMimeTy, jsSourceTy))
            isQmlFile = false;

        else if (! matchesMimeType(fileMimeTy, qmlSourceTy))
            continue; // skip it. it's not a QML or a JS file.

        QString contents;
        int documentRevision = 0;

        if (workingCopy.contains(fileName)) {
            WorkingCopy wc = workingCopy.value(fileName);
            contents = wc.contents;
            documentRevision = wc.documentRevision;
        } else {
            QFile inFile(fileName);

            if (inFile.open(QIODevice::ReadOnly)) {
                QTextStream ins(&inFile);
                contents = ins.readAll();
                inFile.close();
            }
        }

        Document::Ptr doc = Document::create(fileName);
        doc->setDocumentRevision(documentRevision);
        doc->setSource(contents);
        doc->parse();

        // get list of referenced files not yet in snapshot or in directories already scanned
        QStringList importedFiles;
        findNewImplicitImports(doc, snapshot, &importedFiles, &scannedPaths);
        findNewFileImports(doc, snapshot, &importedFiles, &scannedPaths);
        findNewLibraryImports(doc, snapshot, modelManager, &importedFiles, &scannedPaths);

        // add new files to parse list
        foreach (const QString &file, importedFiles) {
            if (! files.contains(file))
                files.append(file);
        }

        modelManager->emitDocumentUpdated(doc);
    }

    future.setProgressValue(progressRange);
}

// Check whether fileMimeType is the same or extends knownMimeType
bool ModelManager::matchesMimeType(const Core::MimeType &fileMimeType, const Core::MimeType &knownMimeType)
{
    Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase();

    const QStringList knownTypeNames = QStringList(knownMimeType.type()) + knownMimeType.aliases();

    foreach (const QString knownTypeName, knownTypeNames)
        if (fileMimeType.matchesType(knownTypeName))
            return true;

    // recursion to parent types of fileMimeType
    foreach (const QString &parentMimeType, fileMimeType.subClassesOf()) {
        if (matchesMimeType(db->findByType(parentMimeType), knownMimeType))
            return true;
    }

    return false;
}

void ModelManager::setProjectImportPaths(const QStringList &importPaths)
{
    m_projectImportPaths = importPaths;

    // check if any file in the snapshot imports something new in the new paths
    Snapshot snapshot = _snapshot;
    QStringList importedFiles;
    QSet<QString> scannedPaths;
    foreach (const Document::Ptr &doc, snapshot)
        findNewLibraryImports(doc, snapshot, this, &importedFiles, &scannedPaths);

    updateSourceFiles(importedFiles);
}

QStringList ModelManager::importPaths() const
{
    QStringList paths;
    paths << m_projectImportPaths;
    paths << m_defaultImportPaths;
    return paths;
}

static QStringList environmentImportPaths()
{
    QStringList paths;

    QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");

#if defined(Q_OS_WIN)
    QLatin1Char pathSep(';');
#else
    QLatin1Char pathSep(':');
#endif
    foreach (const QString &path, QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts)) {
        QString canonicalPath = QDir(path).canonicalPath();
        if (!canonicalPath.isEmpty() && !paths.contains(canonicalPath))
            paths.append(canonicalPath);
    }

    return paths;
}
<|endoftext|>


<commit_before>/**
 * @file
 *
 * @brief Source for simplespeclang plugin
 *
 * @copyright BSD License (see doc/COPYING or http://www.libelektra.org)
 *
 */

#include "simplespeclang.hpp"

using namespace ckdb;

#include <kdbease.h>
#include <kdberrors.h>

#include <kdbmeta.h>

#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>

namespace
{

std::string getConfigEnum (Plugin * handle)
{
	Key * k = ksLookupByName (elektraPluginGetConfig (handle), "/keyword/enum", 0);
	if (!k) return "enum";

	return keyString (k);
}

std::string getConfigAssign (Plugin * handle)
{
	Key * k = ksLookupByName (elektraPluginGetConfig (handle), "/keyword/assign", 0);
	if (!k) return "=";

	return keyString (k);
}

int serialise (std::ostream & os, ckdb::Key * parentKey, ckdb::KeySet * ks, Plugin * handle)
{
	ckdb::Key * cur;

	ksRewind (ks);
	while ((cur = ksNext (ks)) != nullptr)
	{
		const ckdb::Key * meta = ckdb::keyGetMeta (cur, "check/enum");
		if (!meta) continue;

		os << getConfigEnum (handle) << " ";
		os << elektraKeyGetRelativeName (cur, parentKey) << " ";
		os << getConfigAssign (handle);
		while ((meta = keyNextMeta (cur)))
		{
			os << " " << ckdb::keyString (meta);
		}
		os << "\n";
	}

	return 1;
}

int unserialise (std::istream & is, ckdb::Key * errorKey, ckdb::KeySet * ks, Plugin * handle)
{
	Key * cur = nullptr;

	Key * parent = keyNew (keyName (errorKey), KEY_END);

	std::string line;
	while (std::getline (is, line))
	{
		std::string read;
		std::stringstream ss (line);
		ss >> read;
		if (read == "mountpoint")
		{
			ss >> read;
			keySetMeta (parent, "mountpoint", read.c_str ());
			continue;
		}
		else if (read == "plugins")
		{
			std::string plugins;
			while (ss >> read)
			{
				// replace (read.begin(), read.end(), '_', ' ');
				plugins += read + " ";
			}
			keySetMeta (parent, "infos/plugins", plugins.c_str ());
			continue;
		}
		else if (read != getConfigEnum (handle))
		{
			ELEKTRA_LOG ("not an enum %s", read.c_str ());
			continue;
		}

		std::string name;
		ss >> name;
		ELEKTRA_LOG ("key name for enum is %s", name.c_str ());
		cur = keyNew (keyName (errorKey), KEY_END);
		keyAddName (cur, name.c_str ());

		ss >> read;
		if (read != getConfigAssign (handle))
		{
			ELEKTRA_SET_ERRORF (ELEKTRA_ERROR_PARSE, errorKey, "Expected assignment (%s), but got (%s)",
					    getConfigAssign (handle).c_str (), read.c_str ());
			continue;
		}

		int added = 0;
		while (ss >> read)
		{
			++added;
			elektraMetaArrayAdd (cur, "check/enum", read.c_str ());
		}

		if (added)
		{
			ELEKTRA_LOG ("%s is enum with %d entries (last char is %c)", name.c_str (), added, name.back ());
			keySetMeta (cur, "check/enum", "#");
			if (name.back () == '*')
			{
				keySetMeta (cur, "check/enum/multi", " ");
			}
		}

		keySetMeta (cur, "require", "yes");
		// keySetMeta (cur, "required", "yes"); // seems to have wrong semantics
		keySetMeta (cur, "conflict/get/missing", "ERROR");
		keySetMeta (cur, "conflict/set/missing", "ERROR");
		keySetMeta (cur, "mandatory", "yes"); // TODO bug: required key removed by spec?

		ckdb::ksAppendKey (ks, cur);
	}

	ckdb::ksAppendKey (ks, parent);

	return 1;
}
}

extern "C" {

int elektraSimplespeclangGet (Plugin * handle, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
{
	if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/simplespeclang"))
	{
		KeySet * contract =
			ksNew (30, keyNew ("system/elektra/modules/simplespeclang", KEY_VALUE,
					   "simplespeclang plugin waits for your orders", KEY_END),
			       keyNew ("system/elektra/modules/simplespeclang/exports", KEY_END),
			       keyNew ("system/elektra/modules/simplespeclang/exports/get", KEY_FUNC, elektraSimplespeclangGet, KEY_END),
			       keyNew ("system/elektra/modules/simplespeclang/exports/set", KEY_FUNC, elektraSimplespeclangSet, KEY_END),
			       keyNew ("system/elektra/modules/simplespeclang/exports/checkconf", KEY_FUNC,
				       elektraSimplespeclangCheckConfig, KEY_END),
#include ELEKTRA_README (simplespeclang)
			       keyNew ("system/elektra/modules/simplespeclang/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
		ksAppend (returned, contract);
		ksDel (contract);

		return 1; // success
	}

	std::ifstream ofs (keyString (parentKey), std::ios::binary);
	if (!ofs.is_open ())
	{
		ELEKTRA_SET_ERROR_GET (parentKey);
		return -1;
	}

	return unserialise (ofs, parentKey, returned, handle);
}

int elektraSimplespeclangSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
{
	// ELEKTRA_LOG (ELEKTRA_LOG_MODULE_DUMP, "opening file %s", keyString (parentKey));
	std::ofstream ofs (keyString (parentKey), std::ios::binary);
	if (!ofs.is_open ())
	{
		ELEKTRA_SET_ERROR_SET (parentKey);
		return -1;
	}

	return serialise (ofs, parentKey, returned, handle);
}

int elektraSimplespeclangCheckConfig (Key * errorKey ELEKTRA_UNUSED, KeySet * conf ELEKTRA_UNUSED)
{
	return 0;
}

Plugin * ELEKTRA_PLUGIN_EXPORT (simplespeclang)
{
	// clang-format off
	return elektraPluginExport ("simplespeclang",
		ELEKTRA_PLUGIN_GET,	&elektraSimplespeclangGet,
		ELEKTRA_PLUGIN_SET,	&elektraSimplespeclangSet,
		ELEKTRA_PLUGIN_END);
}

}
<commit_msg>simplespeclang: only write out enum meta+log<commit_after>/**
 * @file
 *
 * @brief Source for simplespeclang plugin
 *
 * @copyright BSD License (see doc/COPYING or http://www.libelektra.org)
 *
 */

#include "simplespeclang.hpp"

using namespace ckdb;

#include <kdbease.h>
#include <kdberrors.h>

#include <kdbmeta.h>

#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>

namespace
{

std::string getConfigEnum (Plugin * handle)
{
	Key * k = ksLookupByName (elektraPluginGetConfig (handle), "/keyword/enum", 0);
	if (!k) return "enum";

	return keyString (k);
}

std::string getConfigAssign (Plugin * handle)
{
	Key * k = ksLookupByName (elektraPluginGetConfig (handle), "/keyword/assign", 0);
	if (!k) return "=";

	return keyString (k);
}

bool startsWith (std::string const & str, std::string const & start)
{
	return std::equal (start.begin (), start.end (), str.begin ());
}

int serialise (std::ostream & os, ckdb::Key * parentKey, ckdb::KeySet * ks, Plugin * handle)
{
	ckdb::Key * cur;

	ksRewind (ks);
	while ((cur = ksNext (ks)) != nullptr)
	{
		const ckdb::Key * meta = ckdb::keyGetMeta (cur, "check/enum");
		if (!meta) continue;

		os << getConfigEnum (handle) << " ";
		os << elektraKeyGetRelativeName (cur, parentKey) << " ";
		os << getConfigAssign (handle);
		while ((meta = keyNextMeta (cur)))
		{
			if (startsWith (keyName (meta), "check/enum/#"))
			{
				os << " " << ckdb::keyString (meta);
			}
		}
		os << "\n";
	}

	return 1;
}

int unserialise (std::istream & is, ckdb::Key * errorKey, ckdb::KeySet * ks, Plugin * handle)
{
	Key * cur = nullptr;

	Key * parent = keyNew (keyName (errorKey), KEY_END);

	std::string line;
	while (std::getline (is, line))
	{
		std::string read;
		std::stringstream ss (line);
		ss >> read;
		if (read == "mountpoint")
		{
			ss >> read;
			keySetMeta (parent, "mountpoint", read.c_str ());
			continue;
		}
		else if (read == "plugins")
		{
			std::string plugins;
			while (ss >> read)
			{
				// replace (read.begin(), read.end(), '_', ' ');
				plugins += read + " ";
			}
			keySetMeta (parent, "infos/plugins", plugins.c_str ());
			continue;
		}
		else if (read != getConfigEnum (handle))
		{
			ELEKTRA_LOG ("not an enum %s", read.c_str ());
			continue;
		}

		std::string name;
		ss >> name;
		ELEKTRA_LOG ("key name for enum is %s", name.c_str ());
		cur = keyNew (keyName (errorKey), KEY_END);
		keyAddName (cur, name.c_str ());

		ss >> read;
		if (read != getConfigAssign (handle))
		{
			ELEKTRA_SET_ERRORF (ELEKTRA_ERROR_PARSE, errorKey, "Expected assignment (%s), but got (%s)",
					    getConfigAssign (handle).c_str (), read.c_str ());
			continue;
		}

		int added = 0;
		while (ss >> read)
		{
			++added;
			elektraMetaArrayAdd (cur, "check/enum", read.c_str ());
		}

		if (added)
		{
			ELEKTRA_LOG ("%s is enum with %d entries (last char is %c)", name.c_str (), added, name.back ());
			keySetMeta (cur, "check/enum", "#");
			if (name.back () == '*')
			{
				keySetMeta (cur, "check/enum/multi", " ");
			}
		}

		keySetMeta (cur, "require", "1");
		keySetMeta (cur, "conflict/set/missing", "ERROR");
		keySetMeta (cur, "mandatory", "1");

		ckdb::ksAppendKey (ks, cur);
	}

	ckdb::ksAppendKey (ks, parent);

	return 1;
}
}

extern "C" {

int elektraSimplespeclangGet (Plugin * handle, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
{
	if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/simplespeclang"))
	{
		KeySet * contract =
			ksNew (30, keyNew ("system/elektra/modules/simplespeclang", KEY_VALUE,
					   "simplespeclang plugin waits for your orders", KEY_END),
			       keyNew ("system/elektra/modules/simplespeclang/exports", KEY_END),
			       keyNew ("system/elektra/modules/simplespeclang/exports/get", KEY_FUNC, elektraSimplespeclangGet, KEY_END),
			       keyNew ("system/elektra/modules/simplespeclang/exports/set", KEY_FUNC, elektraSimplespeclangSet, KEY_END),
			       keyNew ("system/elektra/modules/simplespeclang/exports/checkconf", KEY_FUNC,
				       elektraSimplespeclangCheckConfig, KEY_END),
#include ELEKTRA_README (simplespeclang)
			       keyNew ("system/elektra/modules/simplespeclang/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
		ksAppend (returned, contract);
		ksDel (contract);

		return 1; // success
	}

	std::ifstream ofs (keyString (parentKey), std::ios::binary);
	if (!ofs.is_open ())
	{
		ELEKTRA_SET_ERROR_GET (parentKey);
		return -1;
	}

	return unserialise (ofs, parentKey, returned, handle);
}

int elektraSimplespeclangSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
{
	ELEKTRA_LOG ("opening file %s", keyString (parentKey));
	std::ofstream ofs (keyString (parentKey), std::ios::binary);
	if (!ofs.is_open ())
	{
		ELEKTRA_SET_ERROR_SET (parentKey);
		return -1;
	}

	return serialise (ofs, parentKey, returned, handle);
}

int elektraSimplespeclangCheckConfig (Key * errorKey ELEKTRA_UNUSED, KeySet * conf ELEKTRA_UNUSED)
{
	return 0;
}

Plugin * ELEKTRA_PLUGIN_EXPORT (simplespeclang)
{
	// clang-format off
	return elektraPluginExport ("simplespeclang",
		ELEKTRA_PLUGIN_GET,	&elektraSimplespeclangGet,
		ELEKTRA_PLUGIN_SET,	&elektraSimplespeclangSet,
		ELEKTRA_PLUGIN_END);
}

}
<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/dock_info.h"

#include "base/basictypes.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
#include "chrome/browser/browser_window.h"
#include "chrome/browser/views/frame/browser_view.h"
#include "chrome/browser/views/tabs/tab.h"

namespace {

// BaseWindowFinder -----------------------------------------------------------

// Base class used to locate a window. This is intended to be used with the
// various win32 functions that iterate over windows.
//
// A subclass need only override ShouldStopIterating to determine when
// iteration should stop.
class BaseWindowFinder {
 public:
  // Creates a BaseWindowFinder with the specified set of HWNDs to ignore.
  explicit BaseWindowFinder(const std::set<HWND>& ignore) : ignore_(ignore) {}
  virtual ~BaseWindowFinder() {}

 protected:
  // Returns true if iteration should stop, false if iteration should continue.
  virtual bool ShouldStopIterating(HWND window) = 0;

  static BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) {
    BaseWindowFinder* finder = reinterpret_cast<BaseWindowFinder*>(lParam);
    if (finder->ignore_.find(hwnd) != finder->ignore_.end())
      return TRUE;

    return finder->ShouldStopIterating(hwnd) ? FALSE : TRUE;
  }

 private:
  const std::set<HWND>& ignore_;

  DISALLOW_COPY_AND_ASSIGN(BaseWindowFinder);
};

// TopMostFinder --------------------------------------------------------------

// Helper class to determine if a particular point of a window is not obscured
// by another window.
class TopMostFinder : public BaseWindowFinder {
 public:
  // Returns true if |window| is the topmost window at the location
  // |screen_loc|, not including the windows in |ignore|.
  static bool IsTopMostWindowAtPoint(HWND window,
                                     const gfx::Point& screen_loc,
                                     const std::set<HWND>& ignore) {
    TopMostFinder finder(window, screen_loc, ignore);
    return finder.is_top_most_;
  }

  virtual bool ShouldStopIterating(HWND hwnd) {
    if (hwnd == target_) {
      // Window is topmost, stop iterating.
      is_top_most_ = true;
      return true;
    }

    if (!IsWindowVisible(hwnd)) {
      // The window isn't visible, keep iterating.
      return false;
    }

    RECT r;
    if (!GetWindowRect(hwnd, &r) || !PtInRect(&r, screen_loc_.ToPOINT())) {
      // The window doesn't contain the point, keep iterating.
      return false;
    }

    // hwnd is at the point. Make sure the point is within the windows region.
    if (GetWindowRgn(hwnd, tmp_region_.Get()) == ERROR) {
      // There's no region on the window and the window contains the point. Stop
      // iterating.
      return true;
    }

    // The region is relative to the window's rect.
    BOOL is_point_in_region = PtInRegion(tmp_region_.Get(),
        screen_loc_.x() - r.left, screen_loc_.y() - r.top);
    tmp_region_ = CreateRectRgn(0, 0, 0, 0);
    // Stop iterating if the region contains the point.
    return !!is_point_in_region;
  }

 private:
  TopMostFinder(HWND window,
                const gfx::Point& screen_loc,
                const std::set<HWND>& ignore)
      : BaseWindowFinder(ignore),
        target_(window),
        screen_loc_(screen_loc),
        is_top_most_(false),
        tmp_region_(CreateRectRgn(0, 0, 0, 0)) {
    EnumWindows(WindowCallbackProc, reinterpret_cast<LPARAM>(this));
  }

  // The window we're looking for.
  HWND target_;

  // Location of window to find.
  gfx::Point screen_loc_;

  // Is target_ the top most window? This is initially false but set to true
  // in ShouldStopIterating if target_ is passed in.
  bool is_top_most_;

  ScopedRegion tmp_region_;

  DISALLOW_COPY_AND_ASSIGN(TopMostFinder);
};

// WindowFinder ---------------------------------------------------------------

// Helper class to determine if a particular point contains a window from our
// process.
class LocalProcessWindowFinder : public BaseWindowFinder {
 public:
  // Returns the hwnd from our process at screen_loc that is not obscured by
  // another window. Returns NULL otherwise.
  static HWND GetProcessWindowAtPoint(const gfx::Point& screen_loc,
                                      const std::set<HWND>& ignore) {
    LocalProcessWindowFinder finder(screen_loc, ignore);
    if (finder.result_ &&
        TopMostFinder::IsTopMostWindowAtPoint(finder.result_, screen_loc,
                                              ignore)) {
      return finder.result_;
    }
    return NULL;
  }

 protected:
  virtual bool ShouldStopIterating(HWND hwnd) {
    RECT r;
    if (IsWindowVisible(hwnd) && GetWindowRect(hwnd, &r) &&
        PtInRect(&r, screen_loc_.ToPOINT())) {
      result_ = hwnd;
      return true;
    }
    return false;
  }

 private:
  LocalProcessWindowFinder(const gfx::Point& screen_loc,
                           const std::set<HWND>& ignore)
      : BaseWindowFinder(ignore),
        screen_loc_(screen_loc),
        result_(NULL) {
    EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc,
                      reinterpret_cast<LPARAM>(this));
  }

  // Position of the mouse.
  gfx::Point screen_loc_;

  // The resulting window. This is initially null but set to true in
  // ShouldStopIterating if an appropriate window is found.
  HWND result_;

  DISALLOW_COPY_AND_ASSIGN(LocalProcessWindowFinder);
};

// DockToWindowFinder ---------------------------------------------------------

// Helper class for creating a DockInfo from a specified point.
class DockToWindowFinder : public BaseWindowFinder {
 public:
  // Returns the DockInfo for the specified point. If there is no docking
  // position for the specified point the returned DockInfo has a type of NONE.
  static DockInfo GetDockInfoAtPoint(const gfx::Point& screen_loc,
                                     const std::set<HWND>& ignore) {
    DockToWindowFinder finder(screen_loc, ignore);
    if (!finder.result_.window() ||
        !TopMostFinder::IsTopMostWindowAtPoint(finder.result_.window(),
                                               finder.result_.hot_spot(),
                                               ignore)) {
      finder.result_.set_type(DockInfo::NONE);
    }
    return finder.result_;
  }

 protected:
  virtual bool ShouldStopIterating(HWND hwnd) {
    BrowserView* window = BrowserView::GetBrowserViewForNativeWindow(hwnd);
    RECT bounds;
    if (!window || !IsWindowVisible(hwnd) ||
        !GetWindowRect(hwnd, &bounds)) {
      return false;
    }

    // Check the three corners we allow docking to. We don't currently allow
    // docking to top of window as it conflicts with docking to the tab strip.
    if (CheckPoint(hwnd, bounds.left, (bounds.top + bounds.bottom) / 2,
                   DockInfo::LEFT_OF_WINDOW) ||
        CheckPoint(hwnd, bounds.right - 1, (bounds.top + bounds.bottom) / 2,
                   DockInfo::RIGHT_OF_WINDOW) ||
        CheckPoint(hwnd, (bounds.left + bounds.right) / 2, bounds.bottom - 1,
                   DockInfo::BOTTOM_OF_WINDOW)) {
      return true;
    }
    return false;
  }

 private:
  DockToWindowFinder(const gfx::Point& screen_loc,
                     const std::set<HWND>& ignore)
      : BaseWindowFinder(ignore),
        screen_loc_(screen_loc) {
    HMONITOR monitor = MonitorFromPoint(screen_loc.ToPOINT(),
                                        MONITOR_DEFAULTTONULL);
    MONITORINFO monitor_info = {0};
    monitor_info.cbSize = sizeof(MONITORINFO);
    if (monitor && GetMonitorInfo(monitor, &monitor_info)) {
      result_.set_monitor_bounds(gfx::Rect(monitor_info.rcWork));
      EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc,
                        reinterpret_cast<LPARAM>(this));
    }
  }

  bool CheckPoint(HWND hwnd, int x, int y, DockInfo::Type type) {
    bool in_enable_area;
    if (DockInfo::IsCloseToPoint(screen_loc_, x, y, &in_enable_area)) {
      result_.set_in_enable_area(in_enable_area);
      result_.set_window(hwnd);
      result_.set_type(type);
      result_.set_hot_spot(gfx::Point(x, y));
      // Only show the hotspot if the monitor contains the bounds of the popup
      // window. Otherwise we end with a weird situation where the popup window
      // isn't completely visible.
      return result_.monitor_bounds().Contains(result_.GetPopupRect());
    }
    return false;
  }

  // The location to look for.
  gfx::Point screen_loc_;

  // The resulting DockInfo.
  DockInfo result_;

  DISALLOW_COPY_AND_ASSIGN(DockToWindowFinder);
};

}  // namespace

// DockInfo -------------------------------------------------------------------

// static
DockInfo DockInfo::GetDockInfoAtPoint(const gfx::Point& screen_point,
                                      const std::set<HWND>& ignore) {
  if (factory_)
    return factory_->GetDockInfoAtPoint(screen_point, ignore);

  // Try docking to a window first.
  DockInfo info = DockToWindowFinder::GetDockInfoAtPoint(screen_point, ignore);
  if (info.type() != DockInfo::NONE)
    return info;

  // No window relative positions. Try monitor relative positions.
  const gfx::Rect& m_bounds = info.monitor_bounds();
  int mid_x = m_bounds.x() + m_bounds.width() / 2;
  int mid_y = m_bounds.y() + m_bounds.height() / 2;

  bool result =
      info.CheckMonitorPoint(screen_point, mid_x, m_bounds.y(),
                             DockInfo::MAXIMIZE) ||
      info.CheckMonitorPoint(screen_point, mid_x, m_bounds.bottom(),
                             DockInfo::BOTTOM_HALF) ||
      info.CheckMonitorPoint(screen_point, m_bounds.x(), mid_y,
                             DockInfo::LEFT_HALF) ||
      info.CheckMonitorPoint(screen_point, m_bounds.right(), mid_y,
                             DockInfo::RIGHT_HALF);

  return info;
}

HWND DockInfo::GetLocalProcessWindowAtPoint(const gfx::Point& screen_point,
                                            const std::set<HWND>& ignore) {
  if (factory_)
    return factory_->GetLocalProcessWindowAtPoint(screen_point, ignore);
  return
      LocalProcessWindowFinder::GetProcessWindowAtPoint(screen_point, ignore);
}

bool DockInfo::GetWindowBounds(gfx::Rect* bounds) const {
  RECT window_rect;
  if (!window() || !GetWindowRect(window(), &window_rect))
    return false;
  *bounds = gfx::Rect(window_rect);
  return true;
}

void DockInfo::SizeOtherWindowTo(const gfx::Rect& bounds) const {
  if (IsZoomed(window())) {
    // We're docking relative to another window, we need to make sure the
    // window we're docking to isn't maximized.
    ShowWindow(window(), SW_RESTORE | SW_SHOWNA);
  }
  SetWindowPos(window(), HWND_TOP, bounds.x(), bounds.y(), bounds.width(),
               bounds.height(), SWP_NOACTIVATE | SWP_NOOWNERZORDER);
}
<commit_msg>Changes tab dragging code to ignore WS_EX_TRANSPARENT and WS_EX_LAYERED windows. Here's the comment I'm adding as to why:<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/dock_info.h"

#include "base/basictypes.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
#include "chrome/browser/browser_window.h"
#include "chrome/browser/views/frame/browser_view.h"
#include "chrome/browser/views/tabs/tab.h"

namespace {

// BaseWindowFinder -----------------------------------------------------------

// Base class used to locate a window. This is intended to be used with the
// various win32 functions that iterate over windows.
//
// A subclass need only override ShouldStopIterating to determine when
// iteration should stop.
class BaseWindowFinder {
 public:
  // Creates a BaseWindowFinder with the specified set of HWNDs to ignore.
  explicit BaseWindowFinder(const std::set<HWND>& ignore) : ignore_(ignore) {}
  virtual ~BaseWindowFinder() {}

 protected:
  // Returns true if iteration should stop, false if iteration should continue.
  virtual bool ShouldStopIterating(HWND window) = 0;

  static BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) {
    BaseWindowFinder* finder = reinterpret_cast<BaseWindowFinder*>(lParam);
    if (finder->ignore_.find(hwnd) != finder->ignore_.end())
      return TRUE;

    return finder->ShouldStopIterating(hwnd) ? FALSE : TRUE;
  }

 private:
  const std::set<HWND>& ignore_;

  DISALLOW_COPY_AND_ASSIGN(BaseWindowFinder);
};

// TopMostFinder --------------------------------------------------------------

// Helper class to determine if a particular point of a window is not obscured
// by another window.
class TopMostFinder : public BaseWindowFinder {
 public:
  // Returns true if |window| is the topmost window at the location
  // |screen_loc|, not including the windows in |ignore|.
  static bool IsTopMostWindowAtPoint(HWND window,
                                     const gfx::Point& screen_loc,
                                     const std::set<HWND>& ignore) {
    TopMostFinder finder(window, screen_loc, ignore);
    return finder.is_top_most_;
  }

  virtual bool ShouldStopIterating(HWND hwnd) {
    if (hwnd == target_) {
      // Window is topmost, stop iterating.
      is_top_most_ = true;
      return true;
    }

    if (!IsWindowVisible(hwnd)) {
      // The window isn't visible, keep iterating.
      return false;
    }

    RECT r;
    if (!GetWindowRect(hwnd, &r) || !PtInRect(&r, screen_loc_.ToPOINT())) {
      // The window doesn't contain the point, keep iterating.
      return false;
    }

    LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE);
    if (ex_styles & WS_EX_TRANSPARENT || ex_styles & WS_EX_LAYERED) {
      // Mouse events fall through WS_EX_TRANSPARENT windows, so we ignore them.
      //
      // WS_EX_LAYERED is trickier. Apps like Switcher create a totally
      // transparent WS_EX_LAYERED window that is always on top. If we don't
      // ignore WS_EX_LAYERED windows and there are totally transparent
      // WS_EX_LAYERED windows then there are effectively holes on the screen
      // that the user can't reattach tabs to. So we ignore them. This is a bit
      // problematic in so far as WS_EX_LAYERED windows need not be totally
      // transparent in which case we treat chrome windows as not being obscured
      // when they really are, but this is better than not being able to
      // reattach tabs.
      return false;
    }

    // hwnd is at the point. Make sure the point is within the windows region.
    if (GetWindowRgn(hwnd, tmp_region_.Get()) == ERROR) {
      // There's no region on the window and the window contains the point. Stop
      // iterating.
      return true;
    }

    // The region is relative to the window's rect.
    BOOL is_point_in_region = PtInRegion(tmp_region_.Get(),
        screen_loc_.x() - r.left, screen_loc_.y() - r.top);
    tmp_region_ = CreateRectRgn(0, 0, 0, 0);
    // Stop iterating if the region contains the point.
    return !!is_point_in_region;
  }

 private:
  TopMostFinder(HWND window,
                const gfx::Point& screen_loc,
                const std::set<HWND>& ignore)
      : BaseWindowFinder(ignore),
        target_(window),
        screen_loc_(screen_loc),
        is_top_most_(false),
        tmp_region_(CreateRectRgn(0, 0, 0, 0)) {
    EnumWindows(WindowCallbackProc, reinterpret_cast<LPARAM>(this));
  }

  // The window we're looking for.
  HWND target_;

  // Location of window to find.
  gfx::Point screen_loc_;

  // Is target_ the top most window? This is initially false but set to true
  // in ShouldStopIterating if target_ is passed in.
  bool is_top_most_;

  ScopedRegion tmp_region_;

  DISALLOW_COPY_AND_ASSIGN(TopMostFinder);
};

// WindowFinder ---------------------------------------------------------------

// Helper class to determine if a particular point contains a window from our
// process.
class LocalProcessWindowFinder : public BaseWindowFinder {
 public:
  // Returns the hwnd from our process at screen_loc that is not obscured by
  // another window. Returns NULL otherwise.
  static HWND GetProcessWindowAtPoint(const gfx::Point& screen_loc,
                                      const std::set<HWND>& ignore) {
    LocalProcessWindowFinder finder(screen_loc, ignore);
    if (finder.result_ &&
        TopMostFinder::IsTopMostWindowAtPoint(finder.result_, screen_loc,
                                              ignore)) {
      return finder.result_;
    }
    return NULL;
  }

 protected:
  virtual bool ShouldStopIterating(HWND hwnd) {
    RECT r;
    if (IsWindowVisible(hwnd) && GetWindowRect(hwnd, &r) &&
        PtInRect(&r, screen_loc_.ToPOINT())) {
      result_ = hwnd;
      return true;
    }
    return false;
  }

 private:
  LocalProcessWindowFinder(const gfx::Point& screen_loc,
                           const std::set<HWND>& ignore)
      : BaseWindowFinder(ignore),
        screen_loc_(screen_loc),
        result_(NULL) {
    EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc,
                      reinterpret_cast<LPARAM>(this));
  }

  // Position of the mouse.
  gfx::Point screen_loc_;

  // The resulting window. This is initially null but set to true in
  // ShouldStopIterating if an appropriate window is found.
  HWND result_;

  DISALLOW_COPY_AND_ASSIGN(LocalProcessWindowFinder);
};

// DockToWindowFinder ---------------------------------------------------------

// Helper class for creating a DockInfo from a specified point.
class DockToWindowFinder : public BaseWindowFinder {
 public:
  // Returns the DockInfo for the specified point. If there is no docking
  // position for the specified point the returned DockInfo has a type of NONE.
  static DockInfo GetDockInfoAtPoint(const gfx::Point& screen_loc,
                                     const std::set<HWND>& ignore) {
    DockToWindowFinder finder(screen_loc, ignore);
    if (!finder.result_.window() ||
        !TopMostFinder::IsTopMostWindowAtPoint(finder.result_.window(),
                                               finder.result_.hot_spot(),
                                               ignore)) {
      finder.result_.set_type(DockInfo::NONE);
    }
    return finder.result_;
  }

 protected:
  virtual bool ShouldStopIterating(HWND hwnd) {
    BrowserView* window = BrowserView::GetBrowserViewForNativeWindow(hwnd);
    RECT bounds;
    if (!window || !IsWindowVisible(hwnd) ||
        !GetWindowRect(hwnd, &bounds)) {
      return false;
    }

    // Check the three corners we allow docking to. We don't currently allow
    // docking to top of window as it conflicts with docking to the tab strip.
    if (CheckPoint(hwnd, bounds.left, (bounds.top + bounds.bottom) / 2,
                   DockInfo::LEFT_OF_WINDOW) ||
        CheckPoint(hwnd, bounds.right - 1, (bounds.top + bounds.bottom) / 2,
                   DockInfo::RIGHT_OF_WINDOW) ||
        CheckPoint(hwnd, (bounds.left + bounds.right) / 2, bounds.bottom - 1,
                   DockInfo::BOTTOM_OF_WINDOW)) {
      return true;
    }
    return false;
  }

 private:
  DockToWindowFinder(const gfx::Point& screen_loc,
                     const std::set<HWND>& ignore)
      : BaseWindowFinder(ignore),
        screen_loc_(screen_loc) {
    HMONITOR monitor = MonitorFromPoint(screen_loc.ToPOINT(),
                                        MONITOR_DEFAULTTONULL);
    MONITORINFO monitor_info = {0};
    monitor_info.cbSize = sizeof(MONITORINFO);
    if (monitor && GetMonitorInfo(monitor, &monitor_info)) {
      result_.set_monitor_bounds(gfx::Rect(monitor_info.rcWork));
      EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc,
                        reinterpret_cast<LPARAM>(this));
    }
  }

  bool CheckPoint(HWND hwnd, int x, int y, DockInfo::Type type) {
    bool in_enable_area;
    if (DockInfo::IsCloseToPoint(screen_loc_, x, y, &in_enable_area)) {
      result_.set_in_enable_area(in_enable_area);
      result_.set_window(hwnd);
      result_.set_type(type);
      result_.set_hot_spot(gfx::Point(x, y));
      // Only show the hotspot if the monitor contains the bounds of the popup
      // window. Otherwise we end with a weird situation where the popup window
      // isn't completely visible.
      return result_.monitor_bounds().Contains(result_.GetPopupRect());
    }
    return false;
  }

  // The location to look for.
  gfx::Point screen_loc_;

  // The resulting DockInfo.
  DockInfo result_;

  DISALLOW_COPY_AND_ASSIGN(DockToWindowFinder);
};

}  // namespace

// DockInfo -------------------------------------------------------------------

// static
DockInfo DockInfo::GetDockInfoAtPoint(const gfx::Point& screen_point,
                                      const std::set<HWND>& ignore) {
  if (factory_)
    return factory_->GetDockInfoAtPoint(screen_point, ignore);

  // Try docking to a window first.
  DockInfo info = DockToWindowFinder::GetDockInfoAtPoint(screen_point, ignore);
  if (info.type() != DockInfo::NONE)
    return info;

  // No window relative positions. Try monitor relative positions.
  const gfx::Rect& m_bounds = info.monitor_bounds();
  int mid_x = m_bounds.x() + m_bounds.width() / 2;
  int mid_y = m_bounds.y() + m_bounds.height() / 2;

  bool result =
      info.CheckMonitorPoint(screen_point, mid_x, m_bounds.y(),
                             DockInfo::MAXIMIZE) ||
      info.CheckMonitorPoint(screen_point, mid_x, m_bounds.bottom(),
                             DockInfo::BOTTOM_HALF) ||
      info.CheckMonitorPoint(screen_point, m_bounds.x(), mid_y,
                             DockInfo::LEFT_HALF) ||
      info.CheckMonitorPoint(screen_point, m_bounds.right(), mid_y,
                             DockInfo::RIGHT_HALF);

  return info;
}

HWND DockInfo::GetLocalProcessWindowAtPoint(const gfx::Point& screen_point,
                                            const std::set<HWND>& ignore) {
  if (factory_)
    return factory_->GetLocalProcessWindowAtPoint(screen_point, ignore);
  return
      LocalProcessWindowFinder::GetProcessWindowAtPoint(screen_point, ignore);
}

bool DockInfo::GetWindowBounds(gfx::Rect* bounds) const {
  RECT window_rect;
  if (!window() || !GetWindowRect(window(), &window_rect))
    return false;
  *bounds = gfx::Rect(window_rect);
  return true;
}

void DockInfo::SizeOtherWindowTo(const gfx::Rect& bounds) const {
  if (IsZoomed(window())) {
    // We're docking relative to another window, we need to make sure the
    // window we're docking to isn't maximized.
    ShowWindow(window(), SW_RESTORE | SW_SHOWNA);
  }
  SetWindowPos(window(), HWND_TOP, bounds.x(), bounds.y(), bounds.width(),
               bounds.height(), SWP_NOACTIVATE | SWP_NOOWNERZORDER);
}
<|endoftext|>