Fix: require the correct file

require 'activerecord'

module Coercionlogic
  def write_attribute_with_coercion(attr_name, value)
    value = nil if value.is_a?(String) && value.blank?
    write_attribute_without_coercion(attr_name, value)
  end
end

ActiveRecord::Base.class_eval do
  include Coercionlogic
  alias_method_chain :write_attribute, :coercion
end

require 'active_record'

module Coercionlogic
  def write_attribute_with_coercion(attr_name, value)
    value = nil if value.is_a?(String) && value.blank?
    write_attribute_without_coercion(attr_name, value)
  end
end

ActiveRecord::Base.class_eval do
  include Coercionlogic
  alias_method_chain :write_attribute, :coercion
end


Unbind load event on destroy

/*!
* jQuery ImageColorPicker Plugin v0.2
* http://github.com/Skarabaeus/ImageColorPicker
*
* Copyright 2010, Stefan Siebel
* Licensed under the MIT license.
* http://github.com/Skarabaeus/ImageColorPicker/MIT-LICENSE.txt
*
* Released under the MIT
*
* Date: Tue May 17 11:20:16 2011 -0700
*/
(function(){
var uiImageColorPicker = function(){

	var _d2h = function(d) {
		var result;
		if (! isNaN( parseInt(d) ) ) {
			result = parseInt(d).toString(16);
		} else {
			result = d;
		}

		if (result.length === 1) {
			result = "0" + result;
		}
		return result;
	};

	var _h2d = function(h) {
		return parseInt(h,16);
	};

	var _createImageColorPicker = function(widget) {
		// store 2D context in widget for later access
		widget.ctx = null;

		// rgb
		widget.color = [0, 0, 0];

		// create additional DOM elements.
		widget.$canvas = $('<canvas class="ImageColorPickerCanvas"></canvas>');

		// add them to the DOM
		widget.element.wrap('

');
		widget.$wrapper = widget.element.parent();
		widget.$wrapper.append(widget.$canvas);

		if (typeof(widget.$canvas.get(0).getContext) === 'function') { // FF, Chrome, ...
			widget.ctx = widget.$canvas.get(0).getContext('2d');

		// this does not work yet!
		} else {
			widget.destroy();
			if (console) {
				console.log("ImageColor Picker: Can't get canvas context. Use "
					+ "Firefox, Chrome or include excanvas to your project.");
			}

		}

		// draw the image in the canvas
		var img = new Image();
		img.src = widget.element.attr("src");
		widget.$canvas.attr("width", img.width);
		widget.$canvas.attr("height", img.height);
		widget.ctx.drawImage(img, 0, 0);

		// get the image data.
		try {
			try {
				widget.imageData = widget.ctx.getImageData(0, 0, img.width, img.height);
			} catch (e1) {
				netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
				widget.imageData = widget.ctx.getImageData(0, 0, img.width, img.height);
			}
		} catch (e2) {
			widget.destroy();
			if (console) {
				console.log("ImageColor Picker: Unable to access image data. "
					+ "This could be either due "
					+ "to the browser you are using (IE doesn't work) or image and script "
					+ "are saved on different servers or you run the script locally. ");
			}
		}

		// hide the original image
		widget.element.hide();

		// for usage in events
		var that = widget;

		widget.$canvas.bind("mousemove", function(e){
      var point = imageCoordinates( that, e.pageX, e.pageY );
      var color = lookupColor( that.imageData, point );

      updateCurrentColor( that, color.red, color.green, color.blue );
		});

		widget.$canvas.bind("click", function(e){
      var point = imageCoordinates( that, e.pageX, e.pageY );
      var color = lookupColor( that.imageData, point );

      updateSelectedColor( that, color.red, color.green, color.blue );
			that._trigger("afterColorSelected", 0, that.selectedColor());
		});

		widget.$canvas.bind("mouseleave", function(e){
			updateCurrentColor(that, 255, 255, 255);
		});

		// hope that helps to prevent memory leaks
		$(window).unload(function(e){
			that.destroy();
		});
	};

  // for pageX and pageY, determine image coordinates using offset
  var imageCoordinates = function( widget, pageX, pageY ) {
    var offset = widget.$canvas.offset();

    return { x: Math.round( pageX - offset.left ),
             y: Math.round( pageY - offset.top ) };
  }

  // lookup color values for point [x,y] location in image
  var lookupColor = function( imageData, point) {
    var pixel =  ((point.y * imageData.width) + point.x) * 4;

    return { red: imageData.data[pixel],
             green: imageData.data[(pixel + 1)],
             blue: imageData.data[(pixel + 2)] }

  }

	var updateCurrentColor = function(widget, red, green, blue) {
		var c = widget.ctx;
		var canvasWidth = widget.$canvas.attr("width");
		var canvasHeight = widget.$canvas.attr("height");

		// draw current Color
		c.fillStyle = "rgb(" + red + "," + green + "," + blue + ")";
		c.fillRect (canvasWidth - 62, canvasHeight - 32, 30, 30);

		// draw border
		c.lineWidth = "3"
		c.lineJoin = "round";
		c.strokeRect (canvasWidth - 62, canvasHeight - 32, 30, 30);
	}

	var updateSelectedColor = function(widget, red, green, blue) {
		var c = widget.ctx;
		var canvasWidth = widget.$canvas.attr("width");
		var canvasHeight = widget.$canvas.attr("height");

		// draw current Color
		c.fillStyle = "rgb(" + red + "," + green + "," + blue + ")";
		c.fillRect (canvasWidth - 32, canvasHeight - 32, 30, 30);

		// draw border
		c.lineWidth = "3"
		c.lineJoin = "round";
		c.strokeRect (canvasWidth - 32, canvasHeight - 32, 30, 30);

		// set new selected color
		var newColor = [red, green, blue];
		widget.color = newColor;
	}

	return {
		// default options
		options: {

		},

		_create: function() {
			if (this.element.get(0).tagName.toLowerCase() === 'img') {
				if (this.element.get(0).complete) {
					_createImageColorPicker(this);
				} else {
					this.element.bind('load', { that: this }, function(e){
						var that = e.data.that;
						_createImageColorPicker(that);
					});
				}
			}
		},

		destroy: function() {
			// default destroy
			$.Widget.prototype.destroy.apply(this, arguments);

			// remove possible large array with pixel data
			this.imageData = null;

			// remove additional elements
			this.$canvas.remove();
			this.element.unwrap();
			this.element.show();
		},

		selectedColor: function() {
			return "#" + _d2h(this.color[0]) + _d2h(this.color[1]) + _d2h(this.color[2]);
		}

	};
}();
	$.widget("ui.ImageColorPicker", uiImageColorPicker);
})();

/*!
* jQuery ImageColorPicker Plugin v0.2
* http://github.com/Skarabaeus/ImageColorPicker
*
* Copyright 2010, Stefan Siebel
* Licensed under the MIT license.
* http://github.com/Skarabaeus/ImageColorPicker/MIT-LICENSE.txt
*
* Released under the MIT
*
* Date: Tue May 17 11:20:16 2011 -0700
*/
(function(){var n=function(){var g=function(a){a=isNaN(parseInt(a))?a:parseInt(a).toString(16);if(a.length===1)a="0"+a;return a},k=function(a){a.ctx=null;a.color=[0,0,0];a.$canvas=$('<canvas class="ImageColorPickerCanvas"></canvas>');a.element.wrap('

');a.$wrapper=a.element.parent();a.$wrapper.append(a.$canvas);if(typeof a.$canvas.get(0).getContext==="function")a.ctx=a.$canvas.get(0).getContext("2d");else{a.destroy();console&&console.log("ImageColor Picker: Can't get canvas context. Use Firefox, Chrome or include excanvas to your project.")}var c=
new Image;c.src=a.element.attr("src");a.$canvas.attr("width",c.width);a.$canvas.attr("height",c.height);a.ctx.drawImage(c,0,0);try{try{a.imageData=a.ctx.getImageData(0,0,c.width,c.height)}catch(d){netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");a.imageData=a.ctx.getImageData(0,0,c.width,c.height)}}catch(e){a.destroy();console&&console.log("ImageColor Picker: Unable to access image data. This could be either due to the browser you are using (IE doesn't work) or image and script are saved on different servers or you run the script locally. ")}a.element.hide();
a.$canvas.bind("mousemove",function(b){b=h(a,b.pageX,b.pageY);b=i(a.imageData,b);j(a,b.red,b.green,b.blue)});a.$canvas.bind("click",function(b){b=h(a,b.pageX,b.pageY);b=i(a.imageData,b);m(a,b.red,b.green,b.blue);a._trigger("afterColorSelected",0,a.selectedColor())});a.$canvas.bind("mouseleave",function(){j(a,255,255,255)});$(window).unload(function(){a.destroy()})},h=function(a,c,d){a=a.$canvas.offset();return{x:Math.round(c-a.left),y:Math.round(d-a.top)}},i=function(a,c){c=(c.y*a.width+c.x)*4;return{red:a.data[c],
green:a.data[c+1],blue:a.data[c+2]}},j=function(a,c,d,e){var b=a.ctx,f=a.$canvas.attr("width");a=a.$canvas.attr("height");b.fillStyle="rgb("+c+","+d+","+e+")";b.fillRect(f-62,a-32,30,30);b.lineWidth="3";b.lineJoin="round";b.strokeRect(f-62,a-32,30,30)},m=function(a,c,d,e){var b=a.ctx,f=a.$canvas.attr("width"),l=a.$canvas.attr("height");b.fillStyle="rgb("+c+","+d+","+e+")";b.fillRect(f-32,l-32,30,30);b.lineWidth="3";b.lineJoin="round";b.strokeRect(f-32,l-32,30,30);a.color=[c,d,e]};return{options:{},
_create:function(){if(this.element.get(0).tagName.toLowerCase()==="img")this.element.get(0).complete?k(this):this.element.bind("load",{that:this},function(a){k(a.data.that)})},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments);this.imageData=null;this.$canvas.remove();this.element.unwrap();this.element.show()},selectedColor:function(){return"#"+g(this.color[0])+g(this.color[1])+g(this.color[2])}}}();$.widget("ui.ImageColorPicker",n)})();

/*!
* jQuery ImageColorPicker Plugin v0.2
* http://github.com/Skarabaeus/ImageColorPicker
*
* Copyright 2010, Stefan Siebel
* Licensed under the MIT license.
* http://github.com/Skarabaeus/ImageColorPicker/MIT-LICENSE.txt
*
* Released under the MIT
*
* Date: Tue May 17 11:20:16 2011 -0700
*/
(function(){
var uiImageColorPicker = function(){

	var _d2h = function(d) {
		var result;
		if (! isNaN( parseInt(d) ) ) {
			result = parseInt(d).toString(16);
		} else {
			result = d;
		}

		if (result.length === 1) {
			result = "0" + result;
		}
		return result;
	};

	var _h2d = function(h) {
		return parseInt(h,16);
	};

	var _createImageColorPicker = function(widget) {
		// store 2D context in widget for later access
		widget.ctx = null;

		// rgb
		widget.color = [0, 0, 0];

		// create additional DOM elements.
		widget.$canvas = $('<canvas class="ImageColorPickerCanvas"></canvas>');

		// add them to the DOM
		widget.element.wrap('

');
		widget.$wrapper = widget.element.parent();
		widget.$wrapper.append(widget.$canvas);

		if (typeof(widget.$canvas.get(0).getContext) === 'function') { // FF, Chrome, ...
			widget.ctx = widget.$canvas.get(0).getContext('2d');

		// this does not work yet!
		} else {
			widget.destroy();
			if (console) {
				console.log("ImageColor Picker: Can't get canvas context. Use "
					+ "Firefox, Chrome or include excanvas to your project.");
			}

		}

		// draw the image in the canvas
		var img = new Image();
		img.src = widget.element.attr("src");
		widget.$canvas.attr("width", img.width);
		widget.$canvas.attr("height", img.height);
		widget.ctx.drawImage(img, 0, 0);

		// get the image data.
		try {
			try {
				widget.imageData = widget.ctx.getImageData(0, 0, img.width, img.height);
			} catch (e1) {
				netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
				widget.imageData = widget.ctx.getImageData(0, 0, img.width, img.height);
			}
		} catch (e2) {
			widget.destroy();
			if (console) {
				console.log("ImageColor Picker: Unable to access image data. "
					+ "This could be either due "
					+ "to the browser you are using (IE doesn't work) or image and script "
					+ "are saved on different servers or you run the script locally. ");
			}
		}

		// hide the original image
		widget.element.hide();

		// for usage in events
		var that = widget;

		widget.$canvas.bind("mousemove", function(e){
      var point = imageCoordinates( that, e.pageX, e.pageY );
      var color = lookupColor( that.imageData, point );

      updateCurrentColor( that, color.red, color.green, color.blue );
		});

		widget.$canvas.bind("click", function(e){
      var point = imageCoordinates( that, e.pageX, e.pageY );
      var color = lookupColor( that.imageData, point );

      updateSelectedColor( that, color.red, color.green, color.blue );
			that._trigger("afterColorSelected", 0, that.selectedColor());
		});

		widget.$canvas.bind("mouseleave", function(e){
			updateCurrentColor(that, 255, 255, 255);
		});

		// hope that helps to prevent memory leaks
		$(window).unload(function(e){
			that.destroy();
		});
	};

  // for pageX and pageY, determine image coordinates using offset
  var imageCoordinates = function( widget, pageX, pageY ) {
    var offset = widget.$canvas.offset();

    return { x: Math.round( pageX - offset.left ),
             y: Math.round( pageY - offset.top ) };
  }

  // lookup color values for point [x,y] location in image
  var lookupColor = function( imageData, point) {
    var pixel =  ((point.y * imageData.width) + point.x) * 4;

    return { red: imageData.data[pixel],
             green: imageData.data[(pixel + 1)],
             blue: imageData.data[(pixel + 2)] }

  }

	var updateCurrentColor = function(widget, red, green, blue) {
		var c = widget.ctx;
		var canvasWidth = widget.$canvas.attr("width");
		var canvasHeight = widget.$canvas.attr("height");

		// draw current Color
		c.fillStyle = "rgb(" + red + "," + green + "," + blue + ")";
		c.fillRect (canvasWidth - 62, canvasHeight - 32, 30, 30);

		// draw border
		c.lineWidth = "3"
		c.lineJoin = "round";
		c.strokeRect (canvasWidth - 62, canvasHeight - 32, 30, 30);
	}

	var updateSelectedColor = function(widget, red, green, blue) {
		var c = widget.ctx;
		var canvasWidth = widget.$canvas.attr("width");
		var canvasHeight = widget.$canvas.attr("height");

		// draw current Color
		c.fillStyle = "rgb(" + red + "," + green + "," + blue + ")";
		c.fillRect (canvasWidth - 32, canvasHeight - 32, 30, 30);

		// draw border
		c.lineWidth = "3"
		c.lineJoin = "round";
		c.strokeRect (canvasWidth - 32, canvasHeight - 32, 30, 30);

		// set new selected color
		var newColor = [red, green, blue];
		widget.color = newColor;
	}

	return {
		// default options
		options: {

		},

		_create: function() {
			if (this.element.get(0).tagName.toLowerCase() === 'img') {
				if (this.element.get(0).complete) {
					_createImageColorPicker(this);
				} else {
					this.element.bind('load', { that: this }, function(e){
						var that = e.data.that;
						_createImageColorPicker(that);
					});
				}
			}
		},

		destroy: function() {
			// default destroy
			$.Widget.prototype.destroy.apply(this, arguments);

			// remove possible large array with pixel data
			this.imageData = null;

			// remove additional elements
			this.$canvas.remove();
			this.element.unbind('load');
			this.element.unwrap();
			this.element.show();
		},

		selectedColor: function() {
			return "#" + _d2h(this.color[0]) + _d2h(this.color[1]) + _d2h(this.color[2]);
		}

	};
}();
	$.widget("ui.ImageColorPicker", uiImageColorPicker);
})();

/*!
* jQuery ImageColorPicker Plugin v0.2
* http://github.com/Skarabaeus/ImageColorPicker
*
* Copyright 2010, Stefan Siebel
* Licensed under the MIT license.
* http://github.com/Skarabaeus/ImageColorPicker/MIT-LICENSE.txt
*
* Released under the MIT
*
* Date: Tue May 17 11:20:16 2011 -0700
*/
(function(){var n=function(){var g=function(a){a=isNaN(parseInt(a))?a:parseInt(a).toString(16);if(a.length===1)a="0"+a;return a},k=function(a){a.ctx=null;a.color=[0,0,0];a.$canvas=$('<canvas class="ImageColorPickerCanvas"></canvas>');a.element.wrap('

');a.$wrapper=a.element.parent();a.$wrapper.append(a.$canvas);if(typeof a.$canvas.get(0).getContext==="function")a.ctx=a.$canvas.get(0).getContext("2d");else{a.destroy();console&&console.log("ImageColor Picker: Can't get canvas context. Use Firefox, Chrome or include excanvas to your project.")}var c=
new Image;c.src=a.element.attr("src");a.$canvas.attr("width",c.width);a.$canvas.attr("height",c.height);a.ctx.drawImage(c,0,0);try{try{a.imageData=a.ctx.getImageData(0,0,c.width,c.height)}catch(d){netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");a.imageData=a.ctx.getImageData(0,0,c.width,c.height)}}catch(e){a.destroy();console&&console.log("ImageColor Picker: Unable to access image data. This could be either due to the browser you are using (IE doesn't work) or image and script are saved on different servers or you run the script locally. ")}a.element.hide();
a.$canvas.bind("mousemove",function(b){b=h(a,b.pageX,b.pageY);b=i(a.imageData,b);j(a,b.red,b.green,b.blue)});a.$canvas.bind("click",function(b){b=h(a,b.pageX,b.pageY);b=i(a.imageData,b);m(a,b.red,b.green,b.blue);a._trigger("afterColorSelected",0,a.selectedColor())});a.$canvas.bind("mouseleave",function(){j(a,255,255,255)});$(window).unload(function(){a.destroy()})},h=function(a,c,d){a=a.$canvas.offset();return{x:Math.round(c-a.left),y:Math.round(d-a.top)}},i=function(a,c){c=(c.y*a.width+c.x)*4;return{red:a.data[c],
green:a.data[c+1],blue:a.data[c+2]}},j=function(a,c,d,e){var b=a.ctx,f=a.$canvas.attr("width");a=a.$canvas.attr("height");b.fillStyle="rgb("+c+","+d+","+e+")";b.fillRect(f-62,a-32,30,30);b.lineWidth="3";b.lineJoin="round";b.strokeRect(f-62,a-32,30,30)},m=function(a,c,d,e){var b=a.ctx,f=a.$canvas.attr("width"),l=a.$canvas.attr("height");b.fillStyle="rgb("+c+","+d+","+e+")";b.fillRect(f-32,l-32,30,30);b.lineWidth="3";b.lineJoin="round";b.strokeRect(f-32,l-32,30,30);a.color=[c,d,e]};return{options:{},
_create:function(){if(this.element.get(0).tagName.toLowerCase()==="img")this.element.get(0).complete?k(this):this.element.bind("load",{that:this},function(a){k(a.data.that)})},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments);this.imageData=null;this.$canvas.remove();this.element.unbind('load');this.element.unwrap();this.element.show()},selectedColor:function(){return"#"+g(this.color[0])+g(this.color[1])+g(this.color[2])}}}();$.widget("ui.ImageColorPicker",n)})();


Handle spaces by quoting the command string instead of escaping

"""easy_extract module"""
__version__ = '0.1.2'
__license__ = 'GPL'

__author__ = 'Fantomas42'
__email__ = 'fantomas42@gmail.com'

__url__ = 'https://github.com/Fantomas42/easy-extract'

"""Archive collection modules"""
import os

CHAR_TO_ESCAPE = (' ', '(', ')', '*', "'", '"', '&')

class BaseFileCollection(object):
    """Base file collection"""

    def __init__(self, name, path='.', filenames=[]):
        self.name = name
        self.path = path
        self.filenames = filenames

    @property
    def files(self):
        return self.filenames

    def escape_filename(self, filename):
        """Escape a filename"""
        for char in CHAR_TO_ESCAPE:
            filename = filename.replace(char, '\%s' % char)
        return filename

    def get_path_filename(self, filename):
        """Concatenate path and filename"""
        return os.path.join(self.path, filename)

    def get_command_filename(self, filename):
        """Convert filename for command line"""
        return self.escape_filename(self.get_path_filename(filename))

    def remove(self):
        """Remove all files collection"""
        return os.system('rm -f %s' % ' '.join(
            [self.get_command_filename(f) for f in self.files]))

class MedKit(BaseFileCollection):
    """MedKit is collection of par2 files"""

    def __init__(self, name, path='.', filenames=[]):
        super(MedKit, self).__init__(name, path, filenames)
        self.medkits = []
        self.find_medkits(self.filenames)

    @property
    def files(self):
        return self.medkits

    def is_medkit_file(self, filename):
        """Check if the filename is a medkit"""
        return bool(filename.startswith(self.name) and
                    filename.lower().endswith('.par2'))

    def find_medkits(self, filenames=[]):
        """Find files for building the medkit"""
        for filename in filenames:
            if self.is_medkit_file(filename) and filename not in self.medkits:
                self.medkits.append(filename)
        self.medkits.sort()

    def check_and_repair(self, silent=False):
        """Check and repair with medkits"""
        if self.medkits:
            options = silent and '-qq' or ''
            root_medkit = self.get_command_filename(self.medkits[0])
            extra_kits = '%s*' % self.get_command_filename(self.name)
            command = 'par2 r %s %s %s' % (options, root_medkit, extra_kits)
            result = os.system(command)
            return bool(not result)
        return False

class Archive(MedKit):
    """Archive is a collection of archive files and a MedKit"""
    archive_type = 'undefined'
    ALLOWED_EXTENSIONS = []

    def __init__(self, name, path='.', filenames=[]):
        super(Archive, self).__init__(name, path, filenames)
        self.archives = []
        self.find_archives(self.filenames)

    @property
    def files(self):
        return self.archives + self.medkits

    @classmethod
    def is_archive_file(cls, filename):
        """Check if the filename is allowed for the Archive"""
        for regext in cls.ALLOWED_EXTENSIONS:
            if regext.search(filename):
                return regext.split(filename)[0]
        return False

    def find_archives(self, filenames=[]):
        """Find files for building the archive"""
        for filename in filenames:
            if (filename.startswith(self.name) and
                self.is_archive_file(filename) and
                    filename not in self.archives):
                self.archives.append(filename)
        self.archives.sort()

    def extract(self, repair=True):
        """Extract the archive and do integrity checking"""
        extraction = self._extract()

        if not extraction and repair:
            if self.check_and_repair():
                extraction = self._extract()

        return extraction

    def _extract(self):
        """Extract the archive"""
        raise NotImplementedError

    def __str__(self):
        if self.medkits:
            return '%s (%i %s archives, %i par2 files)' % (
                self.name, len(self.archives),
                self.archive_type, len(self.medkits))
        return '%s (%i %s archives)' % (
            self.name, len(self.archives), self.archive_type)

"""HJ Split archive format"""
import os
import re

from easy_extract.archive import Archive

EXTENSIONS = [re.compile('\.\d{3}$', re.I)]

class HJSplitArchive(Archive):
    """The HJ Split format"""
    archive_type = 'hj-split'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        new_filename = self.escape_filename(self.name)
        first_archive = self.get_command_filename(self.archives[0])

        print 'Extracting %s...' % new_filename

        os.system('cat %s > %s' % (first_archive, new_filename))

        for archive in self.archives[1:]:
            archive = self.get_command_filename(archive)
            os.system('cat %s >> %s' % (archive, new_filename))

        return True

    def remove(self):
        pass

"""Media archive file format"""
import os
import re

from easy_extract.archive import Archive

RAW_EXTENSIONS = ['\.AVI', '\.OGG', '\.OGV',
                  '\.MP4', '\.MPG', '\.MPEG',
                  '\.MKV', '\.M4V']

EXTENSIONS = [re.compile('%s$' % ext, re.I) for ext in RAW_EXTENSIONS]

class MediaArchive(Archive):
    """The Rar format Archive"""
    archive_type = 'media'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        first_archive = self.get_command_filename(self.archives[0])
        return not os.system('synoindex -a %s' % first_archive)

    def remove(self):
        pass

"""Rar archive format"""
import os
import re

from easy_extract.archive import Archive

EXTENSIONS = [re.compile('\.r\d{2}$', re.I),
              re.compile('\.part\d+\.rar$', re.I),
              re.compile('\.rar$', re.I)]

class RarArchive(Archive):
    """The Rar format Archive"""
    archive_type = 'rar'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        if '%s.rar' % self.name in self.archives:
            first_archive = self.get_command_filename('%s.rar' % self.name)
        else:
            first_archive = self.get_command_filename(self.archives[0])

        return not os.system('unrar e -o+ %s' % first_archive)

"""7zip archive format"""
import os
import re

from easy_extract.archive import Archive

RAW_EXTENSIONS = ['\.ARJ', '\.CAB', '\.CHM', '\.CPIO',
                  '\.DMG', '\.HFS', '\.LZH', '\.LZMA',
                  '\.NSIS', '\.UDF', '\.WIM', '\.XAR',
                  '\.Z', '\.ZIP', '\.GZIP', '\.TAR']

EXTENSIONS = [re.compile('%s$' % ext, re.I) for ext in RAW_EXTENSIONS]

class SevenZipArchive(Archive):
    """The 7z unarchiver is used for many formats"""
    archive_type = '7zip'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        first_archive = self.get_command_filename(self.archives[0])
        return not os.system('7z e -y %s' % first_archive)

"""Xtm archive format"""
import os
import re

from easy_extract.archive import Archive

EXTENSIONS = [re.compile('\.\d{3}\.xtm$', re.I),
              re.compile('\.xtm$', re.I)]

class XtmArchive(Archive):
    """The XTM archive format"""
    archive_type = 'xtm'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        new_filename = self.escape_filename(self.name)
        first_archive = self.get_command_filename(self.archives[0])

        print 'Extracting %s...' % new_filename

        os.system('dd if=%s skip=1 ibs=104 status=noxfer > %s 2>/dev/null' %
                  (first_archive, new_filename))

        for archive in self.archives[1:]:
            archive = self.get_command_filename(archive)
            os.system('cat %s >> %s' % (archive, new_filename))

        return True

    def remove(self):
        pass

"""easy_extract module"""
__version__ = '0.1.3'
__license__ = 'GPL'

__author__ = 'Fantomas42'
__email__ = 'fantomas42@gmail.com'

__url__ = 'https://github.com/Fantomas42/easy-extract'

"""Archive collection modules"""
import os

CHAR_TO_ESCAPE = ('(', ')', '*', "'", '"', '&')

class BaseFileCollection(object):
    """Base file collection"""

    def __init__(self, name, path='.', filenames=[]):
        self.name = name
        self.path = path
        self.filenames = filenames

    @property
    def files(self):
        return self.filenames

    def escape_filename(self, filename):
        """Escape a filename"""
        for char in CHAR_TO_ESCAPE:
            filename = filename.replace(char, '\%s' % char)
        return filename

    def get_path_filename(self, filename):
        """Concatenate path and filename"""
        return os.path.join(self.path, filename)

    def get_command_filename(self, filename):
        """Convert filename for command line"""
        return self.escape_filename(self.get_path_filename(filename))

    def remove(self):
        """Remove all files collection"""
        return os.system('rm -f %s' % ' '.join(
            [self.get_command_filename(f) for f in self.files]))

class MedKit(BaseFileCollection):
    """MedKit is collection of par2 files"""

    def __init__(self, name, path='.', filenames=[]):
        super(MedKit, self).__init__(name, path, filenames)
        self.medkits = []
        self.find_medkits(self.filenames)

    @property
    def files(self):
        return self.medkits

    def is_medkit_file(self, filename):
        """Check if the filename is a medkit"""
        return bool(filename.startswith(self.name) and
                    filename.lower().endswith('.par2'))

    def find_medkits(self, filenames=[]):
        """Find files for building the medkit"""
        for filename in filenames:
            if self.is_medkit_file(filename) and filename not in self.medkits:
                self.medkits.append(filename)
        self.medkits.sort()

    def check_and_repair(self, silent=False):
        """Check and repair with medkits"""
        if self.medkits:
            options = silent and '-qq' or ''
            root_medkit = self.get_command_filename(self.medkits[0])
            extra_kits = '%s*' % self.get_command_filename(self.name)
            command = 'par2 r %s %s %s' % (options, root_medkit, extra_kits)
            result = os.system(command)
            return bool(not result)
        return False

class Archive(MedKit):
    """Archive is a collection of archive files and a MedKit"""
    archive_type = 'undefined'
    ALLOWED_EXTENSIONS = []

    def __init__(self, name, path='.', filenames=[]):
        super(Archive, self).__init__(name, path, filenames)
        self.archives = []
        self.find_archives(self.filenames)

    @property
    def files(self):
        return self.archives + self.medkits

    @classmethod
    def is_archive_file(cls, filename):
        """Check if the filename is allowed for the Archive"""
        for regext in cls.ALLOWED_EXTENSIONS:
            if regext.search(filename):
                return regext.split(filename)[0]
        return False

    def find_archives(self, filenames=[]):
        """Find files for building the archive"""
        for filename in filenames:
            if (filename.startswith(self.name) and
                self.is_archive_file(filename) and
                    filename not in self.archives):
                self.archives.append(filename)
        self.archives.sort()

    def extract(self, repair=True):
        """Extract the archive and do integrity checking"""
        extraction = self._extract()

        if not extraction and repair:
            if self.check_and_repair():
                extraction = self._extract()

        return extraction

    def _extract(self):
        """Extract the archive"""
        raise NotImplementedError

    def __str__(self):
        if self.medkits:
            return '%s (%i %s archives, %i par2 files)' % (
                self.name, len(self.archives),
                self.archive_type, len(self.medkits))
        return '%s (%i %s archives)' % (
            self.name, len(self.archives), self.archive_type)

"""HJ Split archive format"""
import os
import re

from easy_extract.archive import Archive

EXTENSIONS = [re.compile('\.\d{3}$', re.I)]

class HJSplitArchive(Archive):
    """The HJ Split format"""
    archive_type = 'hj-split'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        new_filename = self.escape_filename(self.name)
        first_archive = self.get_command_filename(self.archives[0])

        print 'Extracting %s...' % new_filename

        os.system('cat "%s" > "%s"' % (first_archive, new_filename))

        for archive in self.archives[1:]:
            archive = self.get_command_filename(archive)
            os.system('cat "%s" >> "%s"' % (archive, new_filename))

        return True

    def remove(self):
        pass

"""Media archive file format"""
import os
import re

from easy_extract.archive import Archive

RAW_EXTENSIONS = ['\.AVI', '\.OGG', '\.OGV',
                  '\.MP4', '\.MPG', '\.MPEG',
                  '\.MKV', '\.M4V']

EXTENSIONS = [re.compile('%s$' % ext, re.I) for ext in RAW_EXTENSIONS]

class MediaArchive(Archive):
    """The Rar format Archive"""
    archive_type = 'media'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        first_archive = self.get_command_filename(self.archives[0])
        return not os.system('synoindex -a "%s"' % first_archive)

    def remove(self):
        pass

"""Rar archive format"""
import os
import re

from easy_extract.archive import Archive

EXTENSIONS = [re.compile('\.r\d{2}$', re.I),
              re.compile('\.part\d+\.rar$', re.I),
              re.compile('\.rar$', re.I)]

class RarArchive(Archive):
    """The Rar format Archive"""
    archive_type = 'rar'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        if '%s.rar' % self.name in self.archives:
            first_archive = self.get_command_filename('%s.rar' % self.name)
        else:
            first_archive = self.get_command_filename(self.archives[0])

        return not os.system('unrar e -o+ "%s"' % first_archive)

"""7zip archive format"""
import os
import re

from easy_extract.archive import Archive

RAW_EXTENSIONS = ['\.ARJ', '\.CAB', '\.CHM', '\.CPIO',
                  '\.DMG', '\.HFS', '\.LZH', '\.LZMA',
                  '\.NSIS', '\.UDF', '\.WIM', '\.XAR',
                  '\.Z', '\.ZIP', '\.GZIP', '\.TAR']

EXTENSIONS = [re.compile('%s$' % ext, re.I) for ext in RAW_EXTENSIONS]

class SevenZipArchive(Archive):
    """The 7z unarchiver is used for many formats"""
    archive_type = '7zip'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        first_archive = self.get_command_filename(self.archives[0])
        return not os.system('7z e -y "%s"' % first_archive)

"""Xtm archive format"""
import os
import re

from easy_extract.archive import Archive

EXTENSIONS = [re.compile('\.\d{3}\.xtm$', re.I),
              re.compile('\.xtm$', re.I)]

class XtmArchive(Archive):
    """The XTM archive format"""
    archive_type = 'xtm'
    ALLOWED_EXTENSIONS = EXTENSIONS

    def _extract(self):
        new_filename = self.escape_filename(self.name)
        first_archive = self.get_command_filename(self.archives[0])

        print 'Extracting %s...' % new_filename

        os.system('dd if="%s" skip=1 ibs=104 status=noxfer > "%s" 2>/dev/null'
                  % (first_archive, new_filename))

        for archive in self.archives[1:]:
            archive = self.get_command_filename(archive)
            os.system('cat "%s" >> "%s"' % (archive, new_filename))

        return True

    def remove(self):
        pass


make include folder and various bug-fixes

#include "bank.h"
#include <openssl/asn1.h>

static BIO *dout;
static BIO *mout;

const char _NL[]="\n";

void SetDumper(BIO *out)
    {
    dout=out;
    if(!mout)
	mout=out;
    }

void SetDumper(FILE *f)
    {
    BIO *out=BIO_new(BIO_s_file());
    assert(out);
    BIO_set_fp(out,f,BIO_NOCLOSE);
    SetDumper(out);
    }

void SetMonitor(BIO *out)
    { mout=out; }

void SetMonitor(FILE *f)
    {
    BIO *out=BIO_new(BIO_s_file());
    assert(out);
    BIO_set_fp(out,f,BIO_NOCLOSE);
    SetMonitor(out);
    }

BIGNUM *ReadNumber(BIO *in,const char *szTitle)
    {
    char szLine[10240];
    unsigned char aucBN[1024];
    int nTLen=strlen(szTitle);

    BIO_gets(in,szLine,sizeof szLine-1);
    if(strncmp(szLine,szTitle,nTLen))
	{
	fprintf(stderr,"Got %s, expected %s\n",szLine,szTitle);
	assert(!"Unexpected input");
	return NULL;
	}
    BIGNUM *bn=BN_new();

    int n=strcspn(szLine+nTLen,"\r\n");
    szLine[nTLen+n]='\0';
    if(n&1)
	{
	memmove(szLine+nTLen+1,szLine+nTLen,n+1);
	szLine[nTLen]='0';
	}

    for(n=0 ; szLine[nTLen+n*2] ; ++n)
	{
	int h;

	sscanf(&szLine[nTLen+n*2],"%02x",&h);
	aucBN[n]=(unsigned char)h;
	}

    BN_bin2bn(aucBN,n,bn);

    return bn;
    }

void DumpNumber(BIO *out,const char *szTitle,const BIGNUM *bn,
		const char *szTrailer)
    {
    if(!out)
	return;
    BIO_puts(out,szTitle);
    if(!bn)
	BIO_puts(out,"(null)");
    else
	BN_print(out,bn);
    BIO_puts(out,szTrailer);
    }

void DumpNumber(const char *szTitle,const BIGNUM *bn,const char *szTrailer)
    { DumpNumber(dout,szTitle,bn,szTrailer); }

void HexDump(BIO *out,const char *szTitle,const unsigned char *acBuf,
	     int nLength)
    {
    if(!out)
	return;
    BIO_puts(out,szTitle);
    for(int n=0 ; n < nLength ; ++n)
	{
	char buf[3];
	sprintf(buf,"%02X",acBuf[n]);
	BIO_puts(out,buf);
	}
    BIO_puts(out,"\n");
    }

void HexDump(const char *szTitle,const unsigned char *acBuf,int nLength)
    { HexDump(dout,szTitle,acBuf,nLength); }

PublicBank::PublicBank(Bank &bank)
    {
    m_pDH=DH_new();
    m_pDH->g=BN_dup(bank.g());
    m_pDH->p=BN_dup(bank.p());
    m_pDH->pub_key=BN_dup(bank.pub_key());
    }

void Bank::cb(int n, int, void */*arg*/)
    {
    if(!mout)
	return;

    char c='*';

    if (n == 0) c='.';
    if (n == 1) c='+';
    if (n == 2) c='*';
    if (n == 3) c='\n';
    BIO_write(mout,&c,1);
    BIO_flush(mout);
    }

/*const*/ BIGNUM *Bank::SignRequest(PublicCoinRequest &req)
    {
    InitCTX();

    BIGNUM *BtoA=BN_new();
    BN_mod_exp(BtoA,req.Request(),priv_key(),p(),m_ctx);
    DumpNumber("B->A=     ",BtoA);

    return BtoA;
    }

boolean Bank::Verify(Coin &coin)
    {
    InitCTX();

    BIGNUM *t=BN_new();
    if(!coin.GenerateCoinNumber(t,*this))
	return false;
    BN_mod_exp(t,t,priv_key(),p(),m_ctx);
    DumpNumber("y^k=      ",t);

    BN_sub(t,t,coin.Signature());
    boolean bRet=BN_is_zero(t);

    BN_free(t);

    return bRet;
    }

void Bank::WriteBIO(BIO *bio)
    {
    PublicBank::WriteBIO(bio);
    DumpNumber(bio,"private=",priv_key());
    }

void Bank::ReadBIO(BIO *bio)
    {
    PublicBank::ReadBIO(bio);
    m_pDH->priv_key=ReadNumber(bio,"private=");
    }

void PublicBank::WriteBIO(BIO *bio)
    {
    DumpNumber(bio,"g=",g());
    DumpNumber(bio,"p=",p());
    DumpNumber(bio,"public=",pub_key());
    }

void PublicBank::ReadBIO(BIO *bio)
    {
    m_pDH=DH_new();

    m_pDH->g=ReadNumber(bio,"g=");
    m_pDH->p=ReadNumber(bio,"p=");
    m_pDH->pub_key=ReadNumber(bio,"public=");

    Dump();
    }

void UnsignedCoin::WriteBIO(BIO *bio)
    {
    DumpNumber(bio,"id=",m_bnCoinID);
    }

void UnsignedCoin::ReadBIO(BIO *bio)
    {
    m_bnCoinID=ReadNumber(bio,"id=");
    }

void Coin::WriteBIO(BIO *bio)
    {
    UnsignedCoin::WriteBIO(bio);
    DumpNumber(bio,"signature=",m_bnCoinSignature);
    }

void Coin::ReadBIO(BIO *bio)
    {
    UnsignedCoin::ReadBIO(bio);
    m_bnCoinSignature=ReadNumber(bio,"signature=");
    Dump();
    }

void PublicCoinRequest::WriteBIO(BIO *bio)
    {
    DumpNumber(bio,"request=",m_bnCoinRequest);
    }

void PublicCoinRequest::ReadBIO(BIO *bio)
    {
    m_bnCoinRequest=ReadNumber(bio,"request=");
    }

void CoinRequest::WriteBIO(BIO *bio)
    {
    PublicCoinRequest::WriteBIO(bio);
    m_coin.WriteBIO(bio);
    DumpNumber(bio,"blinding=",m_bnBlindingFactor);
    }

void CoinRequest::ReadBIO(BIO *bio)
    {
    PublicCoinRequest::ReadBIO(bio);
    m_coin.ReadBIO(bio);
    m_bnBlindingFactor=ReadNumber(bio,"blinding=");

    Dump();
    }

#ifdef __APPLE__
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

#include "bank.h"
#include <openssl/asn1.h>

#ifdef _WIN32
#include <string.h>
#endif

static BIO *dout;
static BIO *mout;

const char _NL[]="\n";

void SetDumper(BIO *out)
    {
    dout=out;
    if(!mout)
	mout=out;
    }

void SetDumper(FILE *f)
    {
    BIO *out=BIO_new(BIO_s_file());
    assert(out);
    BIO_set_fp(out,f,BIO_NOCLOSE);
    SetDumper(out);
    }

void SetMonitor(BIO *out)
    { mout=out; }

void SetMonitor(FILE *f)
    {
    BIO *out=BIO_new(BIO_s_file());
    assert(out);
    BIO_set_fp(out,f,BIO_NOCLOSE);
    SetMonitor(out);
    }

BIGNUM *ReadNumber(BIO *in,const char *szTitle)
    {
    char szLine[10240];
    unsigned char aucBN[1024];
    int nTLen=strlen(szTitle);

    BIO_gets(in,szLine,sizeof szLine-1);
    if(strncmp(szLine,szTitle,nTLen))
	{
	fprintf(stderr,"Got %s, expected %s\n",szLine,szTitle);
	assert(!"Unexpected input");
	return NULL;
	}
    BIGNUM *bn=BN_new();

    int n=strcspn(szLine+nTLen,"\r\n");
    szLine[nTLen+n]='\0';
    if(n&1)
	{
	memmove(szLine+nTLen+1,szLine+nTLen,n+1);
	szLine[nTLen]='0';
	}

    for(n=0 ; szLine[nTLen+n*2] ; ++n)
	{
	int h;

	sscanf(&szLine[nTLen+n*2],"%02x",&h);
	aucBN[n]=(unsigned char)h;
	}

    BN_bin2bn(aucBN,n,bn);

    return bn;
    }

void DumpNumber(BIO *out,const char *szTitle,const BIGNUM *bn,
		const char *szTrailer)
    {
    if(!out)
	return;
    BIO_puts(out,szTitle);
    if(!bn)
	BIO_puts(out,"(null)");
    else
	BN_print(out,bn);
    BIO_puts(out,szTrailer);
    }

void DumpNumber(const char *szTitle,const BIGNUM *bn,const char *szTrailer)
    { DumpNumber(dout,szTitle,bn,szTrailer); }

void HexDump(BIO *out,const char *szTitle,const unsigned char *acBuf,
	     int nLength)
    {
    if(!out)
	return;
    BIO_puts(out,szTitle);
    for(int n=0 ; n < nLength ; ++n)
	{
	char buf[3];
	sprintf(buf,"%02X",acBuf[n]);
	BIO_puts(out,buf);
	}
    BIO_puts(out,"\n");
    }

void HexDump(const char *szTitle,const unsigned char *acBuf,int nLength)
    { HexDump(dout,szTitle,acBuf,nLength); }

PublicBank::PublicBank(Bank &bank)
    {
    m_pDH=DH_new();
    m_pDH->g=BN_dup(bank.g());
    m_pDH->p=BN_dup(bank.p());
    m_pDH->pub_key=BN_dup(bank.pub_key());
    }

void Bank::cb(int n, int, void * /*arg*/)
    {
    if(!mout)
	return;

    char c='*';

    if (n == 0) c='.';
    if (n == 1) c='+';
    if (n == 2) c='*';
    if (n == 3) c='\n';
    BIO_write(mout,&c,1);
    BIO_flush(mout);
    }

/*const*/ BIGNUM *Bank::SignRequest(PublicCoinRequest &req)
    {
    InitCTX();

    BIGNUM *BtoA=BN_new();
    BN_mod_exp(BtoA,req.Request(),priv_key(),p(),m_ctx);
    DumpNumber("B->A=     ",BtoA);

    return BtoA;
    }

boolean Bank::Verify(Coin &coin)
    {
    InitCTX();

    BIGNUM *t=BN_new();
    if(!coin.GenerateCoinNumber(t,*this))
	return false;
    BN_mod_exp(t,t,priv_key(),p(),m_ctx);
    DumpNumber("y^k=      ",t);

    BN_sub(t,t,coin.Signature());
    boolean bRet=BN_is_zero(t);

    BN_free(t);

    return bRet;
    }

void Bank::WriteBIO(BIO *bio)
    {
    PublicBank::WriteBIO(bio);
    DumpNumber(bio,"private=",priv_key());
    }

void Bank::ReadBIO(BIO *bio)
    {
    PublicBank::ReadBIO(bio);
    m_pDH->priv_key=ReadNumber(bio,"private=");
    }

void PublicBank::WriteBIO(BIO *bio)
    {
    DumpNumber(bio,"g=",g());
    DumpNumber(bio,"p=",p());
    DumpNumber(bio,"public=",pub_key());
    }

void PublicBank::ReadBIO(BIO *bio)
    {
    m_pDH=DH_new();

    m_pDH->g=ReadNumber(bio,"g=");
    m_pDH->p=ReadNumber(bio,"p=");
    m_pDH->pub_key=ReadNumber(bio,"public=");

    Dump();
    }

void UnsignedCoin::WriteBIO(BIO *bio)
    {
    DumpNumber(bio,"id=",m_bnCoinID);
    }

void UnsignedCoin::ReadBIO(BIO *bio)
    {
    m_bnCoinID=ReadNumber(bio,"id=");
    }

void Coin::WriteBIO(BIO *bio)
    {
    UnsignedCoin::WriteBIO(bio);
    DumpNumber(bio,"signature=",m_bnCoinSignature);
    }

void Coin::ReadBIO(BIO *bio)
    {
    UnsignedCoin::ReadBIO(bio);
    m_bnCoinSignature=ReadNumber(bio,"signature=");
    Dump();
    }

void PublicCoinRequest::WriteBIO(BIO *bio)
    {
    DumpNumber(bio,"request=",m_bnCoinRequest);
    }

void PublicCoinRequest::ReadBIO(BIO *bio)
    {
    m_bnCoinRequest=ReadNumber(bio,"request=");
    }

void CoinRequest::WriteBIO(BIO *bio)
    {
    PublicCoinRequest::WriteBIO(bio);
    m_coin.WriteBIO(bio);
    DumpNumber(bio,"blinding=",m_bnBlindingFactor);
    }

void CoinRequest::ReadBIO(BIO *bio)
    {
    PublicCoinRequest::ReadBIO(bio);
    m_coin.ReadBIO(bio);
    m_bnBlindingFactor=ReadNumber(bio,"blinding=");

    Dump();
    }


adding fix to return the correct datetimestamp for rails generator

module ActsAsArchive
  module Migration

    def self.included(base)
      unless base.included_modules.include?(InstanceMethods)
        base.send :extend, ClassMethods
        base.class_eval do
          class <<self
            unless method_defined?(:method_missing_without_archive)
              alias_method :method_missing_without_archive, :method_missing
              alias_method :method_missing, :method_missing_with_archive
            end
          end
        end
      end
    end

    module ClassMethods

      def method_missing_with_archive(method, *arguments, &block)
        args = Marshal.load(Marshal.dump(arguments))
        method_missing_without_archive(method, *arguments, &block)
        supported = [
          :add_column, :add_timestamps, :change_column,
          :change_column_default, :change_table,
          :drop_table, :remove_column, :remove_columns,
          :remove_timestamps, :rename_column, :rename_table
        ]
        if args.include?(:deleted_at) || args.include?('deleted_at')
          # Don't change the archive's deleted_at column
          return
        end
        if !args.empty? && supported.include?(method)
          connection = ActiveRecord::Base.connection
          args[0] = "archived_" + ActiveRecord::Migrator.proper_table_name(args[0])
          if method == :rename_table
            args[1] = "archived_" + args[1].to_s
          end
          if connection.table_exists?(args[0])
            connection.send(method, *args, &block)
          end
        end
      end
    end

    module InstanceMethods
    end
  end
end

module ActsAsArchive
  module Migration

    def self.included(base)
      unless base.included_modules.include?(InstanceMethods)
        base.send :extend, ClassMethods
        base.class_eval do
          class <<self
            unless method_defined?(:method_missing_without_archive)
              alias_method :method_missing_without_archive, :method_missing
              alias_method :method_missing, :method_missing_with_archive
            end
          end
        end
      end
    end

    module ClassMethods

      def method_missing_with_archive(method, *arguments, &block)
        args = Marshal.load(Marshal.dump(arguments))
        method_missing_without_archive(method, *arguments, &block)
        supported = [
          :add_column, :add_timestamps, :change_column,
          :change_column_default, :change_table,
          :drop_table, :remove_column, :remove_columns,
          :remove_timestamps, :rename_column, :rename_table
        ]
        if args.include?(:deleted_at) || args.include?('deleted_at')
          # Don't change the archive's deleted_at column
          return
        end
        if !args.empty? && supported.include?(method)
          connection = ActiveRecord::Base.connection
          args[0] = "archived_" + ActiveRecord::Migrator.proper_table_name(args[0])
          if method == :rename_table
            args[1] = "archived_" + args[1].to_s
          end
          if connection.table_exists?(args[0])
            connection.send(method, *args, &block)
          end
        end
        args[0]
      end
    end

    module InstanceMethods
    end
  end
end


Create entity class

require 'httparty'

%w[extras/httparty_icebox extras/attributes video genre person image country studio cast_member movie extras/movie_hasher].each do |class_name|
  require "tmdb_party/#{class_name}"
end

module TMDBParty
  class Base
    include HTTParty
    include HTTParty::Icebox
    cache :store => 'file', :timeout => 120, :location => Dir.tmpdir

    base_uri 'http://api.themoviedb.org/2.1'
    format :json

    def initialize(key, lang = 'en')
      @api_key = key
      @default_lang = lang
    end

    def search(query, lang = @default_lang)
      data = self.class.get(method_url('Movie.search', lang, query))

      result_or_empty_array(data, Movie)
    end

    # Read more about the parameters that can be passed to this method here:
    # http://api.themoviedb.org/2.1/methods/Movie.browse
    def browse(params = {}, lang = @default_lang)
      data = self.class.get(method_url('Movie.browse', lang), :query => {:order => "asc", :order_by => "title"}.merge(params))

      result_or_empty_array(data, Movie)
    end

    def search_person(query, lang = @default_lang)
      data = self.class.get(method_url('Person.search', lang, query))

      result_or_empty_array(data, Person)
    end

    def imdb_lookup(imdb_id, lang = @default_lang)
      data = self.class.get(method_url('Movie.imdbLookup', lang, imdb_id)).parsed_response
      if data.class != Array || data.first == "Nothing found."
        nil
      else
        Movie.new(data.first, self)
      end
    end

    def get_info(id, lang = @default_lang)
      data = self.class.get(method_url('Movie.getInfo', lang, id)).parsed_response
      Movie.new(data.first, self)
    end

    def get_file_info(file, lang=@default_lang)
      hash = TMDBParty::MovieHasher.compute_hash(file)
      bytesize = file.size
      data = self.class.get(method_url('Media.getInfo', lang, hash, bytesize))

      result_or_empty_array(data, Movie)
    end

    def get_person(id, lang = @default_lang)
      data = self.class.get(method_url('Person.getInfo', lang, id)).parsed_response
      Person.new(data.first, self)
    end

    def get_genres(lang = @default_lang)
      data = self.class.get(method_url('Genres.getList', lang)).parsed_response
      data[1..-1].collect { |genre| Genre.new(genre) } # Skips the first, see spec/fixtures/genres_results.json
    end

    private

    def result_or_empty_array(data, klass)
      data = data.parsed_response
      if data.class != Array || data.first == "Nothing found."
        []
      else
        data.collect { |object| klass.new(object, self) }
      end
    end

    def method_url(method, lang, *args)
      url = [method, lang, self.class.format, @api_key]
      url += args.collect{ |a| URI.escape(a.to_s) }
      '/' + url.join('/')
    end
  end
end

module TMDBParty
  class CastMember
    include Attributes
    attr_reader :tmdb
    attributes :name, :url, :job, :department
    attributes :id, :type => Integer

    def initialize(values, tmdb)
      @tmdb = tmdb
      self.attributes = values
    end

    def character_name
      read_attribute('character')
    end

    def image_url
      read_attribute('profile')
    end

    def person
      tmdb.get_person(id)
    end

    def self.parse(data, tmdb)
      return unless data
      if data.is_a?(Array)
        data.collect do |person|
          CastMember.new(person, tmdb)
        end
      else
        [CastMember.new(data, tmdb)]
      end
    end
  end
end

module TMDBParty
  class Category
    include Attributes
    attributes :name, :url

    def initialize(values)
      self.attributes = values
    end

    def self.parse(data)
      return unless data
      data = data["category"]
      if data.is_a?(Array)
        data.collect do |category|
          Category.new(category)
        end
      else
        [Category.new(data)]
      end
    end
  end
end

module TMDBParty
  class Genre
    include Attributes
    attributes :id, :type => Integer
    attributes :name, :url

    def initialize(values)
      self.attributes = values
    end

    def self.parse(data)
      return unless data
      if data.is_a?(Array)
        data.collect do |g|
          Genre.new(g)
        end
      else
        [Genre.new(data)]
      end
    end
  end
end

module TMDBParty
  class Movie
    include Attributes
    attr_reader :tmdb

    attributes :name, :overview, :id, :imdb_id, :movie_type, :url, :alternative_title, :translated, :certification
    attributes :released
    attributes :id, :popularity, :type => Integer
    attributes :score, :type => Float

    attributes :tagline, :lazy => :get_info!
    attributes :posters, :backdrops, :lazy => :get_info!, :type => Image
    attributes :homepage, :lazy => :get_info!
    attributes :trailer, :lazy => :get_info!
    attributes :runtime, :lazy => :get_info!, :type => Integer
    attributes :genres, :lazy => :get_info!, :type => Genre
    attributes :countries, :lazy => :get_info!, :type => Country
    attributes :studios, :lazy => :get_info!, :type => Studio

    alias_method :translated?, :translated

    def initialize(values, tmdb)
      @tmdb = tmdb
      self.attributes = values
    end

    def get_info!
      movie = tmdb.get_info(self.id)
      @attributes.merge!(movie.attributes) if movie
      @loaded = true
    end

    def cast
      # TODO: This needs refactoring
      CastMember.parse(read_or_load_attribute('cast', nil, :get_info!), tmdb)
    end

    def language
      read_attribute('language').downcase.to_sym
    end

    def last_modified_at
      # Date from TMDB is always in MST, but no timezone is present in date string
      Time.parse(read_attribute('last_modified_at') + ' MST')
    end

    def directors
      find_cast('Directing')
    end

    def actors
      find_cast('Actors')
    end

    def writers
      find_cast('Writing')
    end

    def producers
      find_cast('Production')
    end

    private

    def find_cast(type)
      return [] unless cast
      guys = cast.select{|c| c.department == type}
    end

  end

end

module TMDBParty
  class Person
    include Attributes
    attr_reader :tmdb
    attributes :id, :popularity, :type => Integer
    attributes :score, :type => Float
    attributes :name, :url, :biography

    attributes :birthplace, :birthday, :lazy => :get_info!

    def initialize(values, tmdb)
      @tmdb = tmdb
      self.attributes = values
    end

    def biography
      # HTTParty does not parse the encoded hexadecimal properly. It does not consider 000F to be a hex, but 000f is
      # A bug has been submitted about this
      read_attribute('biography').gsub("\\n", "\n").gsub(/\\u([0-9A-F]{4})/) { [$1.hex].pack("U") }
    end

    def get_info!
      person = tmdb.get_person(self.id)
      @attributes.merge!(person.attributes) if person
      @loaded = true
    end
  end
end

module TMDBParty
  class Studio
    include Attributes
    attributes :name, :url

    def self.parse(data)
      return unless data
      if data.is_a?(Array)
        data.map { |row| Studio.new(row) }
      else
        [Studio.new(data)]
      end
    end

    def initialize(attributes)
      self.attributes = attributes
    end
  end
end

module TMDBParty
  class Video
    attr_reader :url

    def initialize(url)
      @url = url
    end

    def self.parse(data)
      return unless data
      if data.is_a?(Array)
        data.collect do |url|
          Video.new(url)
        end
      else
        Video.new(data)
      end
    end
  end
end

require 'httparty'

%w[extras/httparty_icebox extras/attributes entity video genre person image country studio cast_member movie extras/movie_hasher].each do |class_name|
  require "tmdb_party/#{class_name}"
end

module TMDBParty
  class Base
    include HTTParty
    include HTTParty::Icebox
    cache :store => 'file', :timeout => 120, :location => Dir.tmpdir

    base_uri 'http://api.themoviedb.org/2.1'
    format :json

    def initialize(key, lang = 'en')
      @api_key = key
      @default_lang = lang
    end

    def search(query, lang = @default_lang)
      data = self.class.get(method_url('Movie.search', lang, query))

      result_or_empty_array(data, Movie)
    end

    # Read more about the parameters that can be passed to this method here:
    # http://api.themoviedb.org/2.1/methods/Movie.browse
    def browse(params = {}, lang = @default_lang)
      data = self.class.get(method_url('Movie.browse', lang), :query => {:order => "asc", :order_by => "title"}.merge(params))

      result_or_empty_array(data, Movie)
    end

    def search_person(query, lang = @default_lang)
      data = self.class.get(method_url('Person.search', lang, query))

      result_or_empty_array(data, Person)
    end

    def imdb_lookup(imdb_id, lang = @default_lang)
      data = self.class.get(method_url('Movie.imdbLookup', lang, imdb_id)).parsed_response
      if data.class != Array || data.first == "Nothing found."
        nil
      else
        Movie.new(data.first, self)
      end
    end

    def get_info(id, lang = @default_lang)
      data = self.class.get(method_url('Movie.getInfo', lang, id)).parsed_response
      Movie.new(data.first, self)
    end

    def get_file_info(file, lang=@default_lang)
      hash = TMDBParty::MovieHasher.compute_hash(file)
      bytesize = file.size
      data = self.class.get(method_url('Media.getInfo', lang, hash, bytesize))

      result_or_empty_array(data, Movie)
    end

    def get_person(id, lang = @default_lang)
      data = self.class.get(method_url('Person.getInfo', lang, id)).parsed_response
      Person.new(data.first, self)
    end

    def get_genres(lang = @default_lang)
      data = self.class.get(method_url('Genres.getList', lang)).parsed_response
      data[1..-1].collect { |genre| Genre.new(genre) } # Skips the first, see spec/fixtures/genres_results.json
    end

    private

    def result_or_empty_array(data, klass)
      data = data.parsed_response
      if data.class != Array || data.first == "Nothing found."
        []
      else
        data.collect { |object| klass.new(object, self) }
      end
    end

    def method_url(method, lang, *args)
      url = [method, lang, self.class.format, @api_key]
      url += args.collect{ |a| URI.escape(a.to_s) }
      '/' + url.join('/')
    end
  end
end

module TMDBParty
  class CastMember < Entity
    attr_reader :tmdb
    attributes :name, :url, :job, :department
    attributes :id, :type => Integer

    def character_name
      read_attribute('character')
    end

    def image_url
      read_attribute('profile')
    end

    def person
      tmdb.get_person(id)
    end
  end
end

module TMDBParty
  class Category < Entity
    attributes :name, :url
  end
end

module TMDBParty
  class Entity
    include Attributes
    def initialize(values, tmdb=nil)
      @tmdb = tmdb
      self.attributes = values
    end

    def self.parse(data, tmdb=nil)
      return unless data
      if data.is_a?(Array)
        data.collect do |person|
          self.new(person, tmdb)
        end
      else
        [self.new(data, tmdb)]
      end
    end
  end
end

module TMDBParty
  class Genre < Entity
    attributes :id, :type => Integer
    attributes :name, :url
  end
end

module TMDBParty
  class Movie < Entity
    attr_reader :tmdb

    attributes :name, :overview, :id, :imdb_id, :movie_type, :url, :alternative_title, :translated, :certification
    attributes :released
    attributes :id, :popularity, :type => Integer
    attributes :score, :type => Float

    attributes :tagline, :lazy => :get_info!
    attributes :posters, :backdrops, :lazy => :get_info!, :type => Image
    attributes :homepage, :lazy => :get_info!
    attributes :trailer, :lazy => :get_info!
    attributes :runtime, :lazy => :get_info!, :type => Integer
    attributes :genres, :lazy => :get_info!, :type => Genre
    attributes :countries, :lazy => :get_info!, :type => Country
    attributes :studios, :lazy => :get_info!, :type => Studio

    alias_method :translated?, :translated

    def get_info!
      movie = tmdb.get_info(self.id)
      @attributes.merge!(movie.attributes) if movie
      @loaded = true
    end

    def cast
      # TODO: This needs refactoring
      CastMember.parse(read_or_load_attribute('cast', nil, :get_info!), tmdb)
    end

    def language
      read_attribute('language').downcase.to_sym
    end

    def last_modified_at
      # Date from TMDB is always in MST, but no timezone is present in date string
      Time.parse(read_attribute('last_modified_at') + ' MST')
    end

    def directors
      find_cast('Directing')
    end

    def actors
      find_cast('Actors')
    end

    def writers
      find_cast('Writing')
    end

    def producers
      find_cast('Production')
    end

    private

    def find_cast(type)
      return [] unless cast
      guys = cast.select{|c| c.department == type}
    end

  end

end

module TMDBParty
  class Person < Entity
    attr_reader :tmdb
    attributes :id, :popularity, :type => Integer
    attributes :score, :type => Float
    attributes :name, :url, :biography

    attributes :birthplace, :birthday, :lazy => :get_info!

    def biography
      # HTTParty does not parse the encoded hexadecimal properly. It does not consider 000F to be a hex, but 000f is
      # A bug has been submitted about this
      read_attribute('biography').gsub("\\n", "\n").gsub(/\\u([0-9A-F]{4})/) { [$1.hex].pack("U") }
    end

    def get_info!
      person = tmdb.get_person(self.id)
      @attributes.merge!(person.attributes) if person
      @loaded = true
    end
  end
end

module TMDBParty
  class Studio < Entity
    attributes :name, :url
  end
end

module TMDBParty
  class Video < Entity
    attr_reader :url

    def initialize(url)
      super
      @url = url
    end
  end
end