package nz.co.searchwellington;

import com.google.common.collect.Maps;
import nz.co.searchwellington.commentfeeds.detectors.CommentFeedDetector;
import nz.co.searchwellington.commentfeeds.detectors.DateRegexCommentFeedDetector;
import nz.co.searchwellington.commentfeeds.detectors.GenericCommentFeedDetector;
import nz.co.searchwellington.urls.RssUrlBuilder;
import nz.co.searchwellington.urls.AdminUrlBuilder;
import nz.co.searchwellington.model.SiteInformation;
import nz.co.searchwellington.model.UrlWordsGenerator;
import nz.co.searchwellington.urls.UrlBuilder;
import nz.co.searchwellington.views.*;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.spring.VelocityEngineFactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import uk.co.eelpieconsulting.common.caching.MemcachedCache;
import uk.co.eelpieconsulting.spring.views.velocity.VelocityViewResolver;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, MongoAutoConfiguration.class})
@EnableScheduling
@ComponentScan({"nz.co.searchwellington","uk.co.eelpieconsulting.common"})
@Configuration
public class Main {

    private static ApplicationContext ctx;

    public static void main(String[] args) {
        ctx = SpringApplication.run(Main.class, args);
    }

    @Bean
    public CommentFeedDetector newswireCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://www.newswire.co.nz/\\d{4}/\\d{2}/.*?/feed/$");
    }
    @Bean
    public CommentFeedDetector aucklandTrainsCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://www.aucklandtrains.co.nz/\\d{4}/\\d{2}/\\d{2}/.*?/feed/$");
    }

    @Bean
    public CommentFeedDetector tepapaBlogCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://blog.tepapa.govt.nz/\\d{4}/\\d{2}/\\d{2}/.*?/feed/$");
    }

    @Bean
    public CommentFeedDetector wellingtonistaCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://wellingtonista.com/crss/node/\\d+$");
    }

    @Bean
    public CommentFeedDetector yearMonthCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://.*?/\\d{4}/\\d{2}/.*?$");
    }
    @Bean
    public CommentFeedDetector dateRegexCommentFeedDetector() {
        return new DateRegexCommentFeedDetector();
    }

    @Bean("feedReaderTaskExecutor")
    public TaskExecutor feedReaderTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(5);
        threadPoolTaskExecutor.setMaxPoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(50000);
        return threadPoolTaskExecutor;
    }

    @Bean("linkCheckerTaskExecutor")
    public TaskExecutor linkCheckerTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(5);
        threadPoolTaskExecutor.setMaxPoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(50000);
        return threadPoolTaskExecutor;
    }

    @Bean
    public MemcachedCache memcachedCache(@Value("${memcached.urls}") String memcacheUrl) throws IOException {
        return new MemcachedCache(memcacheUrl);
    }

    @Bean
    public VelocityViewResolver velocityViewResolver(
            AdminUrlBuilder adminUrlBuilder,
            ColumnSplitter columnSplitter,
            DateFormatter dateFormatter,
            RssUrlBuilder rssUrlBuilder,
            SiteInformation siteInformation,
            UrlBuilder urlBuilder,
            UrlWordsGenerator urlWordsGenerator,
            MapPinDeduplicator mapPinDeduplicator,
            DeduplicateImageService deduplicateImageService) {
        final VelocityViewResolver viewResolver = new VelocityViewResolver();
        viewResolver.setCache(true);
        viewResolver.setSuffix(".vm");
        viewResolver.setContentType("text/html;charset=UTF-8");
        final Map<String, Object> attributes = Maps.newHashMap();
        attributes.put("adminUrlBuilder", adminUrlBuilder);
        attributes.put("columnSplitter", columnSplitter);
        attributes.put("dateFormatter", dateFormatter);
        attributes.put("escape", new EscapeTools());
        attributes.put("rssUrlBuilder", rssUrlBuilder);
        attributes.put("siteInformation", siteInformation);
        attributes.put("urlBuilder", urlBuilder);
        attributes.put("urlWordsGenerator", urlWordsGenerator);
        attributes.put("mapPinDeduplicator", mapPinDeduplicator);
        attributes.put("deduplicateImageService", deduplicateImageService);
        viewResolver.setAttributesMap(attributes);
        return viewResolver;
    }

    @Bean("velocityEngine")
    public VelocityEngineFactoryBean velocityEngineFactoryBean() {
        VelocityEngineFactoryBean velocityEngineFactory= new VelocityEngineFactoryBean();
        Properties vp = new Properties();
        vp.setProperty(Velocity.INPUT_ENCODING, "UTF-8");
        vp.setProperty(Velocity.EVENTHANDLER_REFERENCEINSERTION, "org.apache.velocity.app.event.implement.EscapeHtmlReference");
        vp.setProperty("resource.loader", "class");
        vp.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

        vp.setProperty("resource.loader.class.cache", "true");
        // When resource.manager.cache.default_size is set to 0, then the default implementation uses the standard Java ConcurrentHashMap.
        vp.setProperty("resource.manager.cache.default_size", "0");

        vp.setProperty("velocimacro.library", "spring.vm");
        velocityEngineFactory.setVelocityProperties(vp);
        return velocityEngineFactory;
    }

}

package nz.co.searchwellington;

import com.google.common.collect.Maps;
import nz.co.searchwellington.commentfeeds.detectors.CommentFeedDetector;
import nz.co.searchwellington.commentfeeds.detectors.DateRegexCommentFeedDetector;
import nz.co.searchwellington.commentfeeds.detectors.GenericCommentFeedDetector;
import nz.co.searchwellington.urls.RssUrlBuilder;
import nz.co.searchwellington.urls.AdminUrlBuilder;
import nz.co.searchwellington.model.SiteInformation;
import nz.co.searchwellington.model.UrlWordsGenerator;
import nz.co.searchwellington.urls.UrlBuilder;
import nz.co.searchwellington.views.*;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.spring.VelocityEngineFactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import uk.co.eelpieconsulting.common.caching.MemcachedCache;
import uk.co.eelpieconsulting.spring.views.velocity.VelocityViewResolver;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, MongoAutoConfiguration.class})
@EnableScheduling
@ComponentScan({"nz.co.searchwellington","uk.co.eelpieconsulting.common"})
@Configuration
public class Main {

    private static ApplicationContext ctx;

    public static void main(String[] args) {
        ctx = SpringApplication.run(Main.class, args);
    }

    @Bean
    public CommentFeedDetector newswireCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://www.newswire.co.nz/\\d{4}/\\d{2}/.*?/feed/$");
    }
    @Bean
    public CommentFeedDetector aucklandTrainsCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://www.aucklandtrains.co.nz/\\d{4}/\\d{2}/\\d{2}/.*?/feed/$");
    }

    @Bean
    public CommentFeedDetector tepapaBlogCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://blog.tepapa.govt.nz/\\d{4}/\\d{2}/\\d{2}/.*?/feed/$");
    }

    @Bean
    public CommentFeedDetector wellingtonistaCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://wellingtonista.com/crss/node/\\d+$");
    }

    @Bean
    public CommentFeedDetector yearMonthCommentFeedDetector() {
        return new GenericCommentFeedDetector("^http://.*?/\\d{4}/\\d{2}/.*?$");
    }
    @Bean
    public CommentFeedDetector dateRegexCommentFeedDetector() {
        return new DateRegexCommentFeedDetector();
    }

    @Bean("elasticIndexTaskExecutor")
    public TaskExecutor elasticIndexTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(1);
        threadPoolTaskExecutor.setMaxPoolSize(2);
        threadPoolTaskExecutor.setQueueCapacity(50000);
        return threadPoolTaskExecutor;
    }

    @Bean("feedReaderTaskExecutor")
    public TaskExecutor feedReaderTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(5);
        threadPoolTaskExecutor.setMaxPoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(50000);
        return threadPoolTaskExecutor;
    }

    @Bean("linkCheckerTaskExecutor")
    public TaskExecutor linkCheckerTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(5);
        threadPoolTaskExecutor.setMaxPoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(50000);
        return threadPoolTaskExecutor;
    }

    @Bean
    public MemcachedCache memcachedCache(@Value("${memcached.urls}") String memcacheUrl) throws IOException {
        return new MemcachedCache(memcacheUrl);
    }

    @Bean
    public VelocityViewResolver velocityViewResolver(
            AdminUrlBuilder adminUrlBuilder,
            ColumnSplitter columnSplitter,
            DateFormatter dateFormatter,
            RssUrlBuilder rssUrlBuilder,
            SiteInformation siteInformation,
            UrlBuilder urlBuilder,
            UrlWordsGenerator urlWordsGenerator,
            MapPinDeduplicator mapPinDeduplicator,
            DeduplicateImageService deduplicateImageService) {
        final VelocityViewResolver viewResolver = new VelocityViewResolver();
        viewResolver.setCache(true);
        viewResolver.setSuffix(".vm");
        viewResolver.setContentType("text/html;charset=UTF-8");
        final Map<String, Object> attributes = Maps.newHashMap();
        attributes.put("adminUrlBuilder", adminUrlBuilder);
        attributes.put("columnSplitter", columnSplitter);
        attributes.put("dateFormatter", dateFormatter);
        attributes.put("escape", new EscapeTools());
        attributes.put("rssUrlBuilder", rssUrlBuilder);
        attributes.put("siteInformation", siteInformation);
        attributes.put("urlBuilder", urlBuilder);
        attributes.put("urlWordsGenerator", urlWordsGenerator);
        attributes.put("mapPinDeduplicator", mapPinDeduplicator);
        attributes.put("deduplicateImageService", deduplicateImageService);
        viewResolver.setAttributesMap(attributes);
        return viewResolver;
    }

    @Bean("velocityEngine")
    public VelocityEngineFactoryBean velocityEngineFactoryBean() {
        VelocityEngineFactoryBean velocityEngineFactory= new VelocityEngineFactoryBean();
        Properties vp = new Properties();
        vp.setProperty(Velocity.INPUT_ENCODING, "UTF-8");
        vp.setProperty(Velocity.EVENTHANDLER_REFERENCEINSERTION, "org.apache.velocity.app.event.implement.EscapeHtmlReference");
        vp.setProperty("resource.loader", "class");
        vp.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

        vp.setProperty("resource.loader.class.cache", "true");
        // When resource.manager.cache.default_size is set to 0, then the default implementation uses the standard Java ConcurrentHashMap.
        vp.setProperty("resource.manager.cache.default_size", "0");

        vp.setProperty("velocimacro.library", "spring.vm");
        velocityEngineFactory.setVelocityProperties(vp);
        return velocityEngineFactory;
    }

}


require 'sinatra/base'

module Sinatra
  module Warden
    module Helpers

      # The main accessor to the warden middleware
      def warden
        request.env['warden']
      end

      # Return session info
      #
      # @param [Symbol] the scope to retrieve session info for
      def session_info(scope=nil)
        scope ? warden.session(scope) : scope
      end

      # Check the current session is authenticated to a given scope
      def authenticated?(scope=nil)
        scope ? warden.authenticated?(scope) : warden.authenticated?
      end
      alias_method :logged_in?, :authenticated?

      # Authenticate a user against defined strategies
      def authenticate(*args)
        warden.authenticate!(*args)
      end
      alias_method :login, :authenticate

      # Terminate the current session
      #
      # @param [Symbol] the session scope to terminate
      def logout(scopes=nil)
        scopes ? warden.logout(scopes) : warden.logout(warden.config.default_scope)
      end

      # Access the user from the current session
      #
      # @param [Symbol] the scope for the logged in user
      def user(scope=nil)
        scope ? warden.user(scope) : warden.user
      end
      alias_method :current_user, :user

      # Store the logged in user in the session
      #
      # @param [Object] the user you want to store in the session
      # @option opts [Symbol] :scope The scope to assign the user
      # @example Set John as the current user
      #   user = User.find_by_name('John')
      def user=(new_user, opts={})
        warden.set_user(new_user, opts)
      end
      alias_method :current_user=, :user=

      # Require authorization for an action
      #
      # @param [String] path to redirect to if user is unauthenticated
      def authorize!(failure_path=nil)
        unless authenticated?
          session[:return_to] = request.path if options.auth_use_referrer
          redirect(failure_path ? failure_path : options.auth_failure_path)
        end
      end

    end

    def self.registered(app)
      app.helpers Warden::Helpers

      # Enable Sessions
      app.set :sessions, true

      app.set :auth_failure_path, '/'
      app.set :auth_success_path, '/'
      # Setting this to true will store last request URL
      # into a user's session so that to redirect back to it
      # upon successful authentication
      app.set :auth_use_referrer, false

      app.set :auth_error_message,   "Could not log you in."
      app.set :auth_success_message, "You have logged in successfully."
      app.set :auth_template_renderer, :haml
      app.set :auth_login_template, :login

      # OAuth Specific Settings
      app.set :auth_use_oauth, false

      app.post '/unauthenticated/?' do
        status 401
        warden.custom_failure! if warden.config.failure_app == self.class
        env['x-rack.flash'][:error] = options.auth_error_message if defined?(Rack::Flash)
        self.send(options.auth_template_renderer, options.auth_login_template)
      end

      app.get '/login/?' do
        if options.auth_use_oauth && !@auth_oauth_request_token.nil?
          session[:request_token] = @auth_oauth_request_token.token
          session[:request_token_secret] = @auth_oauth_request_token.secret
          redirect @auth_oauth_request_token.authorize_url
        else
          self.send(options.auth_template_renderer, options.auth_login_template)
        end
      end

      app.get '/oauth_callback/?' do
        if options.auth_use_oauth
          authenticate
          env['x-rack.flash'][:success] = options.auth_success_message if defined?(Rack::Flash)
          redirect options.auth_success_path
        else
          redirect options.auth_failure_path
        end
      end

      app.post '/login/?' do
        authenticate
        env['x-rack.flash'][:success] = options.auth_success_message if defined?(Rack::Flash)
        redirect options.auth_use_referrer && session[:return_to] ? session.delete(:return_to) :
                 options.auth_success_path
      end

      app.get '/logout/?' do
        authorize!
        logout
        env['x-rack.flash'][:success] = options.auth_success_message if defined?(Rack::Flash)
        redirect options.auth_success_path
      end
    end
  end # Warden

  register Warden
end # Sinatra

require 'sinatra/base'

module Sinatra
  module Warden
    module Helpers

      # The main accessor to the warden middleware
      def warden
        request.env['warden']
      end

      # Return session info
      #
      # @param [Symbol] the scope to retrieve session info for
      def session_info(scope=nil)
        scope ? warden.session(scope) : scope
      end

      # Check the current session is authenticated to a given scope
      def authenticated?(scope=nil)
        scope ? warden.authenticated?(scope) : warden.authenticated?
      end
      alias_method :logged_in?, :authenticated?

      # Authenticate a user against defined strategies
      def authenticate(*args)
        warden.authenticate!(*args)
      end
      alias_method :login, :authenticate

      # Terminate the current session
      #
      # @param [Symbol] the session scope to terminate
      def logout(scopes=nil)
        scopes ? warden.logout(scopes) : warden.logout(warden.config.default_scope)
      end

      # Access the user from the current session
      #
      # @param [Symbol] the scope for the logged in user
      def user(scope=nil)
        scope ? warden.user(scope) : warden.user
      end
      alias_method :current_user, :user

      # Store the logged in user in the session
      #
      # @param [Object] the user you want to store in the session
      # @option opts [Symbol] :scope The scope to assign the user
      # @example Set John as the current user
      #   user = User.find_by_name('John')
      def user=(new_user, opts={})
        warden.set_user(new_user, opts)
      end
      alias_method :current_user=, :user=

      # Require authorization for an action
      #
      # @param [String] path to redirect to if user is unauthenticated
      def authorize!(failure_path=nil)
        unless authenticated?
          session[:return_to] = request.path if settings.auth_use_referrer
          redirect(failure_path ? failure_path : settings.auth_failure_path)
        end
      end

    end

    def self.registered(app)
      app.helpers Warden::Helpers

      # Enable Sessions
      app.set :sessions, true

      app.set :auth_failure_path, '/'
      app.set :auth_success_path, '/'
      # Setting this to true will store last request URL
      # into a user's session so that to redirect back to it
      # upon successful authentication
      app.set :auth_use_referrer, false

      app.set :auth_error_message,   "Could not log you in."
      app.set :auth_success_message, "You have logged in successfully."
      app.set :auth_template_renderer, :haml
      app.set :auth_login_template, :login

      # OAuth Specific Settings
      app.set :auth_use_oauth, false

      app.post '/unauthenticated/?' do
        status 401
        warden.custom_failure! if warden.config.failure_app == self.class
        env['x-rack.flash'][:error] = settings.auth_error_message if defined?(Rack::Flash)
        self.send(settings.auth_template_renderer, settings.auth_login_template)
      end

      app.get '/login/?' do
        if settings.auth_use_oauth && !@auth_oauth_request_token.nil?
          session[:request_token] = @auth_oauth_request_token.token
          session[:request_token_secret] = @auth_oauth_request_token.secret
          redirect @auth_oauth_request_token.authorize_url
        else
          self.send(settings.auth_template_renderer, settings.auth_login_template)
        end
      end

      app.get '/oauth_callback/?' do
        if settings.auth_use_oauth
          authenticate
          env['x-rack.flash'][:success] = settings.auth_success_message if defined?(Rack::Flash)
          redirect settings.auth_success_path
        else
          redirect settings.auth_failure_path
        end
      end

      app.post '/login/?' do
        authenticate
        env['x-rack.flash'][:success] = settings.auth_success_message if defined?(Rack::Flash)
        redirect settings.auth_use_referrer && session[:return_to] ? session.delete(:return_to) :
                 settings.auth_success_path
      end

      app.get '/logout/?' do
        authorize!
        logout
        env['x-rack.flash'][:success] = settings.auth_success_message if defined?(Rack::Flash)
        redirect settings.auth_success_path
      end
    end
  end # Warden

  register Warden
end # Sinatra


#!/bin/bash
#
#    Script to backup a Zimbra installation (open source version)
#    by installing the Zimbra on a separate LVM Logical Volume,
#    taking a snapshot of that partition after stopping Zimbra,
#    restarting Zimbra services, then rsyncing the snapshot to a
#    separate backup point.

#    This script was originally based on a script found on the Zimbra wiki
#    http://wiki.zimbra.com/index.php?title=Open_Source_Edition_Backup_Procedure
#    and totally rewritten since then.

#    Copyright (C) 2007 Serge van Ginderachter <svg@ginsys.be>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License version 2 as
#    published by the Free Software Foundation.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#    Or download it from http://www.gnu.org/licenses/old-licenses/gpl-2.0.html

####################################################################################

# Read config
source backup_zimbra_config
# zm_backup_path=/opt.bak
# zm_lv=opt
# zm_vg=data
# zm_path=
# zm_lv_fs=ext3
# LVCREATE=/sbin/lvcreate
# LVREMOVE=/sbin/lvremove
# zm_snapshot=opt-snapshot
# zm_snapshot_size=1GB
# zm_snapshot_path=/tmp/opt-snapshot
# V=
# debug=

##########################################
# Do not change anything beyond this point
##########################################

pause() {
        if [ -n "$debug" ]; then
        echo "Press Enter to execute this step..";
        read input;
        fi
        }

say() {
	MESSAGE_PREFIX="zimbra backup:"
	MESSAGE="$1"
	TIMESTAMP=$(date +"%F %T")
	echo -e "$TIMESTAMP $MESSAGE_PREFIX $MESSAGE"
	logger -t $log_tag -p $log_facility.$log_level "$MESSAGE"
	logger -t $log_tag -p $log_facility_mail.$log_level "$MESSAGE"
	pause
	}

error ()  {
	MESSAGE_PREFIX="zimbra backup:"
        MESSAGE="$1"
	TIMESTAMP=$(date +"%F %T")
	echo -e $TIMESTAMP $MESSAGE >&2
	logger -t $log_tag -p $log_facility.$log_level_err "$MESSAGE"
	logger -t $log_tag -p $log_facility_mail.$log_level_err "$MESSAGE"
	exit
	}

# load kernel module to enable LVM snapshots
/sbin/modprobe dm-snapshot || error "Error loading dm-snapshot module"

# Output date
say "backup started"

# Stop the Zimbra services
say "stopping the Zimbra services, this may take some time"
/etc/init.d/zimbra stop || error "error stopping Zimbra"
[ "$(ps -u zimbra -o "pid=")" ] && kill -9 $(ps -u zimbra -o "pid=") #added as a workaround to zimbra bug 18653

# Create a logical volume called ZimbraBackup
say "creating a LV called $zm_snapshot"
$LVCREATE -L $zm_snapshot_size -s -n $zm_snapshot /dev/$zm_vg/$zm_lv  || error "error creating snapshot, exiting"

# Start the Zimbra services
say "starting the Zimbra services in the background....."
(/etc/init.d/zimbra start && say "services background startup completed") || error "services background startup FAILED" &

# Create a mountpoint to mount the logical volume to
say "creating mountpoint for the LV"
mkdir -p $zm_snapshot_path || error "error creating snapshot mount point $zm_snapshot_path"

# Mount the logical volume snapshot to the mountpoint
say "mounting the snapshot $zm_snapshot"
mount -t $zm_lv_fs -o nouuid,ro /dev/$zm_vg/$zm_snapshot $zm_snapshot_path

# Create the current backup
say "rsyncing the snapshot to the backup directory $backup_dir"
rsync -aAH$V --delete $zm_snapshot_path/$zm_path $zm_backup_path || say "error during rsync but continuing the backup script"

# Unmount $zm_snapshot from $zm_snapshot_mnt
say "unmounting the snapshot"
umount $zm_snapshot_path || error "error unmounting snapshot"

# Delete the snapshot mount dir
rmdir $zm_snapshot_path

# Remove the snapshot volume
# https://bugs.launchpad.net/ubuntu/+source/linux-source-2.6.15/+bug/71567
say "pausing 1s and syncing before removing the snapshot from LVM"
sleep 1 ; sync
say "removing the snapshot"
$LVREMOVE --force /dev/$zm_vg/$zm_snapshot  || say "error removing the snapshot"

# Done!
say "backup ended"
date >$zm_backup_path/lastsync

#!/bin/bash
#
#    Script to backup a Zimbra installation (open source version)
#    by installing the Zimbra on a separate LVM Logical Volume,
#    taking a snapshot of that partition after stopping Zimbra,
#    restarting Zimbra services, then rsyncing the snapshot to a
#    separate backup point.

#    This script was originally based on a script found on the Zimbra wiki
#    http://wiki.zimbra.com/index.php?title=Open_Source_Edition_Backup_Procedure
#    and totally rewritten since then.

#    Copyright (C) 2007 Serge van Ginderachter <svg@ginsys.be>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License version 2 as
#    published by the Free Software Foundation.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#    Or download it from http://www.gnu.org/licenses/old-licenses/gpl-2.0.html

####################################################################################

# Read config
source backup_zimbra_config
# zm_backup_path=/opt.bak
# zm_lv=opt
# zm_lv_mount_point=
# zm_vg=data
# zm_path=
# zm_lv_fs=auto
# zm_mount_opts=ro
# LVCREATE=/sbin/lvcreate
# LVREMOVE=/sbin/lvremove
# zm_snapshot=opt-snapshot
# zm_snapshot_size=1G
# zm_snapshot_extents=
# zm_snapshot_path=/tmp/opt-snapshot
# backup_util=rsync
# obnam_tune="--lru-size=1024 --upload-queue-size=512"
# obnam_keep_policy=14d,8w,12m
# V=
# debug=

##########################################
# Do not change anything beyond this point
##########################################

pause() {
        if [ -n "$debug" ]; then
        echo "Press Enter to execute this step..";
        read input;
        fi
        }

say() {
	MESSAGE_PREFIX="zimbra backup:"
	MESSAGE="$1"
	TIMESTAMP=$(date +"%F %T")
	echo -e "$TIMESTAMP $MESSAGE_PREFIX $MESSAGE"
	logger -t $log_tag -p $log_facility.$log_level "$MESSAGE"
	logger -t $log_tag -p $log_facility_mail.$log_level "$MESSAGE"
	pause
	}

error ()  {
	MESSAGE_PREFIX="zimbra backup:"
        MESSAGE="$1"
	TIMESTAMP=$(date +"%F %T")
	echo -e $TIMESTAMP $MESSAGE >&2
	logger -t $log_tag -p $log_facility.$log_level_err "$MESSAGE"
	logger -t $log_tag -p $log_facility_mail.$log_level_err "$MESSAGE"
	exit 1
	}

# Check for sane lv settings
if [[ $zm_snapshot_size && $zm_snapshot_extents ]]; then
	error "cannot specify both byte size ($zm_snapshot_size) and number of extents ($zm_snapshot_extents) for snapshot; please set only one or the other"
fi

# Output date
say "backup started"

# Stop the Zimbra services
say "stopping the Zimbra services, this may take some time"
/etc/init.d/zimbra stop || error "error stopping Zimbra"
[ "$(ps -u zimbra -o "pid=")" ] && kill -9 $(ps -u zimbra -o "pid=") #added as a workaround to zimbra bug 18653

# Unmount volume to ensure clean filesystem for snapshot
if [[ $zm_lv_mount_point ]]; then
	say "unmounting $zm_lv_mount_point"
	umount $zm_lv_mount_point || error "unable to unmount $zm_lv_mount_point"
fi

# Create a logical volume called ZimbraBackup
say "creating a LV called $zm_snapshot"
if [[ $zm_snapshot_size ]]; then
	lv_size="-L $zm_snapshot_size"
else
	lv_size="-l $zm_snapshot_extents"
fi
$LVCREATE $lv_size -s -n $zm_snapshot /dev/$zm_vg/$zm_lv  || error "error creating snapshot, exiting"

# Remount original volume
if [[ $zm_lv_mount_point ]]; then
        say "re-mounting $zm_lv_mount_point"
        mount $zm_lv_mount_point || error "unable to re-mount $zm_lv_mount_point"
fi

# Start the Zimbra services
say "starting the Zimbra services in the background....."
(/etc/init.d/zimbra start && say "services background startup completed") || error "services background startup FAILED" &

# zmconfigd in Zimbra 8.6 seems to have a hard time getting going during heavy I/O; let's give it time to start up before the backup begins
sleep 120

# Create a mountpoint to mount the logical volume to
say "creating mountpoint for the LV"
mkdir -p $zm_snapshot_path || error "error creating snapshot mount point $zm_snapshot_path"

# Mount the logical volume snapshot to the mountpoint
say "mounting the snapshot $zm_snapshot"
mount -t $zm_lv_fs -o $zm_mount_opts /dev/$zm_vg/$zm_snapshot $zm_snapshot_path

# Create the current backup using the configured tool
case $backup_util in
	rsync)
		# Use rsync
		say "rsyncing the snapshot to the backup directory $zm_backup_path"
		rsync -aAHS$V --delete $zm_snapshot_path/$zm_path $zm_backup_path || say "error during rsync but continuing the backup script"
		;;
	obnam)
		# Use obnam
		say "backing up via obnam to the backup directory $zm_backup_path"
		[[ $V = "v" ]] && verbose="--verbose"
		obnam backup $verbose $obnam_tune --repository $zm_backup_path $zm_snapshot_path/$zm_path || error "error creating obnam backup"
		if [[ $obnam_keep_policy ]]; then
			say "forgetting old obnam backups according to policy: $obnam_keep_policy"
			obnam forget $verbose --repository $zm_backup_path --keep $obnam_keep_policy || error "error forgetting obnam backups"
		fi
		;;
esac

# Unmount $zm_snapshot from $zm_snapshot_mnt
say "unmounting the snapshot"
umount $zm_snapshot_path || error "error unmounting snapshot"

# Delete the snapshot mount dir
rmdir $zm_snapshot_path

# Remove the snapshot volume
# https://bugs.launchpad.net/ubuntu/+source/linux-source-2.6.15/+bug/71567
say "pausing 1s and syncing before removing the snapshot from LVM"
sleep 1 ; sync
say "removing the snapshot"
$LVREMOVE --force /dev/$zm_vg/$zm_snapshot  || say "error removing the snapshot"

# Done!
say "backup ended"
date >$zm_backup_path/lastsync


# frozen_string_literal: true

require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
require 'app'

class AppTest < Test::Unit::TestCase
  context "Access pages" do
    should "show index" do
      get '/'
      assert_match 'Next Holiday', last_response.body
    end
  end
end


module EmailVeracity
  class Domain

    include Validatability

    def self.whitelisted?(name)
      Config[:whitelist].include?(name.downcase.strip)
    end

    def self.blacklisted?(name)
      Config[:blacklist].include?(name.downcase.strip)
    end

    def initialize(name = '')
      @name = name
    end

    def to_s
      name
    end

    def name
      @name.to_s.downcase.strip
    end

    def whitelisted?
      Domain.whitelisted?(name)
    end

    def blacklisted?
      Domain.blacklisted?(name)
    end

    def address_servers
      @address_servers ||= servers_in(:a)
    end

    def exchange_servers
      @exchange_servers ||= servers_in(:mx)
    end

    def servers
      address_servers + exchange_servers
    end

    protected

    def validate!
      return if whitelisted?
      add_error(:blacklisted) if blacklisted? &&
        Config[:enforce_blacklist]
      unless Config[:skip_lookup]
        add_error(:no_records) if servers.empty? &&
          !Config.enforced_record?(:a) &&
          !Config.enforced_record?(:mx)
        add_error(:no_address_servers) if address_servers.empty? &&
          Config.enforced_record?(:a)
        add_error(:no_exchange_servers) if exchange_servers.empty? &&
          Config.enforced_record?(:mx)
      end
    end

    def servers_in(record)
      return [] if Config[:skip_lookup] || Utils.blank?(name)
      Resolver.get_servers_for(name, record)
     rescue DomainResourcesTimeoutError
      add_error :timed_out
    end

  end
end

module EmailVeracity
  class Domain

    include Validatability

    def self.whitelisted?(name)
      Config[:whitelist].include?(name.downcase.strip)
    end

    def self.blacklisted?(name)
      Config[:blacklist].include?(name.downcase.strip)
    end

    def initialize(name = '')
      @name = name
    end

    def to_s
      name
    end

    def name
      @name.to_s.downcase.strip
    end

    def whitelisted?
      Domain.whitelisted?(name)
    end

    def blacklisted?
      Domain.blacklisted?(name)
    end

    def address_servers
      @address_servers ||= servers_in(:a)
    end

    def exchange_servers
      @exchange_servers ||= servers_in(:mx)
    end

    def servers
      address_servers + exchange_servers
    end

    protected

    def validate!
      return if whitelisted?
      add_error(:blacklisted) if blacklisted? &&
        Config[:enforce_blacklist]
      unless Config[:skip_lookup]
        add_error(:no_records) if !Config.enforced_record?(:a) &&
          !Config.enforced_record?(:mx) &&
          servers.empty?
        add_error(:no_address_servers) if Config.enforced_record?(:a) &&
          address_servers.empty?
        add_error(:no_exchange_servers) if Config.enforced_record?(:mx) &&
          exchange_servers.empty?
      end
    end

    def servers_in(record)
      return [] if Config[:skip_lookup] || Utils.blank?(name)
      Resolver.get_servers_for(name, record)
     rescue DomainResourcesTimeoutError
      add_error :timed_out
    end

  end
end


import array
import time
import re

import bpy
from ..xplane_config import getDebug
from ..xplane_helpers import floatToStr, logger
from ..xplane_constants import *
from .xplane_face import XPlaneFace

# Class: XPlaneMesh
# Creates the OBJ meshes.
class XPlaneMesh():
    # Constructor: __init__
    def __init__(self):
        # list - contains all mesh vertices
        self.vertices = []
        # array - contains all face indices
        self.indices = array.array('i')
        self.faces = []
        # int - Stores the current global vertex index.
        self.globalindex = 0
        self.debug = []

    # Method: collectXPlaneObjects
    # Fills the <vertices> and <indices> from a list of <XPlaneObjects>.
    # This method works recursively on the children of each <XPlaneObject>.
    #
    # Parameters:
    #   list xplaneObjects - list of <XPlaneObjects>.
    def collectXPlaneObjects(self, xplaneObjects):

        # start = time.perf_counter()

        debug = getDebug()
        supports_split_normals = False

        def getSortKey(xplaneObject):
            return xplaneObject.name

        # sort objects by name for consitent vertex and indices table output
        # this is usefull for unit tests and version control, as file changes are kept at a minimum
        xplaneObjects = sorted(xplaneObjects, key = getSortKey)

        for xplaneObject in xplaneObjects:
            # skip non-mesh objects and objects that do not have a xplane bone
            if xplaneObject.type == 'MESH' and xplaneObject.xplaneBone:
                xplaneObject.indices[0] = len(self.indices)
                first_vertice_of_this_xplaneObject = len(self.vertices)

                # create a copy of the xplaneObject mesh with modifiers applied and triangulated
                mesh = xplaneObject.blenderObject.to_mesh(bpy.context.scene, True, "PREVIEW")

                # now get the bake matrix
                # and bake it to the mesh
                xplaneObject.bakeMatrix = xplaneObject.xplaneBone.getBakeMatrixForAttached()
                mesh.transform(xplaneObject.bakeMatrix)

                if hasattr(mesh, 'calc_normals_split'): # split normals
                    mesh.calc_normals_split()
                    supports_split_normals = True

                if hasattr(mesh, 'polygons'): # BMesh
                    mesh.update(calc_tessface = True)
                    mesh.calc_tessface()
                    mesh_faces = mesh.tessfaces
                else:
                    mesh_faces = mesh.faces

                # with the new mesh get uvFaces list
                uvFaces = self.getUVFaces(mesh, xplaneObject.material.uv_name)

                faces = []
                vertices_dct = {}

                d = {'name': xplaneObject.name,'obj_face': 0,'faces': len(mesh_faces),'quads': 0,'vertices': len(mesh.vertices),'uvs': 0}

                # convert faces to triangles
                if len(mesh_faces) > 0:
                    tempfaces = []

                    for i in range(0, len(mesh_faces)):
                        if uvFaces != None:
                            f = self.faceToTrianglesWithUV(mesh_faces[i], uvFaces[i])
                            tempfaces.extend(f)

                            d['uvs'] += 1

                            if len(f) > 1:
                                d['quads'] += 1

                        else:
                            f = self.faceToTrianglesWithUV(mesh_faces[i], None)
                            tempfaces.extend(f)

                            if len(f) > 1:
                                d['quads']+=1

                    d['obj_faces'] = len(tempfaces)

                    for f in tempfaces:
                        xplaneFace = XPlaneFace()
                        l = len(f['indices'])

                        for i in range(0, l):
                            # get the original index but reverse order, as this is reversing normals
                            vindex = f['indices'][2 - i]

                            # get the vertice from original mesh
                            v = mesh.vertices[vindex]
                            co = v.co
                            ns = f['norms'][2 - i] if supports_split_normals else v.normal

                            if f['original_face'].use_smooth: # use smoothed vertex normal
                                vert = (
                                    co[0], co[2], -co[1],
                                    ns[0], ns[2], -ns[1],
                                    f['uv'][i][0], f['uv'][i][1]
                                )
                            else: # use flat face normal
                                vert = (
                                    co[0], co[2], -co[1],
                                    f['original_face'].normal[0], f['original_face'].normal[2], -f['original_face'].normal[1],
                                    f['uv'][i][0], f['uv'][i][1]
                                )

                            if bpy.context.scene.xplane.optimize:
                                #check for duplicates
                                index = vertices_dct.get(vert, -1)
                            else:
                                index = -1

                            if index == -1:
                                index = self.globalindex
                                self.vertices.append(vert)
                                self.globalindex += 1

                                if bpy.context.scene.xplane.optimize:
                                    vertices_dct[vert] = index

                            # store face information in one struct
                            xplaneFace.vertices[i] = (vert[0], vert[1], vert[2])
                            xplaneFace.normals[i] = (vert[3], vert[4], vert[5])
                            xplaneFace.uvs[i] = (vert[6], vert[7])
                            xplaneFace.indices[i] = index

                            self.indices.append(index)

                        faces.append(xplaneFace)

                    # store the faces in the prim
                    xplaneObject.faces = faces
                    xplaneObject.indices[1] = len(self.indices)
                    self.faces.extend(faces)

                d['start_index'] = xplaneObject.indices[0]
                d['end_index'] = xplaneObject.indices[1]
                self.debug.append(d)

        if debug:
            try:
                self.debug.sort(key=lambda k: k['obj_faces'],reverse=True)
            except:
                pass

            for d in self.debug:
                tris_to_quads = 1.0

                if not 'obj_faces' in d:
                    d['obj_faces'] = 0

                if d['faces'] > 0:
                    tris_to_quads = d['obj_faces'] / d['faces']

                logger.info('%s: faces %d | xplaneObject-faces %d | tris-to-quads ratio %6.2f | indices %d | vertices %d' % (d['name'],d['faces'],d['obj_faces'],tris_to_quads,d['end_index']-d['start_index'],d['vertices']))

            logger.info('POINT COUNTS: faces %d - vertices %d - indices %d' % (len(self.faces),len(self.vertices),len(self.indices)))

            # logger.info("End XPlaneMesh  .collectXPlaneObjects: " + str(time.perf_counter()-start))

    # Method: getUVFaces
    # Returns Blender the UV faces of a Blender mesh.
    #
    # Parameters:
    #   mesh - Blender mesh
    #   string uv_name - Name of the uv layer to use. If not given the first layer will be used.
    #
    # Returns:
    #   None if no UV faces could be found or the Blender UV Faces.
    def getUVFaces(self, mesh, uv_name):
        # get the uv_texture
        if hasattr(mesh,'polygons'): # BMesh
            uv_textures = mesh.tessface_uv_textures
        else:
            uv_textures = mesh.uv_textures

        if (uv_name != None and len(uv_textures) > 0):
            uv_layer = None

            if uv_name=="":
                uv_layer = uv_textures[0]
            else:
                i = 0

                while uv_layer == None and i < len(uv_textures):
                    if uv_textures[i].name == uv_name:
                        uv_layer = uv_textures[i]

                    i += 1

            if uv_layer != None:
                return uv_layer.data
            else:
                return None

        else:
            return None

    # Method: getTriangulatedMesh
    # Returns a triangulated mesh from a given Blender xplaneObjectect.
    #
    # Parameters:
    #   blenderObject - Blender Object
    #
    # Returns:
    #   A Blender mesh
    #
    # Todos:
    #   - Does not remove temporarily created mesh/xplaneObjectect yet.
    def getTriangulatedMesh(self, blenderObject):
        me_da = blenderObject.data.copy() #copy data
        me_ob = blenderObject.copy() #copy xplaneObjectect
        #note two copy two types else it will use the current data or mesh
        me_ob.data = me_da
        bpy.context.scene.objects.link(me_ob) #link the xplaneObjectect to the scene #current xplaneObjectect location

        for i in bpy.context.scene.objects: i.select = False #deselect all xplaneObjectects

        me_ob.select = True
        bpy.context.scene.objects.active = me_ob #set the mesh xplaneObjectect to current
        bpy.ops.object.mode_set(mode = 'EDIT') #Operators
        bpy.ops.mesh.select_all(action = 'SELECT')#select all the face/vertex/edge
        bpy.ops.mesh.quads_convert_to_tris() #Operators
        bpy.context.scene.update()
        bpy.ops.object.mode_set(mode = 'OBJECT') # set it in xplaneObjectect

        mesh = me_ob.to_mesh(bpy.context.scene, True, "PREVIEW")

        bpy.context.scene.objects.unlink(me_ob)
        return mesh

    # Method: faceToTrianglesWithUV
    # Converts a Blender face (3 or 4 sided) into one or two 3-sided faces together with the texture coordinates.
    #
    # Parameters:
    #   face - A Blender face.
    #   uv - UV coordiantes of a Blender UV face.
    #
    # Returns:
    #   list - [{'uv':[[u1,v1],[u2,v2],[u3,v3]],'indices':[i1,i2,i3]},..] In length 1 or 2.
    def faceToTrianglesWithUV(self,face,uv):
        triangles = []
        #inverse uv's as we are inversing face indices later
        i0 = face.vertices[0]
        i1 = face.vertices[1]
        i2 = face.vertices[2]
        if len(face.vertices) == 4: #quad
            i3 = face.vertices[3]
            if uv != None:
                triangles.append( {"uv":[[uv.uv3[0], uv.uv3[1]], [uv.uv2[0], uv.uv2[1]], [uv.uv1[0], uv.uv1[1]]], "indices":[face.vertices[0], face.vertices[1], face.vertices[2]],'original_face':face,
                    "norms":[face.split_normals[0], face.split_normals[1], face.split_normals[2]]})
                triangles.append( {"uv":[[uv.uv1[0], uv.uv1[1]], [uv.uv4[0], uv.uv4[1]], [uv.uv3[0], uv.uv3[1]]], "indices":[face.vertices[2], face.vertices[3], face.vertices[0]],'original_face':face,
                    "norms":[face.split_normals[2], face.split_normals[3], face.split_normals[0]]})
            else:
                triangles.append( {"uv":[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "indices":[face.vertices[0], face.vertices[1], face.vertices[2]],'original_face':face,
                    "norms":[face.split_normals[0], face.split_normals[1], face.split_normals[2]]})
                triangles.append( {"uv":[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "indices":[face.vertices[2], face.vertices[3], face.vertices[0]],'original_face':face,
                    "norms":[face.split_normals[2], face.split_normals[3], face.split_normals[0]]})

        else:
            if uv != None:
                triangles.append( {"uv":[[uv.uv3[0], uv.uv3[1]], [uv.uv2[0], uv.uv2[1]], [uv.uv1[0], uv.uv1[1]]], "indices":face.vertices,'original_face':face,
                    "norms":[face.split_normals[0], face.split_normals[1], face.split_normals[2]]})
            else:
                triangles.append( {"uv":[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "indices":face.vertices,'original_face':face,
                    "norms":[face.split_normals[0], face.split_normals[1], face.split_normals[2]]})

        return triangles

    # Method: faceValues
    # Returns the converted vertices of a face.
    #
    # Parameters:
    #   face - A Blender face.
    #   mesh - A Blender mesh.
    #   Matrix matrix - The conversion matrix.
    #
    # Returns:
    #   list - List of vertices.
    def faceValues(self,face, mesh, matrix):
        fv = []

        for verti in face.vertices_raw:
            fv.append(matrix * mesh.vertices[verti].co)

        return fv

    # Method: writeVertices
    # Returns the OBJ vertex table by iterating <vertices>.
    #
    # Returns:
    #   string - The OBJ vertex table.
    def writeVertices(self):
        ######################################################################
        # WARNING! This is a hot path! So don't change it without profiling! #
        ######################################################################
        o = bytearray()
        #print("Begin XPlaneMesh.writeVertices")
        #start = time.perf_counter()
        debug = getDebug()

        vt_array = array.array('f', [round(component,8) for vertice in self.vertices for component in vertice])
        #Loop through every line, format it's 8 components, use rstrip, if statement for 10.00000000->10.0
        #52-60 seconds
        for i,line in enumerate(range(0,len(vt_array),8)):
            o += b"VT"
            for component in  vt_array[line:line+8]:
                sb = bytes("\t%.8f" % component,"utf-8").rstrip(b'0')
                if sb[-1] == 46:#'.':
                    o += sb[:-1]
                else:
                    o += sb

            if debug:
                o += bytes("\t# %d\n" % i,"utf-8")
            else:
                o += b"\n"

        #print("end XPlaneMesh.writeVertices " + str(time.perf_counter()-start))
        return o.decode("utf-8")

    # Method: writeIndices
    # Returns the OBJ indices table by itering <indices>.
    #
    # Returns:
    #   string - The OBJ indices table.
    def writeIndices(self):
        ######################################################################
        # WARNING! This is a hot path! So don't change it without profiling! #
        ######################################################################
        o=''
        #print("Begin XPlaneMesh.writeIndices")
        #start = time.perf_counter()

        s_idx10 = "IDX10\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n"
        s_idx   = "IDX\t%d\n"
        partition_point = len(self.indices) - (len(self.indices) % 10)

        if len(self.indices) >= 10:
            o += ''.join([s_idx10 % (*self.indices[i:i+10],) for i in range(0,partition_point-1,10)])

        o += ''.join([s_idx % (self.indices[i]) for i in range(partition_point,len(self.indices))])
        #print("End XPlaneMesh.writeIndices: " + str(time.perf_counter()-start))
        return o

    def write(self):
        o = ''
        debug = False

        verticesOut = self.writeVertices()
        o += verticesOut
        if len(verticesOut):
            o += '\n'
        o += self.writeIndices()

        return o

import array
import time
import re

import bpy
from ..xplane_config import getDebug
from ..xplane_helpers import floatToStr, logger
from ..xplane_constants import *
from .xplane_face import XPlaneFace

# Class: XPlaneMesh
# Creates the OBJ meshes.
class XPlaneMesh():
    # Constructor: __init__
    def __init__(self):
        # list - contains all mesh vertices
        self.vertices = []
        # array - contains all face indices
        self.indices = array.array('i')
        self.faces = []
        # int - Stores the current global vertex index.
        self.globalindex = 0
        self.debug = []

    # Method: collectXPlaneObjects
    # Fills the <vertices> and <indices> from a list of <XPlaneObjects>.
    # This method works recursively on the children of each <XPlaneObject>.
    #
    # Parameters:
    #   list xplaneObjects - list of <XPlaneObjects>.
    def collectXPlaneObjects(self, xplaneObjects):

        # start = time.perf_counter()

        debug = getDebug()
        supports_split_normals = False

        def getSortKey(xplaneObject):
            return xplaneObject.name

        # sort objects by name for consitent vertex and indices table output
        # this is usefull for unit tests and version control, as file changes are kept at a minimum
        xplaneObjects = sorted(xplaneObjects, key = getSortKey)

        for xplaneObject in xplaneObjects:
            # skip non-mesh objects and objects that do not have a xplane bone
            if xplaneObject.type == 'MESH' and xplaneObject.xplaneBone:
                xplaneObject.indices[0] = len(self.indices)
                first_vertice_of_this_xplaneObject = len(self.vertices)

                # create a copy of the xplaneObject mesh with modifiers applied and triangulated
                mesh = xplaneObject.blenderObject.to_mesh(bpy.context.scene, True, "PREVIEW")

                # now get the bake matrix
                # and bake it to the mesh
                xplaneObject.bakeMatrix = xplaneObject.xplaneBone.getBakeMatrixForAttached()
                mesh.transform(xplaneObject.bakeMatrix)

                if hasattr(mesh, 'calc_normals_split'): # split normals
                    mesh.calc_normals_split()
                    supports_split_normals = True

                if hasattr(mesh, 'polygons'): # BMesh
                    mesh.update(calc_tessface = True)
                    mesh.calc_tessface()
                    mesh_faces = mesh.tessfaces
                else:
                    mesh_faces = mesh.faces

                # with the new mesh get uvFaces list
                uvFaces = self.getUVFaces(mesh, xplaneObject.material.uv_name)

                faces = []
                vertices_dct = {}

                d = {'name': xplaneObject.name,'obj_face': 0,'faces': len(mesh_faces),'quads': 0,'vertices': len(mesh.vertices),'uvs': 0}

                # convert faces to triangles
                if len(mesh_faces) > 0:
                    tempfaces = []

                    for i in range(0, len(mesh_faces)):
                        if uvFaces != None:
                            f = self.faceToTrianglesWithUV(mesh_faces[i], uvFaces[i])
                            tempfaces.extend(f)

                            d['uvs'] += 1

                            if len(f) > 1:
                                d['quads'] += 1

                        else:
                            f = self.faceToTrianglesWithUV(mesh_faces[i], None)
                            tempfaces.extend(f)

                            if len(f) > 1:
                                d['quads']+=1

                    d['obj_faces'] = len(tempfaces)

                    for f in tempfaces:
                        xplaneFace = XPlaneFace()
                        l = len(f['indices'])

                        for i in range(0, l):
                            # get the original index but reverse order, as this is reversing normals
                            vindex = f['indices'][2 - i]

                            # get the vertice from original mesh
                            v = mesh.vertices[vindex]
                            co = v.co
                            ns = f['norms'][2 - i] if supports_split_normals else v.normal

                            if f['original_face'].use_smooth: # use smoothed vertex normal
                                vert = (
                                    co[0], co[2], -co[1],
                                    ns[0], ns[2], -ns[1],
                                    f['uv'][i][0], f['uv'][i][1]
                                )
                            else: # use flat face normal
                                vert = (
                                    co[0], co[2], -co[1],
                                    f['original_face'].normal[0], f['original_face'].normal[2], -f['original_face'].normal[1],
                                    f['uv'][i][0], f['uv'][i][1]
                                )

                            if bpy.context.scene.xplane.optimize:
                                #check for duplicates
                                index = vertices_dct.get(vert, -1)
                            else:
                                index = -1

                            if index == -1:
                                index = self.globalindex
                                self.vertices.append(vert)
                                self.globalindex += 1

                                if bpy.context.scene.xplane.optimize:
                                    vertices_dct[vert] = index

                            # store face information in one struct
                            xplaneFace.vertices[i] = (vert[0], vert[1], vert[2])
                            xplaneFace.normals[i] = (vert[3], vert[4], vert[5])
                            xplaneFace.uvs[i] = (vert[6], vert[7])
                            xplaneFace.indices[i] = index

                            self.indices.append(index)

                        faces.append(xplaneFace)

                    # store the faces in the prim
                    xplaneObject.faces = faces
                    xplaneObject.indices[1] = len(self.indices)
                    self.faces.extend(faces)

                d['start_index'] = xplaneObject.indices[0]
                d['end_index'] = xplaneObject.indices[1]
                self.debug.append(d)

        if debug:
            try:
                self.debug.sort(key=lambda k: k['obj_faces'],reverse=True)
            except:
                pass

            for d in self.debug:
                tris_to_quads = 1.0

                if not 'obj_faces' in d:
                    d['obj_faces'] = 0

                if d['faces'] > 0:
                    tris_to_quads = d['obj_faces'] / d['faces']

                logger.info('%s: faces %d | xplaneObject-faces %d | tris-to-quads ratio %6.2f | indices %d | vertices %d' % (d['name'],d['faces'],d['obj_faces'],tris_to_quads,d['end_index']-d['start_index'],d['vertices']))

            logger.info('POINT COUNTS: faces %d - vertices %d - indices %d' % (len(self.faces),len(self.vertices),len(self.indices)))

            # logger.info("End XPlaneMesh  .collectXPlaneObjects: " + str(time.perf_counter()-start))

    # Method: getUVFaces
    # Returns Blender the UV faces of a Blender mesh.
    #
    # Parameters:
    #   mesh - Blender mesh
    #   string uv_name - Name of the uv layer to use. If not given the first layer will be used.
    #
    # Returns:
    #   None if no UV faces could be found or the Blender UV Faces.
    def getUVFaces(self, mesh, uv_name):
        # get the uv_texture
        if hasattr(mesh,'polygons'): # BMesh
            uv_textures = mesh.tessface_uv_textures
        else:
            uv_textures = mesh.uv_textures

        if (uv_name != None and len(uv_textures) > 0):
            uv_layer = None

            if uv_name=="":
                uv_layer = uv_textures[0]
            else:
                i = 0

                while uv_layer == None and i < len(uv_textures):
                    if uv_textures[i].name == uv_name:
                        uv_layer = uv_textures[i]

                    i += 1

            if uv_layer != None:
                return uv_layer.data
            else:
                return None

        else:
            return None

    # Method: getTriangulatedMesh
    # Returns a triangulated mesh from a given Blender xplaneObjectect.
    #
    # Parameters:
    #   blenderObject - Blender Object
    #
    # Returns:
    #   A Blender mesh
    #
    # Todos:
    #   - Does not remove temporarily created mesh/xplaneObjectect yet.
    def getTriangulatedMesh(self, blenderObject):
        me_da = blenderObject.data.copy() #copy data
        me_ob = blenderObject.copy() #copy xplaneObjectect
        #note two copy two types else it will use the current data or mesh
        me_ob.data = me_da
        bpy.context.scene.objects.link(me_ob) #link the xplaneObjectect to the scene #current xplaneObjectect location

        for i in bpy.context.scene.objects: i.select = False #deselect all xplaneObjectects

        me_ob.select = True
        bpy.context.scene.objects.active = me_ob #set the mesh xplaneObjectect to current
        bpy.ops.object.mode_set(mode = 'EDIT') #Operators
        bpy.ops.mesh.select_all(action = 'SELECT')#select all the face/vertex/edge
        bpy.ops.mesh.quads_convert_to_tris() #Operators
        bpy.context.scene.update()
        bpy.ops.object.mode_set(mode = 'OBJECT') # set it in xplaneObjectect

        mesh = me_ob.to_mesh(bpy.context.scene, True, "PREVIEW")

        bpy.context.scene.objects.unlink(me_ob)
        return mesh

    # Method: faceToTrianglesWithUV
    # Converts a Blender face (3 or 4 sided) into one or two 3-sided faces together with the texture coordinates.
    #
    # Parameters:
    #   face - A Blender face.
    #   uv - UV coordiantes of a Blender UV face.
    #
    # Returns:
    #   list - [{'uv':[[u1,v1],[u2,v2],[u3,v3]],'indices':[i1,i2,i3]},..] In length 1 or 2.
    def faceToTrianglesWithUV(self,face,uv):
        triangles = []
        #inverse uv's as we are inversing face indices later
        i0 = face.vertices[0]
        i1 = face.vertices[1]
        i2 = face.vertices[2]
        if len(face.vertices) == 4: #quad
            i3 = face.vertices[3]
            if uv != None:
                triangles.append( {"uv":[[uv.uv3[0], uv.uv3[1]], [uv.uv2[0], uv.uv2[1]], [uv.uv1[0], uv.uv1[1]]], "indices":[face.vertices[0], face.vertices[1], face.vertices[2]],'original_face':face,
                    "norms":[face.split_normals[0], face.split_normals[1], face.split_normals[2]]})
                triangles.append( {"uv":[[uv.uv1[0], uv.uv1[1]], [uv.uv4[0], uv.uv4[1]], [uv.uv3[0], uv.uv3[1]]], "indices":[face.vertices[2], face.vertices[3], face.vertices[0]],'original_face':face,
                    "norms":[face.split_normals[2], face.split_normals[3], face.split_normals[0]]})
            else:
                triangles.append( {"uv":[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "indices":[face.vertices[0], face.vertices[1], face.vertices[2]],'original_face':face,
                    "norms":[face.split_normals[0], face.split_normals[1], face.split_normals[2]]})
                triangles.append( {"uv":[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "indices":[face.vertices[2], face.vertices[3], face.vertices[0]],'original_face':face,
                    "norms":[face.split_normals[2], face.split_normals[3], face.split_normals[0]]})

        else:
            if uv != None:
                triangles.append( {"uv":[[uv.uv3[0], uv.uv3[1]], [uv.uv2[0], uv.uv2[1]], [uv.uv1[0], uv.uv1[1]]], "indices":face.vertices,'original_face':face,
                    "norms":[face.split_normals[0], face.split_normals[1], face.split_normals[2]]})
            else:
                triangles.append( {"uv":[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "indices":face.vertices,'original_face':face,
                    "norms":[face.split_normals[0], face.split_normals[1], face.split_normals[2]]})

        return triangles

    # Method: faceValues
    # Returns the converted vertices of a face.
    #
    # Parameters:
    #   face - A Blender face.
    #   mesh - A Blender mesh.
    #   Matrix matrix - The conversion matrix.
    #
    # Returns:
    #   list - List of vertices.
    def faceValues(self,face, mesh, matrix):
        fv = []

        for verti in face.vertices_raw:
            fv.append(matrix * mesh.vertices[verti].co)

        return fv

    # Method: writeVertices
    # Returns the OBJ vertex table by iterating <vertices>.
    #
    # Returns:
    #   string - The OBJ vertex table.
    def writeVertices(self):
        ######################################################################
        # WARNING! This is a hot path! So don't change it without profiling! #
        ######################################################################
        #print("Begin XPlaneMesh.writeVertices")
        #start = time.perf_counter()
        debug = getDebug()

        precision = 10**8

        #Loop through every line, format it's 8 components
        #52-60 seconds
        if debug:
            fmt = 'VT\t%.8g\t%.8g\t%.8g\t%.8g\t%.8g\t%.8g\t%.8g\t%.8g\t# %d\n'
            return ''.join([fmt % (*[(int(component * precision + 0.5) / precision) for component in line],i) for i, line in enumerate(self.vertices)])
        else:
            fmt = 'VT\t%.8g\t%.8g\t%.8g\t%.8g\t%.8g\t%.8g\t%.8g\t%.8g\n'
            return ''.join([fmt % (*[(int(component * precision + 0.5) / precision) for component in line],) for line in self.vertices])

        #print("end XPlaneMesh.writeVertices " + str(time.perf_counter()-start))

    # Method: writeIndices
    # Returns the OBJ indices table by itering <indices>.
    #
    # Returns:
    #   string - The OBJ indices table.
    def writeIndices(self):
        ######################################################################
        # WARNING! This is a hot path! So don't change it without profiling! #
        ######################################################################
        o=''
        indices = self.indices
        length = len(indices)
        # print("Begin XPlaneMesh.writeIndices")
        # start = time.perf_counter()

        s_idx10 = "IDX10\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n"
        s_idx   = "IDX\t%d\n"
        partition_point = length - (length % 10)

        if length >= 10:
            o += ''.join([s_idx10 % (*indices[i:i+10],) for i in range(0,partition_point-1,10)])

        o += ''.join([s_idx % (indices[i]) for i in range(partition_point,length)])
        # print("End XPlaneMesh.writeIndices: " + str(time.perf_counter()-start))
        return o

    def write(self):
        o = ''
        debug = False

        verticesOut = self.writeVertices()
        o += verticesOut
        if len(verticesOut):
            o += '\n'
        o += self.writeIndices()

        return o


<!DOCTYPE HTML>

    <meta name="viewport" content="width=320; user-scalable=no" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>PhoneGap</title>
	  <link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8">

Welcome to PhoneGap!

this file is located at assets/www/index.html

Platform:   ,   Version:

UUID:   ,   Name:

Width:   ,   Height:
                   , Color Depth:

X:

Y:

Z:

    Toggle Accelerometer
    Get Location
	Call 411
    Beep
    Vibrate
    Get a Picture
    Get Phone's Contacts
    Check Network


/*
---

name: Meio.Mask.Reverse

description: A mask used for currency and decimal numbers.

authors:
 - Fábio Miranda Costa

requires:
 - Meio.Mask

license: MIT-style license

provides: [Meio.Mask.Reverse]

...
*/

Meio.Mask.Reverse = new Class({

	Extends: Meio.Mask,

	options: {
		autoSetSize: false,
		autoEmpty: false,
		alignText: true,
		symbol: '',
		precision: 2,
		decimal: ',',
		thousands: '.',
		maxLength: 18
	},

	initialize: function(options){
		this.parent(options);
		var thousandsChar = this.options.thousands,
			escapedThousandsChars = thousandsChar.escapeRegExp(),
			escapedDecimalChar = this.options.decimal.escapeRegExp();
		this.maxlength = this.options.maxLength;
		this.reThousands = /(\d+)(\d{3})/;
		this.reRemoveLeadingZeros = /^0+(.*)$/;
		this.reDecimalNumber = /^\d$/;
		this.thousandsReplaceStr = '$1' + thousandsChar + '$2';
		this.reThousandsReplace = new RegExp(escapedThousandsChars, 'g');
		this.reCleanup = new RegExp('[' + escapedThousandsChars + escapedDecimalChar + ']', 'g');
		this.reRemoveNonNumbers = new RegExp('[^\\d' + escapedThousandsChars + escapedDecimalChar + ']', 'g');
	},

	link: function(element){
		this.parent(element);
		if (this.options.alignText) this.element.setStyle('text-align', 'right');
		var elementValue = this.element.get('value');
		if (elementValue === '' && !this.options.autoEmpty){
			this.element.set('value', this.forceMask(elementValue, false));
		}
		return this;
	},

	focus: function(e, o){
		var element = this.element,
			elValue = element.get('value');
		if (this.options.autoEmpty){
			if (elValue === '') element.set('value', this.mask(elValue));
		} else {
			element.set('value', this.getValue(elValue, true));
		}
		this.parent(e, o);
	},

	blur: function(e, o){
		this.parent(e, o);
		var element = this.element,
			value = this.getValue(element.get('value'));
		if (this.options.autoEmpty && this.mask(value) == this.mask()) value = '';
		element.set('value', value);
	},

	keypress: function(e, o){
		if (this.ignore) return true;
		e.preventDefault();

		var state = this.getCurrentState(e, o), elementValue = state.value;

		if (!this.testEvents(elementValue, state._char, e.code, o.isRemoveKey)) return true;
		elementValue = this.forceMask(elementValue, true);
		this.element.set('value', elementValue).setCaretPosition(elementValue.length);

		return this.parent();
	},

	testEvents: function(elementValue, _char, code, isRemoveKey){
		var args = [this.element, code, _char];
		if (!isRemoveKey){
			var elementValueLength = this.getValue(elementValue, false).length;
			if (!(this.reDecimalNumber).test(_char) || (this.maxlength && elementValueLength > this.maxlength)){
				this.fireEvent('invalid', args);
				return false;
			}
			this.fireEvent('valid', args);
		}
		return true;
	},

	paste: function(e, o){
		var element = this.element;
		var elValue = element.get('value');
		element.set('value', (elValue = this.forceMask(elValue, true))).setCaretPosition(elValue.length);
		return true;
	},

	forceMask: function(str, applySymbol){
		str = this.cleanup(str);
		var precision = this.options.precision;
		var zeros = precision + 1 - str.length;
		if (zeros > 0) str = this.zeroize(str, zeros);
		if (precision){
			var decimalIndex = str.length - precision;
			str = str.substring(0, decimalIndex) + this.options.decimal + str.substring(decimalIndex);
		}
		return this.getValue(this.maskThousands(str), applySymbol);
	},

	cleanup: function(str){
		return this.getValue(str.replace(this.reCleanup, '')).replace(this.reRemoveLeadingZeros, '$1');
	},

	mask: function(str){
		str = this.unmask(str || '0').replace('.', this.options.decimal);
		return this.getValue(this.maskThousands(str), false);
	},

	unmask: function(str){
		return this.toNumber(this.getValue(str));
	},

	toNumber: function(str){
		str = str.replace(this.reRemoveNonNumbers, '');
		if (!isFinite(str)){
			if (this.options.thousands) str = str.replace(this.reThousandsReplace, '');
			var decimalChar = this.options.decimal;
			if (decimalChar) str = str.replace(decimalChar, '.');
		}
		return str.toFloat().toFixed(this.options.precision);
	},

	getValue: function(str, applySymbol){
		var symbol = this.options.symbol;
		return (str.substring(0, symbol.length) === symbol) ?
			applySymbol ? str : str.substring(symbol.length) :
			applySymbol ? symbol + str : str;
	},

	maskThousands: function(str){
		if (this.options.thousands){
			while (this.reThousands.test(str)) str = str.replace(this.reThousands, this.thousandsReplaceStr);
		}
		return str;
	},

	zeroize: function(str, zeros){
		while (zeros--) str = '0' + str;
		return str;
	},

	shouldFocusNext: function(){
		return this.getValue(this.element.get('value'), false).length >= this.options.maxLength;
	}
});

Meio.Mask.createMasks('Reverse', {
	'Integer'		: {precision: 0, maxLength: 18},
	'Decimal'		: { },
	'DecimalUs'		: {thousands: ',', decimal: '.'},
	'Reais'			: {symbol: 'R$ ' },
	'Dollar'		: {symbol: 'US$ ', thousands: ',', decimal: '.'}
});

/*
---

name: Meio.Mask.Reverse

description: A mask used for currency and decimal numbers.

authors:
 - Fábio Miranda Costa

requires:
 - Meio.Mask

license: MIT-style license

provides: [Meio.Mask.Reverse]

...
*/

Meio.Mask.Reverse = new Class({

	Extends: Meio.Mask,

	options: {
		autoSetSize: false,
		autoEmpty: false,
		alignText: true,
		symbol: '',
		precision: 2,
		decimal: ',',
		thousands: '.',
		maxLength: 18
	},

	initialize: function(options){
		this.parent(options);
		var thousandsChar = this.options.thousands,
			escapedThousandsChars = thousandsChar.escapeRegExp(),
			escapedDecimalChar = this.options.decimal.escapeRegExp();
		this.maxlength = this.options.maxLength;
		this.reThousands = /(\d+)(\d{3})/;
		this.reRemoveLeadingZeros = /^0+(.*)$/;
		this.reDecimalNumber = /^\d$/;
		this.thousandsReplaceStr = '$1' + thousandsChar + '$2';
		this.reThousandsReplace = new RegExp(escapedThousandsChars, 'g');
		this.reCleanup = new RegExp('[' + escapedThousandsChars + escapedDecimalChar + ']', 'g');
		this.reRemoveNonNumbers = new RegExp('[^\\d' + escapedThousandsChars + escapedDecimalChar + ']', 'g');
	},

	link: function(element){
		this.parent(element);
		if (this.options.alignText) this.element.setStyle('text-align', 'right');
		var elementValue = this.element.get('value');
		if (elementValue === '' && !this.options.autoEmpty){
			this.element.set('value', this.forceMask(elementValue, false));
		}
		return this;
	},

	focus: function(e, o){
		var element = this.element,
			elValue = element.get('value');
		if (this.options.autoEmpty){
			if (elValue === '') element.set('value', this.mask(elValue));
		} else {
			element.set('value', this.getValue(elValue, true));
		}
		this.parent(e, o);
	},

	blur: function(e, o){
		this.parent(e, o);
		var element = this.element,
			value = this.getValue(element.get('value'));
		if (this.options.autoEmpty && this.mask(value) == this.mask()) value = '';
		element.set('value', value);
	},

	keypress: function(e, o){
		if (this.ignore) return true;
		e.preventDefault();

		var state = this.getCurrentState(e, o), elementValue = state.value;

		if (!this.testEvents(elementValue, state._char, e.code, o.isRemoveKey)) return true;
		elementValue = this.forceMask(elementValue, true);
		this.element.set('value', elementValue).setCaretPosition(elementValue.length);

		return this.parent();
	},

	testEvents: function(elementValue, _char, code, isRemoveKey){
		var args = [this.element, code, _char];
		if (!isRemoveKey){
			var elementValueLength = this.getValue(elementValue, false).length;
			if (!(this.reDecimalNumber).test(_char) || (this.maxlength && elementValueLength > this.maxlength)){
				this.fireEvent('invalid', args);
				return false;
			}
			this.fireEvent('valid', args);
		}
		return true;
	},

	paste: function(e, o){
		var element = this.element;
		var elValue = element.get('value');
		element.set('value', (elValue = this.forceMask(elValue, true))).setCaretPosition(elValue.length);
		return true;
	},

	forceMask: function(str, applySymbol){
		str = this.cleanup(str);
		var precision = this.options.precision;
		var zeros = precision + 1 - str.length;
		if (zeros > 0) str = this.zeroize(str, zeros);
		if (precision){
			var decimalIndex = str.length - precision;
			str = str.substring(0, decimalIndex) + this.options.decimal + str.substring(decimalIndex);
		}
		return this.getValue(this.maskThousands(str), applySymbol);
	},

	cleanup: function(str){
		return this.getValue(str.replace(this.reCleanup, '')).replace(this.reRemoveLeadingZeros, '$1');
	},

	mask: function(str){
		str = this.unmask(str || '0').replace('.', this.options.decimal);
		return this.getValue(this.maskThousands(str), false);
	},

	unmask: function(str){
		return this.toNumber(this.getValue(str));
	},

	toNumber: function(str){
		str = str.replace(this.reRemoveNonNumbers, '');
		if (!isFinite(str)){
			if (this.options.thousands) str = str.replace(this.reThousandsReplace, '');
			var decimalChar = this.options.decimal;
			if (decimalChar) str = str.replace(decimalChar, '.');
		}
		return str.toFloat().toFixed(this.options.precision);
	},

	getValue: function(str, applySymbol){
		var symbol = this.options.symbol;
		return (str.substring(0, symbol.length) === symbol) ?
			applySymbol ? str : str.substring(symbol.length) :
			applySymbol ? symbol + str : str;
	},

	maskThousands: function(str){
		if (this.options.thousands){
			while (str.toFloat() >= 1000 && this.reThousands.test(str)) {
				str = str.replace(this.reThousands, this.thousandsReplaceStr);
			}
		}
		return str;
	},

	zeroize: function(str, zeros){
		while (zeros--) str = '0' + str;
		return str;
	},

	shouldFocusNext: function(){
		return this.getValue(this.element.get('value'), false).length >= this.options.maxLength;
	}
});

Meio.Mask.createMasks('Reverse', {
	'Integer'		: {precision: 0, maxLength: 18},
	'Decimal'		: { },
	'DecimalUs'		: {thousands: ',', decimal: '.'},
	'Reais'			: {symbol: 'R$ ' },
	'Dollar'		: {symbol: 'US$ ', thousands: ',', decimal: '.'}
});