import os
from torch.autograd import Variable
from torch.optim import lr_scheduler
import torch.nn.functional as F
import torch.nn as nn
from utils import sgd, crossentropyloss, fix_seed, write_log, compute_accuracy, binarycrossentropy, rmsprop
from dice_loss import GeneralizedDiceLoss, WeightedCrossEntropyLoss
import numpy as np
import torch.utils.data as data
import torch.optim as optim
import os.path
import torch
from utils3D import cross_entropy_dice
from data_reader_unet_spine import *
from torchvision import transforms
from torch.utils.data.sampler import SubsetRandomSampler, SequentialSampler
import Unet3D_meta_learning
import scipy.misc
import sklearn as sk
from sklearn.metrics import accuracy_score
from tensorboardX import SummaryWriter
from sklearn.utils import shuffle
import torchvision

class ModelBaseline:
    def __init__(self):
        self.batch_size = 1
        self.num_classes = 2
        self.unseen_index = 0 ####### Attention!! to need to change #######
        self.lr = 0.001
        self.inner_loops = 1200
        self.step_size = 20
        self.step_size_unpad = 20
        self.weight_decay = 0.00005
        self.momentum = 0.9
        self.state_dict = ''
        self.logs = 'logs'
        self.patch_size = 64
        self.test_every = 40
        self.test_unseen = 100
        self.epochs = 20

        self.writer = SummaryWriter(comment='inference')

        torch.set_default_tensor_type('torch.cuda.DoubleTensor')

        self.TrainMetaData = []
        self.ValidMetaData = []
        self.TestMetaData = []
        self.FewShotData = []

        self.count1 = 0
        self.count2 = 0
        self.count3 = 0

        self.setup_path()
        self.network = Unet3D_meta_learning.Unet3D()  # load the vanilla 3D-Unet

        self.network = self.network.cuda()

        self.configure()

    def setup_path(self):
        modality = 'dummy_for_now'

        self.TrainMetaData = []
        self.ValidMetaData = []
        self.TestMetaData = []
        self.FewShotData = []

        self.train_paths = ['/home/pulkit/code_tmi/train_fat.txt',
                            '/home/pulkit/code_tmi/train_inn.txt',
                            '/home/pulkit/code_tmi/train_opp.txt',
                            '/home/pulkit/code_tmi/train_wat.txt']

        self.few_paths = ['/home/pulkit/code_tmi/train_fat.txt',
                            '/home/pulkit/code_tmi/train_fat.txt',
                            '/home/pulkit/code_tmi/train_fat.txt',
                            '/home/pulkit/code_tmi/train_fat.txt']

        self.val_paths = ['/home/pulkit/code_tmi/val_fat.txt',
                            '/home/pulkit/code_tmi/val_inn.txt',
                            '/home/pulkit/code_tmi/val_opp.txt',
                            '/home/pulkit/code_tmi/val_wat.txt']

        self.test_paths = ['/path/to/imagesfull_test_lumbar.txt',
                            '/path/to/imagesfull_test_thoracic_lower.txt',
                            '/path/to/imagesfull_test_thoracic_middle.txt',
                            '/path/to/imagesfull_test_thoracic_upper.txt']

        self.test_paths = ['/path/to/imagestest_lumbar.txt',
                            '/path/to/imagestest_thoracic_lower.txt',
                            '/path/to/imagestest_thoracic_middle.txt',
                            '/path/to/imagestest_thoracic_upper.txt']

        self.test_paths = ['/home/pulkit/june25/verse_test_fov.txt']

        for x in range(4):
            img_path = self.train_paths[x]

            dataset_img = BatchImageGenerator(img_path, modality, transform=True,
                                        patch_size=self.patch_size, n_patches_transform=30)

            self.TrainMetaData.append(dataset_img)

            val_path = self.val_paths[x]

            dataset_val = BatchImageGenerator(val_path, modality, transform=False,
                                        patch_size=self.patch_size, n_patches_transform=30)

            self.ValidMetaData.append(dataset_val)

        curr_test_modality = self.test_paths[self.unseen_index]
        self.TestMetaData = BatchImageGenerator(curr_test_modality, modality, transform=False,
                                        patch_size=self.patch_size, n_patches_transform=30, is_test=True)

        self.batImageGenTests = torch.utils.data.DataLoader(self.TestMetaData, batch_size=self.batch_size,
                                                       sampler=SequentialSampler(self.TestMetaData),
                                                       num_workers=0,
                                                       pin_memory=False)

    def configure(self):

        self.optimizer = optim.SGD(self.network.parameters(), lr=self.lr, weight_decay=self.weight_decay,
                                   momentum=self.momentum)
        self.scheduler = lr_scheduler.StepLR(optimizer=self.optimizer, step_size=self.step_size, gamma=0.1)
        self.loss_fn = GeneralizedDiceLoss()

    def inference(self, model_path, test_file):
        self.network.eval()
        image_path = read_text_files(test_file)
        image_files = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12',
                       '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24']
        print(image_path)

        ds = iter(self.batImageGenTests)
        print(" <<<<<< length of the test loader >>>>> ", len(ds))

        tmp=torch.load(model_path)
        pretrained_dict=tmp['state']
        print(">>>> which iteration was saved for the inference?", tmp['ite'])
        model_dict=self.network.state_dict()
        pretrained_dict={k:v for k, v in pretrained_dict.items() if k in model_dict and v.size()==model_dict[k].size()}
        model_dict.update(pretrained_dict)
        self.network.load_state_dict(model_dict)

        num_images = len(ds)
        accuracies = []
        val_losses = []
        accuracies_all_image = []
        it=0

        for im in range(num_images):
            trains, labels, patch_indices, img_size = next(ds)
            trains = trains.squeeze(0)
            labels = labels.squeeze(0)

            bs = 1
            images_train_three_domains_shape = np.shape(trains)
            total_batches_per_image = int(images_train_three_domains_shape[0] / bs)
            print("total number of batches >>>>>>>>", total_batches_per_image)
            batch_index = 0

            use_gpu = False
            if use_gpu:
                print('Using GPU')
                aggregated_results = torch.zeros(([self.num_classes] + img_size), dtype=torch.half)
                aggregated_nb_of_predictions = torch.zeros(([self.num_classes] + img_size), dtype=torch.half)
                add_for_nb_of_preds = torch.ones((self.patch_size, self.patch_size, self.patch_size), dtype=torch.half)
            else:
                print('Using CPU')
                aggregated_results = np.zeros(([self.num_classes] + img_size), dtype=np.half)
                aggregated_nb_of_predictions = np.zeros(([self.num_classes] + img_size), dtype=np.half)
                add_for_nb_of_preds = np.ones((self.patch_size, self.patch_size, self.patch_size), dtype=np.half)

            for batch in range(1, total_batches_per_image + 1):
                print(batch)
                curr_patch = patch_indices[batch_index]

                images_test, labels_test = trains[batch_index: batch_index + bs, :, :, :, :], labels[batch_index: batch_index + bs, :, :, :]

                images_test, labels_test = Variable(images_test, requires_grad=False).cuda(), Variable(labels_test, requires_grad=False).float().cuda()

                _, predictions = self.network(images_test, meta_step_size=0.001, meta_loss=None,
                                                        stop_gradient=False)

                sg = curr_patch[0]
                idx1_sg, idx2_sg = sg[0], sg[1]

                cr = curr_patch[1]
                idx1_cr,idx2_cr = cr[0], cr[1]

                ax = curr_patch[2]
                idx1_ax,idx2_ax = ax[0], ax[1]

                if use_gpu:
                    predictions_reshaped = torch.reshape(predictions.half(), (self.num_classes, self.patch_size, self.patch_size, self.patch_size))

                else:
                    predictions = predictions.cpu().data.numpy()
                    predicted_classes = np.argmax(predictions, axis=1)
                    predictions_reshaped = np.reshape(predictions, (self.num_classes, self.patch_size, self.patch_size, self.patch_size))

                aggregated_results[:, idx1_sg:idx2_sg, idx1_cr:idx2_cr, idx1_ax:idx2_ax] +=  predictions_reshaped # (num_classes, h,w,d)
                aggregated_nb_of_predictions[:, idx1_sg:idx2_sg, idx1_cr:idx2_cr, idx1_ax:idx2_ax] += add_for_nb_of_preds

                del predictions_reshaped

                batch_index += bs

            class_probabilities = aggregated_results / aggregated_nb_of_predictions
            predicted_classes = np.argmax(class_probabilities, axis=0)
            predicted_classes = np.float64(predicted_classes)

            # unpad prediction
            shape_lb = np.shape(predicted_classes)
            print('seeee', shape_lb)
            predicted_classes = predicted_classes[self.step_size_unpad: shape_lb[0]-self.step_size_unpad, self.step_size_unpad: shape_lb[1]-self.step_size_unpad, self.step_size_unpad: shape_lb[2]- self.step_size_unpad]

            # save the predicted labels
            filepath_name_pred = '/home/pulkit/code_tmi/output_miccai/verse/ubound/' + 'case_' + image_files[im] + '.nii.gz'
            save_nifti(predicted_classes, filepath_name_pred)

            # read the label for the corresponding image from the file
            _, label_data = read_nifti_miccai(image_path[im])

            print(np.shape(predicted_classes), np.shape(label_data))

            accuracy_val_full_image = compute_accuracy(predictions=predicted_classes, labels=label_data)
            accuracies_all_image.append(accuracy_val_full_image)
            print("---------- current image test accuracy by the normalization method ----------", accuracy_val_full_image)

            del label_data
            d

el predicted_classes

        print("---------- TO REPORT final average image test accuracy by the normalization method ----------", np.mean(accuracies_all_image))

###### call the model here
baseline = Model()

PATH = '/path/to/trained/model/trained_model.tar'
test_file = 'path/to/images/test_file.txt'

baseline.inference(PATH, test_file)


import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):
        for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # more hooks
            self.after_model_change(None, model, False)
        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):
        if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone)
        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')

qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):
        qs = json.loads(self.model.objects(id__in=ids).to_json())

        def generate():
            yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'

        return Response(
            generate(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.csv" % self.model.__name__.lower()
            }
        )


yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'

import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):
        for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # more hooks
            self.after_model_change(None, model, False)
        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):
        if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone)
        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')
    def export_to_json(self, ids):
        qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):
        qs = json.loads(self.model.objects(id__in=ids).to_json())

        def generate():

        return Response(
            generate(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.csv" % self.model.__name__.lower()
            }
        )


import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):
        for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # more hooks
            self.after_model_change(None, model, False)
        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):
        if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone)
        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')
    def export_to_json(self, ids):
        qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):

        def generate():
            yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'

        return Response(
            generate(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.csv" % self.model.__name__.lower()
            }
        )


def generate():
            yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'

import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):
        for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # more hooks
            self.after_model_change(None, model, False)
        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):
        if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone)
        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')
    def export_to_json(self, ids):
        qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):
        qs = json.loads(self.model.objects(id__in=ids).to_json())


return Response(
            generate(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.csv" % self.model.__name__.lower()
            }
        )

import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):
        for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # more hooks
            self.after_model_change(None, model, False)
        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):
        if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone)
        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')
    def export_to_json(self, ids):
        qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):
        qs = json.loads(self.model.objects(id__in=ids).to_json())

        def generate():
            yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'


import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):
        for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # more hooks
            self.after_model_change(None, model, False)
        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):
        if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone

        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')
    def export_to_json(self, ids):
        qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):
        qs = json.loads(self.model.objects(id__in=ids).to_json())

        def generate():
            yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'

        return Response(
            generate(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.csv" % self.model.__name__.lower()
            }
        )


if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):
        for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # more hooks
            self.after_model_change(None, model, False)
        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone)
        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')
    def export_to_json(self, ids):
        qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):
        qs = json.loads(self.model.objects(id__in=ids).to_json())

        def generate():
            yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'

        return Response(
            generate(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.csv" % self.model.__name__.lower()
            }
        )


for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # more hooks
            self.after_model_change(None, model, False)

import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):

        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):
        if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone)
        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')
    def export_to_json(self, ids):
        qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):
        qs = json.loads(self.model.objects(id__in=ids).to_json())

        def generate():
            yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'

        return Response(
            generate(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.csv" % self.model.__name__.lower()
            }
        )


import json
import random
from copy import deepcopy

from flask import Response, current_app, flash, redirect, url_for
from flask_admin.actions import action

class PublishAction(object):
    @action(
        'toggle_publish',
        'Publish/Unpublish',
        'Publish/Unpublish?'
    )
    def action_toggle_publish(self, ids):
        for _id in ids:
            model = current_app.db.get_with_content(_id=_id)
            model['published'] = not model['published']
            # fires the versioning and hooks
            self._on_model_change(None, model, False)

            pk = self.get_pk_value(model)
            self.coll.update({'_id': pk}, model)

            # mor

e hooks
            self.after_model_change(None, model, False)
        flash(
            f'{len(ids)} items were successfully published/Unpublished.',
            'success'
        )

class CloneAction(object):
    @action(
        'clone_item',
        'Create a copy',
        'Are you sure you want a copy?'
    )
    def action_clone_item(self, ids):
        if len(ids) > 1:
            flash(
                "You can select only one item for this action",
                'error'
            )
            return

        model = current_app.db.get_with_content(_id=ids[0])
        clone = deepcopy(model)
        del clone['_id']
        clone['slug'] = f'{clone["slug"]}-{random.getrandbits(32)}'
        clone['_isclone'] = True
        self._on_model_change(None, clone, True)
        self.coll.insert(clone)
        self.after_model_change(None, clone, True)
        return redirect(url_for('.edit_view', id=clone['_id']))

# TODO: Serialize and activate thia action
class ExportAction(object):
    @action('export_to_json', 'Export as json')
    def export_to_json(self, ids):
        qs = self.model.objects(id__in=ids)

        return Response(
            qs.to_json(),
            mimetype="text/json",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.json" % self.model.__name__.lower()
            }
        )

    @action('export_to_csv', 'Export as csv')
    def export_to_csv(self, ids):
        qs = json.loads(self.model.objects(id__in=ids).to_json())

        def generate():
            yield ','.join(list(max(qs, key=lambda x: len(x)).keys())) + '\n'
            for item in qs:
                yield ','.join([str(i) for i in list(item.values())]) + '\n'

        return Response(
            generate(),
            mimetype="text/csv",
            headers={
                "Content-Disposition":
                "attachment;filename=%s.csv" % self.model.__name__.lower()
            }
        )


"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)
        mode = stat.S_IMODE(st.st_mode)
        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

__all__ = ['file_move_safe']

if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, tries ``os.rename``, which is simple but will break across filesystems.
    If that fails, streams manually from one file to another in pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)

    try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it.  (For the
        # systems where this happens, temporary files will be auto-deleted
        # on close anyway.)
        if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
            raise


if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)
        mode = stat.S_IMODE(st.st_mode)
        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

__all__ = ['file_move_safe']

def _samefile(src, dst):

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, tries ``os.rename``, which is simple but will break across filesystems.
    If that fails, streams manually from one file to another in pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)

    try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it.  (For the
        # systems where this happens, temporary files will be auto-deleted
        # on close anyway.)
        if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
            raise


"""Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)

"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):

        mode = stat.S_IMODE(st.st_mode)
        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

__all__ = ['file_move_safe']

def _samefile(src, dst):
    if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, tries ``os.rename``, which is simple but will break across filesystems.
    If that fails, streams manually from one file to another in pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)

    try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it.  (For the
        # systems where this happens, temporary files will be auto-deleted
        # on close anyway.)
        if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
            raise


"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)

        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

__all__ = ['file_move_safe']

def _samefile(src, dst):
    if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, tries ``os.rename``, which is simple but will break across filesystems.
    If that fails, streams manually from one file to another in pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)

    try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it.  (For the
        # systems where this happens, temporary files will be auto-deleted
        # on close anyway.)
        if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
            raise


if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)
        mode = stat.S_IMODE(st.st_mode)

__all__ = ['file_move_safe']

def _samefile(src, dst):
    if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, tries ``os.rename``, which is simple but will break across filesystems.
    If that fails, streams manually from one file to another in pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)

    try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it.  (For the
        # systems where this happens, temporary files will be auto-deleted
        # on close anyway.)
        if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
            raise


"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)
        mode = stat.S_IMODE(st.st_m


        st = os.stat(src)
        mode = stat.S_IMODE(st.st_mode)
        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""


if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
            raise

"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)
        mode = stat.S_IMODE(st.st_mode)
        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

__all__ = ['file_move_safe']

def _samefile(src, dst):
    if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, tries ``os.rename``, which is simple but will break across filesystems.
    If that fails, streams manually from one file to another in pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)

    try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it.  (For the
        # systems where this happens, temporary files will be auto-deleted
        # on close anyway.)


while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)

"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)
        mode = stat.S_IMODE(st.st_mode)
        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

__all__ = ['file_move_safe']

def _samefile(src, dst):
    if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, tries ``os.rename``, which is simple but will break across filesystems.
    If that fails, streams manually from one file to another in pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None

        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)

    try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it.  (For the
        # systems where this happens, temporary files will be auto-deleted
        # on close anyway.)
        if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
            raise


try:
        os.remove(old_file_name)
    except OSError, e:
        # Certain operating systems (Cygwin and Windows)
        # fail when deleting opened files, ignore it.  (For the
        # systems where this happens, temporary files will be auto-deleted
        # on close anyway.)
        if getattr(e, 'winerror', 0) != 32 and getattr(e, 'errno', 0) != 13:
            raise

"""
Move a file in the safest way possible::

    >>> from django.core.files.move import file_move_safe
    >>> file_move_safe("/tmp/old_file", "/tmp/new_file")
"""

import os
from django.core.files import locks

try:
    from shutil import copystat
except ImportError:
    import stat
    def copystat(src, dst):
        """Copy all stat info (mode bits, atime and mtime) from src to dst"""
        st = os.stat(src)
        mode = stat.S_IMODE(st.st_mode)
        if hasattr(os, 'utime'):
            os.utime(dst, (st.st_atime, st.st_mtime))
        if hasattr(os, 'chmod'):
            os.chmod(dst, mode)

__all__ = ['file_move_safe']

def _samefile(src, dst):
    if hasattr(os.path,'samefile'):
        try:
            return os.path.samefile(src, dst)
        except OSError:
            return False

    # All other platforms: check for same pathname.
    return (os.path.normcase(os.path.abspath(src)) ==
            os.path.normcase(os.path.abspath(dst)))

def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
    """
    Moves a file from one location to another in the safest way possible.

    First, tries ``os.rename``, which is simple but will break across filesystems.
    If that fails, streams manually from one file to another in pure Python.

    If the destination file exists and ``allow_overwrite`` is ``False``, this
    function will throw an ``IOError``.
    """

    # There's no reason to move if we don't have to.
    if _samefile(old_file_name, new_file_name):
        return

    try:
        os.rename(old_file_name, new_file_name)
        return
    except OSError:
        # This will happen with os.rename if moving to another filesystem
        # or when moving opened files on certain operating systems
        pass

    # first open the old file, so that it won't go away
    old_file = open(old_file_name, 'rb')
    try:
        # now open the new file, not forgetting allow_overwrite
        fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
                                    (not allow_overwrite and os.O_EXCL or 0))
        try:
            locks.lock(fd, locks.LOCK_EX)
            current_chunk = None
            while current_chunk != '':
                current_chunk = old_file.read(chunk_size)
                os.write(fd, current_chunk)
        finally:
            locks.unlock(fd)
            os.close(fd)
    finally:
        old_file.close()
    copystat(old_file_name, new_file_name)


def appendMessage(self, message, milliSeconds=-1):

"""Queued message tool bar. Not a public API, but available via MainWindow.appendMessage()
"""

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QEvent, Qt, QTimer
from PyQt5.QtWidgets import (QAbstractButton, QDialogButtonBox, QHBoxLayout,
                             QLabel, QSizePolicy, QToolBar, QWidget)
from PyQt5.QtGui import QColor, QBrush, QPainter, QPixmap

class _QueuedMessage:

    def __init__(self):
        self.milliSeconds = -1
        self.background = QBrush(Qt.NoBrush)
        self.foreground = QBrush(Qt.NoBrush)
        self.slot = None
        self.buttons = {}

class _QueuedMessageWidget(QWidget):

    cleared = pyqtSignal()
    finished = pyqtSignal()
    shown = pyqtSignal()
    closed = pyqtSignal()
    linkActivated = pyqtSignal(str)
    linkHovered = pyqtSignal(str)
    buttonClicked = pyqtSignal(QAbstractButton)

    def __init__(self, *args):
        QWidget.__init__(self, *args)
        self._messages = []
        self._defaultTimeout = 0
        self._defaultPixmap = QPixmap(":/enkiicons/infos.png")
        self._defaultBackground = QBrush(QColor(250, 230, 147))
        self._defaultForeground = QBrush(QColor(0, 0, 0))

        # pixmap
        self.lPixmap = QLabel(self)
        self.lPixmap.setAlignment(Qt.AlignCenter)
        self.lPixmap.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred))

        # message
        self.lMessage = QLabel(self)
        self.lMessage.setAlignment(Qt.AlignVCenter | Qt.AlignLeft)
        self.lMessage.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred))
        self.lMessage.setWordWrap(True)
        self.lMessage.setOpenExternalLinks(True)
        self.lMessage.setTextInteractionFlags(Qt.TextBrowserInteraction)

        # button
        self.dbbButtons = QDialogButtonBox(self)

        # if false - buttons don't have neither text nor icons
        self.dbbButtons.setStyleSheet("dialogbuttonbox-buttons-have-icons: true;")

        self.dbbButtons.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred))

        self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum))

        # layout
        self.hbl = QHBoxLayout(self)
        self.hbl.setContentsMargins(0, 0, 0, 0)
        self.hbl.addWidget(self.lPixmap, 0, Qt.AlignCenter)
        self.hbl.addWidget(self.lMessage)
        self.hbl.addWidget(self.dbbButtons, 0, Qt.AlignCenter)

        # connections
        self.lMessage.linkActivated.connect(self.linkActivated)
        self.lMessage.linkHovered.connect(self.linkHovered)
        self.dbbButtons.clicked.connect(self.buttonClicked)

        self._closeTimer = QTimer(self)
        self._closeTimer.setSingleShot(True)
        self._closeTimer.timeout.connect(self.closeMessage)

    def sizeHint(self):
        return QWidget.minimumSizeHint(self)

    def openExternalLinks(self):
        return self.lMessage.openExternalLinks()

    def defaultTimeout(self):
        return self._defaultTimeout

    def defaultPixmap(self):
        return self._defaultPixmap

    def defaultBackground(self):
        return self._defaultBackground

    def defaultForeground(self):
        return self._defaultForeground

    def currentMessageInformations(self):
        return self.currentMessagePixmap(), self.currentMessageBackground(), self.currentMessageForeground()

    def pendingMessageCount(self):
        return len(self._messages)

    def currentMessage(self):
        return self._messages[0]

    def append(self, message, milliSeconds):
        msg = _QueuedMessage()
        msg.message = message
        if milliSeconds == -1:
            msg.milliSeconds = self._defaultTimeout
        else:
            msg.milliSeconds = milliSeconds
        msg.pixmap = self._defaultPixmap
        msg.background = self._defaultBackground
        msg.foreground = self._defaultForeground

        self._messages.append(msg)

        if len(self._messages) == 1:
            self.showMessage()

    def setOpenExternalLinks(self, open):
        self.lMessage.setOpenExternalLinks(open)

    def setDefaultTimeout(self, timeout):
        self._defaultTimeout = timeout

    def setDefaultPixmap(self, pixmap):
        self._defaultPixmap = pixmap

    def setDefaultBackground(self, brush):
        self._defaultBackground = brush

    def setDefaultForeground(self, brush):
        self._defaultForeground = brush

    def remove(self, message):
        raise NotImplemented()  # incorrect port from cpp fresh
        if not self._messages or self._messages.first() == message:
            return

        self._messages.removeOne(message)

    def clear(self):
        self._messages.clear()
        self.lPixmap.clear()
        self.lMessage.clear()
        self.dbbButtons.clear()
        self.cleared.emit()

    def currentMessagePixmap(self):
        msg = self.currentMessage()
        if msg.pixmap.isNull():
            return self._defaultPixmap
        else:
            return msg.pixmap

    def currentMessageBackground(self):
        msg = self.currentMessage()
        if msg.background == QBrush(Qt.NoBrush):
            return self._defaultBackground
        else:
            return msg.background

    def currentMessageForeground(self):
        msg = self.currentMessage()
        if msg.foreground == QBrush(Qt.NoBrush):
            return self._defaultForeground
        else:
            return msg.foreground

    def paintEvent(self, event):
        if self.pendingMessageCount() == 0:
            QWidget.paintEvent(self, event)
            return

        painter = QPainter(self)
        painter.setPen(Qt.NoPen)
        painter.setBrush(self.currentMessageBackground())
        painter.drawRect(self.contentsRect())

    @pyqtSlot(QAbstractButton)
    def buttonClicked(self, button):
        msg = self.currentMessage()
        standardButton = self.dbbButtons.standardButton(button)

        if msg.slot is not None:
            msg.slot(standardButton, msg)

        self.closeMessage()

    def showMessage(self):
        # get message
        msg = self.currentMessage()

        # update palette
        pal = self.lMessage.palette()
        pal.setBrush(self.lMessage.foregroundRole(), self.currentMessageForeground())
        self.lMessage.setPalette(pal)

        # format widget
        self.lPixmap.setPixmap(self.currentMessagePixmap())
        self.lMessage.setText(msg.message)
        self.lMessage.setToolTip(msg.message)
        self.lMessage.setWhatsThis(msg.message)

        # set buttons
        if not msg.buttons:
            msg.buttons[QDialogButtonBox.Close] = None

        self.dbbButtons.clear()

        for button in msg.buttons.keys():
            pb = self.dbbButtons.addButton(button)

            if button in msg.buttons:
                pb.setText(msg.buttons[button])

        # auto close if needed
        if msg.milliSeconds == -1:
            timeout = self._defaultTimeout
        else:
            timeout = msg.milliSeconds

        if timeout > 0:
            self._closeTimer.setInterval(timeout)
            self._closeTimer.start()

        # signal.emit
        self.shown.emit()

    @pyqtSlot()
    def closeMessage(self):
        # message.emit
        self.closed.emit()

        # remove remove current message from hash
        self._messages = self._messages[1:]

        # process next if possible, clear gui
        if self._messages:
            self.showMessage()
        else:
            self.clearMessage()

        # finished.emit message if needed
        if not self._messages:
            self.finished.emit()

    def clearMessage(self):
        self.lPixmap.clear()
        self.lMessage.clear()
        self.dbbButtons.clear()

class QueuedMessageToolBar(QToolBar):

    def __init__(self, *args):
        QToolBar.__init__(self, *args)
        self._queuedWidget = _QueuedMessageWidget(self)

        self.setObjectName(self.metaObject().className())
        self.setMovable(False)
        self.setFloatable(False)
        self.setAllowedAreas(Qt.TopToolBarArea)
        self.toggleViewAction().setEnabled(False)
        self.toggleViewAction().setVisible(False)

        self.addWidget(self._queuedWidget)
        self.layout().setContentsMargins(3, 3, 3, 3)

        # connections
        self._queuedWidget.shown.connect(self.messageShown)
        self._queuedWidget.finished.connect(self.messageFinished)

    def queuedMessageWidget(self):
        return self._queuedWidget

    def changeEvent(self, event):
        if event.type() == QEvent.FontChange:
            self._queuedWidget.setFont(self.font())

        QToolBar.changeEvent(self, event)

    def paintEvent(self, event):
        if self._queuedWidget.pendingMessageCount() == 0:
            QToolBar.paintEvent(self, event)
            return

        brush = self._queuedWidget.currentMessageBackground()
        painter = QPainter(self)
        painter.setPen(brush.color().darker(150))
        painter.setBrush(brush)
        painter.drawRect(self.contentsRect().adjusted(0, 0, -1, -1))

return self._queuedWidget.append(message, milliSeconds)

    def removeMessage(self, message):
        self._queuedWidget.remove(message)

    @pyqtSlot()
    def messageShown(self):
        if not self.isVisible():
            self.setVisible(True)

    @pyqtSlot()
    def messageFinished(self):
        if self.isVisible():
            self.setVisible(False)


if self.pendingMessageCount() == 0:
            QWidget.paintEvent(self, event)
            return

        painter = QPainter(self)
        painter.setPen(Qt.NoPen)
        painter.setBrush(self.currentMessageBackground())
        painter.drawRect(self.contentsRect())

"""Queued message tool bar. Not a public API, but available via MainWindow.appendMessage()
"""

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QEvent, Qt, QTimer
from PyQt5.QtWidgets import (QAbstractButton, QDialogButtonBox, QHBoxLayout,
                             QLabel, QSizePolicy, QToolBar, QWidget)
from PyQt5.QtGui import QColor, QBrush, QPainter, QPixmap

class _QueuedMessage:

    def __init__(self):
        self.milliSeconds = -1
        self.background = QBrush(Qt.NoBrush)
        self.foreground = QBrush(Qt.NoBrush)
        self.slot = None
        self.buttons = {}

class _QueuedMessageWidget(QWidget):

    cleared = pyqtSignal()
    finished = pyqtSignal()
    shown = pyqtSignal()
    closed = pyqtSignal()
    linkActivated = pyqtSignal(str)
    linkHovered = pyqtSignal(str)
    buttonClicked = pyqtSignal(QAbstractButton)

    def __init__(self, *args):
        QWidget.__init__(self, *args)
        self._messages = []
        self._defaultTimeout = 0
        self._defaultPixmap = QPixmap(":/enkiicons/infos.png")
        self._defaultBackground = QBrush(QColor(250, 230, 147))
        self._defaultForeground = QBrush(QColor(0, 0, 0))

        # pixmap
        self.lPixmap = QLabel(self)
        self.lPixmap.setAlignment(Qt.AlignCenter)
        self.lPixmap.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred))

        # message
        self.lMessage = QLabel(self)
        self.lMessage.setAlignment(Qt.AlignVCenter | Qt.AlignLeft)
        self.lMessage.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred))
        self.lMessage.setWordWrap(True)
        self.lMessage.setOpenExternalLinks(True)
        self.lMessage.setTextInteractionFlags(Qt.TextBrowserInteraction)

        # button
        self.dbbButtons = QDialogButtonBox(self)

        # if false - buttons don't have neither text nor icons
        self.dbbButtons.setStyleSheet("dialogbuttonbox-buttons-have-icons: true;")

        self.dbbButtons.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred))

        self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum))

        # layout
        self.hbl = QHBoxLayout(self)
        self.hbl.setContentsMargins(0, 0, 0, 0)
        self.hbl.addWidget(self.lPixmap, 0, Qt.AlignCenter)
        self.hbl.addWidget(self.lMessage)
        self.hbl.addWidget(self.dbbButtons, 0, Qt.AlignCenter)

        # connections
        self.lMessage.linkActivated.connect(self.linkActivated)
        self.lMessage.linkHovered.connect(self.linkHovered)
        self.dbbButtons.clicked.connect(self.buttonClicked)

        self._closeTimer = QTimer(self)
        self._closeTimer.setSingleShot(True)
        self._closeTimer.timeout.connect(self.closeMessage)

    def sizeHint(self):
        return QWidget.minimumSizeHint(self)

    def openExternalLinks(self):
        return self.lMessage.openExternalLinks()

    def defaultTimeout(self):
        return self._defaultTimeout

    def defaultPixmap(self):
        return self._defaultPixmap

    def defaultBackground(self):
        return self._defaultBackground

    def defaultForeground(self):
        return self._defaultForeground

    def currentMessageInformations(self):
        return self.currentMessagePixmap(), self.currentMessageBackground(), self.currentMessageForeground()

    def pendingMessageCount(self):
        return len(self._messages)

    def currentMessage(self):
        return self._messages[0]

    def append(self, message, milliSeconds):
        msg = _QueuedMessage()
        msg.message = message
        if milliSeconds == -1:
            msg.milliSeconds = self._defaultTimeout
        else:
            msg.milliSeconds = milliSeconds
        msg.pixmap = self._defaultPixmap
        msg.background = self._defaultBackground
        msg.foreground = self._defaultForeground

        self._messages.append(msg)

        if len(self._messages) == 1:
            self.showMessage()

    def setOpenExternalLinks(self, open):
        self.lMessage.setOpenExternalLinks(open)

    def setDefaultTimeout(self, timeout):
        self._defaultTimeout = timeout

    def setDefaultPixmap(self, pixmap):
        self._defaultPixmap = pixmap

    def setDefaultBackground(self, brush):
        self._defaultBackground = brush

    def setDefaultForeground(self, brush):
        self._defaultForeground = brush

    def remove(self, message):
        raise NotImplemented()  # incorrect port from cpp fresh
        if not self._messages or self._messages.first() == message:
            return

        self._messages.removeOne(message)

    def clear(self):
        self._messages.clear()
        self.lPixmap.clear()
        self.lMessage.clear()
        self.dbbButtons.clear()
        self.cleared.emit()

    def currentMessagePixmap(self):
        msg = self.currentMessage()
        if msg.pixmap.isNull():
            return self._defaultPixmap
        else:
            return msg.pixmap

    def currentMessageBackground(self):
        msg = self.currentMessage()
        if msg.background == QBrush(Qt.NoBrush):
            return self._defaultBackground
        else:
            return msg.background

    def currentMessageForeground(self):
        msg = self.currentMessage()
        if msg.foreground == QBrush(Qt.NoBrush):
            return self._defaultForeground
        else:
            return msg.foreground

    def paintEvent(self, event):

    @pyqtSlot(QAbstractButton)
    def buttonClicked(self, button):
        msg = self.currentMessage()
        standardButton = self.dbbButtons.standardButton(button)

        if msg.slot is not None:
            msg.slot(standardButton, msg)

        self.closeMessage()

    def showMessage(self):
        # get message
        msg = self.currentMessage()

        # update palette
        pal = self.lMessage.palette()
        pal.setBrush(self.lMessage.foregroundRole(), self.currentMessageForeground())
        self.lMessage.setPalette(pal)

        # format widget
        self.lPixmap.setPixmap(self.currentMessagePixmap())
        self.lMessage.setText(msg.message)
        self.lMessage.setToolTip(msg.message)
        self.lMessage.setWhatsThis(msg.message)

        # set buttons
        if not msg.buttons:
            msg.buttons[QDialogButtonBox.Close] = None

        self.dbbButtons.clear()

        for button in msg.buttons.keys():
            pb = self.dbbButtons.addButton(button)

            if button in msg.buttons:
                pb.setText(msg.buttons[button])

        # auto close if needed
        if msg.milliSeconds == -1:
            timeout = self._defaultTimeout
        else:
            timeout = msg.milliSeconds

        if timeout > 0:
            self._closeTimer.setInterval(timeout)
            self._closeTimer.start()

        # signal.emit
        self.shown.emit()

    @pyqtSlot()
    def closeMessage(self):
        # message.emit
        self.closed.emit()

        # remove remove current message from hash
        self._messages = self._messages[1:]

        # process next if possible, clear gui
        if self._messages:
            self.showMessage()
        else:
            self.clearMessage()

        # finished.emit message if needed
        if not self._messages:
            self.finished.emit()

    def clearMessage(self):
        self.lPixmap.clear()
        self.lMessage.clear()
        self.dbbButtons.clear()

class QueuedMessageToolBar(QToolBar):

    def __init__(self, *args):
        QToolBar.__init__(self, *args)
        self._queuedWidget = _QueuedMessageWidget(self)

        self.setObjectName(self.metaObject().className())
        self.setMovable(False)
        self.setFloatable(False)
        self.setAllowedAreas(Qt.TopToolBarArea)
        self.toggleViewAction().setEnabled(False)
        self.toggleViewAction().setVisible(False)

        self.addWidget(self._queuedWidget)
        self.layout().setContentsMargins(3, 3, 3, 3)

        # connections
        self._queuedWidget.shown.connect(self.messageShown)
        self._queuedWidget.finished.connect(self.messageFinished)

    def queuedMessageWidget(self):
        return self._queuedWidget

    def changeEvent(self, event):
        if event.type() == QEvent.FontChange:
            self._queuedWidget.setFont(self.font())

        QToolBar.changeEvent(self, event)

    def paintEvent(self, event):
        if self._queuedWidget.pendingMessageCount() == 0:
            QToolBar.paintEvent(self, event)
            return

        brush = self._queuedWidget.currentMessageBackground()
        painter = QPainter(self)
        painter.setPen(brush.color().darker(150))
        painter.setBrush(brush)
        painter.drawRect(self.contentsRect().adjusted(0, 0, -1, -1))

    def appendMessage(self, message, milliSeconds=-1):
        return self._queuedWidget.append(message, milliSeconds)

    def removeMessage(self, message):
        self._queuedWidget.remove(message)

    @pyqtSlot()
    def messageShown(self):
        if not self.isVisible():
            self.setVisible(True)

    @pyqtSlot()
    def messageFinished(self):
        if self.isVisible():
            self.setVisible(False)


msg = self.currentMessage()
        standardButton = self.dbbButtons.standardButton(button)

"""Queued message tool bar. Not a public API, but available via MainWindow.appendMessage()
"""

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QEvent, Qt, QTimer
from PyQt5.QtWidgets import (QAbstractButton, QDialogButtonBox, QHBoxLayout,
                             QLabel, QSizePolicy, QToolBar, QWidget)
from PyQt5.QtGui import QColor, QBrush, QPainter, QPixmap

class _QueuedMessage:

    def __init__(self):
        self.milliSeconds = -1
        self.background = QBrush(Qt.NoBrush)
        self.foreground = QBrush(Qt.NoBrush)
        self.slot = None
        self.buttons = {}

class _QueuedMessageWidget(QWidget):

    cleared = pyqtSignal()
    finished = pyqtSignal()
    shown = pyqtSignal()
    closed = pyqtSignal()
    linkActivated = pyqtSignal(str)
    linkHovered = pyqtSignal(str)
    buttonClicked = pyqtSignal(QAbstractButton)

    def __init__(self, *args):
        QWidget.__init__(self, *args)
        self._messages = []
        self._defaultTimeout = 0
        self._defaultPixmap = QPixmap(":/enkiicons/infos.png")
        self._defaultBackground = QBrush(QColor(250, 230, 147))
        self._defaultForeground = QBrush(QColor(0, 0, 0))

        # pixmap
        self.lPixmap = QLabel(self)
        self.lPixmap.setAlignment(Qt.AlignCenter)
        self.lPixmap.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred))

        # message
        self.lMessage = QLabel(self)
        self.lMessage.setAlignment(Qt.AlignVCenter | Qt.AlignLeft)
        self.lMessage.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred))
        self.lMessage.setWordWrap(True)
        self.lMessage.setOpenExternalLinks(True)
        self.lMessage.setTextInteractionFlags(Qt.TextBrowserInteraction)

        # button
        self.dbbButtons = QDialogButtonBox(self)

        # if false - buttons don't have neither text nor icons
        self.dbbButtons.setStyleSheet("dialogbuttonbox-buttons-have-icons: true;")

        self.dbbButtons.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred))

        self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum))

        # layout
        self.hbl = QHBoxLayout(self)
        self.hbl.setContentsMargins(0, 0, 0, 0)
        self.hbl.addWidget(self.lPixmap, 0, Qt.AlignCenter)
        self.hbl.addWidget(self.lMessage)
        self.hbl.addWidget(self.dbbButtons, 0, Qt.AlignCenter)

        # connections
        self.lMessage.linkActivated.connect(self.linkActivated)
        self.lMessage.linkHovered.connect(self.linkHovered)
        self.dbbButtons.clicked.connect(self.buttonClicked)

        self._closeTimer = QTimer(self)
        self._closeTimer.setSingleShot(True)
        self._closeTimer.timeout.connect(self.closeMessage)

    def sizeHint(self):
        return QWidget.minimumSizeHint(self)

    def openExternalLinks(self):
        return self.lMessage.openExternalLinks()

    def defaultTimeout(self):
        return self._defaultTimeout

    def defaultPixmap(self):
        return self._defaultPixmap

    def defaultBackground(self):
        return self._defaultBackground

    def defaultForeground(self):
        return self._defaultForeground

    def currentMessageInformations(self):
        return self.currentMessagePixmap(), self.currentMessageBackground(), self.currentMessageForeground()

    def pendingMessageCount(self):
        return len(self._messages)

    def currentMessage(self):
        return self._messages[0]

    def append(self, message, milliSeconds):
        msg = _QueuedMessage()
        msg.message = message
        if milliSeconds == -1:
            msg.milliSeconds = self._defaultTimeout
        else:
            msg.milliSeconds = milliSeconds
        msg.pixmap = self._defaultPixmap
        msg.background = self._defaultBackground
        msg.foreground = self._defaultForeground

        self._messages.append(msg)

        if len(self._messages) == 1:
            self.showMessage()

    def setOpenExternalLinks(self, open):
        self.lMessage.setOpenExternalLinks(open)

    def setDefaultTimeout(self, timeout):
        self._defaultTimeout = timeout

    def setDefaultPixmap(self, pixmap):
        self._defaultPixmap = pixmap

    def setDefaultBackground(self, brush):
        self._defaultBackground = brush

    def setDefaultForeground(self, brush):
        self._defaultForeground = brush

    def remove(self, message):
        raise NotImplemented()  # incorrect port from cpp fresh
        if not self._messages or self._messages.first() == message:
            return

        self._messages.removeOne(message)

    def clear(self):
        self._messages.clear()
        self.lPixmap.clear()
        self.lMessage.clear()
        self.dbbButtons.clear()
        self.cleared.emit()

    def currentMessagePixmap(self):
        msg = self.currentMessage()
        if msg.pixmap.isNull():
            return self._defaultPixmap
        else:
            return msg.pixmap

    def currentMessageBackground(self):
        msg = self.currentMessage()
        if msg.background == QBrush(Qt.NoBrush):
            return self._defaultBackground
        else:
            return msg.background

    def currentMessageForeground(self):
        msg = self.currentMessage()
        if msg.foreground == QBrush(Qt.NoBrush):
            return self._defaultForeground
        else:
            return msg.foreground

    def paintEvent(self, event):
        if self.pendingMessageCount() == 0:
            QWidget.paintEvent(self, event)
            return

        painter = QPainter(self)
        painter.setPen(Qt.NoPen)
        painter.setBrush(self.currentMessageBackground())
        painter.drawRect(self.contentsRect())

    @pyqtSlot(QAbstractButton)
    def buttonClicked(self, button):

        if msg.slot is not None:
            msg.slot(standardButton, msg)

        self.closeMessage()

    def showMessage(self):
        # get message
        msg = self.currentMessage()

        # update palette
        pal = self.lMessage.palette()
        pal.setBrush(self.lMessage.foregroundRole(), self.currentMessageForeground())
        self.lMessage.setPalette(pal)

        # format widget
        self.lPixmap.setPixmap(self.currentMessagePixmap())
        self.lMessage.setText(msg.message)
        self.lMessage.setToolTip(msg.message)
        self.lMessage.setWhatsThis(msg.message)

        # set buttons
        if not msg.buttons:
            msg.buttons[QDialogButtonBox.Close] = None

        self.dbbButtons.clear()

        for button in msg.buttons.keys():
            pb = self.dbbButtons.addButton(button)

            if button in msg.buttons:
                pb.setText(msg.buttons[button])

        # auto close if needed
        if msg.milliSeconds == -1:
            timeout = self._defaultTimeout
        else:
            timeout = msg.milliSeconds

        if timeout > 0:
            self._closeTimer.setInterval(timeout)
            self._closeTimer.start()

        # signal.emit
        self.shown.emit()

    @pyqtSlot()
    def closeMessage(self):
        # message.emit
        self.closed.emit()

        # remove remove current message from hash
        self._messages = self._messages[1:]

        # process next if possible, clear gui
        if self._messages:
            self.showMessage()
        else:
            self.clearMessage()

        # finished.emit message if needed
        if not self._messages:
            self.finished.emit()

    def clearMessage(self):
        self.lPixmap.clear()
        self.lMessage.clear()
        self.dbbButtons.clear()

class QueuedMessageToolBar(QToolBar):

    def __init__(self, *args):
        QToolBar.__init__(self, *args)
        self._queuedWidget = _QueuedMessageWidget(self)

        self.setObjectName(self.metaObject().className())
        self.setMovable(False)
        self.setFloatable(False)
        self.setAllowedAreas(Qt.TopToolBarArea)
        self.toggleViewAction().setEnabled(False)
        self.toggleViewAction().setVisible(False)

        self.addWidget(self._queuedWidget)
        self.layout().setContentsMargins(3, 3, 3, 3)

        # connections
        self._queuedWidget.shown.connect(self.messageShown)
        self._queuedWidget.finished.connect(self.messageFinished)

    def queuedMessageWidget(self):
        return self._queuedWidget

    def changeEvent(self, event):
        if event.type() == QEvent.FontChange:
            self._queuedWidget.setFont(self.font())

        QToolBar.changeEvent(self, event)

    def paintEvent(self, event):
        if self._queuedWidget.pendingMessageCount() == 0:
            QToolBar.paintEvent(self, event)
            return

        brush = self._queuedWidget.currentMessageBackground()
        painter = QPainter(self)
        painter.setPen(brush.color().darker(150))
        painter.setBrush(brush)
        painter.drawRect(self.contentsRect().adjusted(0, 0, -1, -1))

    def appendMessage(self, message, milliSeconds=-1):
        return self._queuedWidget.append(message, milliSeconds)

    def removeMessage(self, message):
        self._queuedWidget.remove(message)

    @pyqtSlot()
    def messageShown(self):
        if not self.isVisible():
            self.setVisible(True)

    @pyqtSlot()
    def messageFinished(self):
        if self.isVisible():
            self.setVisible(False)


if msg.slot is not None:
            msg.slot(standardButton, msg)

"""Queued message tool bar. Not a public API, but available via MainWindow.appendMessage()
"""

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QEvent, Qt, QTimer
from PyQt5.QtWidgets import (QAbstractButton, QDialogButtonBox, QHBoxLayout,
                             QLabel, QSizePolicy, QToolBar, QWidget)
from PyQt5.QtGui import QColor, QBrush, QPainter, QPixmap

class _QueuedMessage:

    def __init__(self):
        self.milliSeconds = -1
        self.background = QBrush(Qt.NoBrush)
        self.foreground = QBrush(Qt.NoBrush)
        self.slot = None
        self.buttons = {}

class _QueuedMessageWidget(QWidget):

    cleared = pyqtSignal()
    finished = pyqtSignal()
    shown = pyqtSignal()
    closed = pyqtSignal()
    linkActivated = pyqtSignal(str)
    linkHovered = pyqtSignal(str)
    buttonClicked = pyqtSignal(QAbstractButton)

    def __init__(self, *args):
        QWidget.__init__(self, *args)
        self._messages = []
        self._defaultTimeout = 0
        self._defaultPixmap = QPixmap(":/enkiicons/infos.png")
        self._defaultBackground = QBrush(QColor(250, 230, 147))
        self._defaultForeground = QBrush(QColor(0, 0, 0))

        # pixmap
        self.lPixmap = QLabel(self)
        self.lPixmap.setAlignment(Qt.AlignCenter)
        self.lPixmap.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred))

        # message
        self.lMessage = QLabel(self)
        self.lMessage.setAlignment(Qt.AlignVCenter | Qt.AlignLeft)
        self.lMessage.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred))
        self.lMessage.setWordWrap(True)
        self.lMessage.setOpenExternalLinks(True)
        self.lMessage.setTextInteractionFlags(Qt.TextBrowserInteraction)

        # button
        self.dbbButtons = QDialogButtonBox(self)

        # if false - buttons don't have neither text nor icons
        self.dbbButtons.setStyleSheet("dialogbuttonbox-buttons-have-icons: true;")

        self.dbbButtons.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred))

        self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum))

        # layout
        self.hbl = QHBoxLayout(self)
        self.hbl.setContentsMargins(0, 0, 0, 0)
        self.hbl.addWidget(self.lPixmap, 0, Qt.AlignCenter)
        self.hbl.addWidget(self.lMessage)
        self.hbl.addWidget(self.dbbButtons, 0, Qt.AlignCenter)

        # connections
        self.lMessage.linkActivated.connect(self.linkActivated)
        self.lMessage.linkHovered.connect(self.linkHovered)
        self.dbbButtons.clicked.connect(self.buttonClicked)

        self._closeTimer = QTimer(self)
        self._closeTimer.setSingleShot(True)
        self._closeTimer.timeout.connect(self.closeMessage)

    def sizeHint(self):
        return QWidget.minimumSizeHint(self)

    def openExternalLinks(self):
        return self.lMessage.openExternalLinks()

    def defaultTimeout(self):
        return self._defaultTimeout

    def defaultPixmap(self):
        return self._defaultPixmap

    def defaultBackground(self):
        return self._defaultBackground

    def defaultForeground(self):
        return self._defaultForeground

    def currentMessageInformations(self):
        return self.currentMessagePixmap(), self.currentMessageBackground(), self.currentMessageForeground()

    def pendingMessageCount(self):
        return len(self._messages)

    def currentMessage(self):
        return self._messages[0]

    def append(self, message, milliSeconds):
        msg = _QueuedMessage()
        msg.message = message
        if milliSeconds == -1:
            msg.milliSeconds = self._defaultTimeout
        else:
            msg.milliSeconds = milliSeconds
        msg.pixmap = self._defaultPixmap
        msg.background = self._defaultBackground
        msg.foreground = self._defaultForeground

        self._messages.append(msg)

        if len(self._messages) == 1:
            self.showMessage()

    def setOpenExternalLinks(self, open):
        self.lMessage.setOpenExternalLinks(open)

    def setDefaultTimeout(self, timeout):
        self._defaultTimeout = timeout

    def setDefaultPixmap(self, pixmap):
        self._defaultPixmap = pixmap

    def setDefaultBackground(self, brush):
        self._defaultBackground = brush

    def setDefaultForeground(self, brush):
        self._defaultForeground = brush

    def remove(self, message):
        raise NotImplemented()  # incorrect port from cpp fresh
        if not self._messages or self._messages.first() == message:
            return

        self._messages.removeOne(message)

    def clear(self):
        self._messages.clear()
        self.lPixmap.clear()
        self.lMessage.clear()
        self.dbbButtons.clear()
        self.cleared.emit()

    def currentMessagePixmap(self):
        msg = self.currentMessage()
        if msg.pixmap.isNull():
            return self._defaultPixmap
        else:
            return msg.pixmap

    def currentMessageBackground(self):
        msg = self.currentMessage()
        if msg.background == QBrush(Qt.NoBrush):
            return self._defaultBackground
        else:
            return msg.background

    def currentMessageForeground(self):
        msg = self.currentMessage()
        if msg.foreground == QBrush(Qt.NoBrush):
            return self._defaultForeground
        else:
            return msg.foreground

    def paintEvent(self, event):
        if self.pendingMessageCount() == 0:
            QWidget.paintEvent(self, event)
            return

        painter = QPainter(self)
        painter.setPen(Qt.NoPen)
        painter.setBrush(self.currentMessageBackground())
        painter.drawRect(self.contentsRect())

    @pyqtSlot(QAbstractButton)
    def buttonClicked(self, button):
        msg = self.currentMessage()
        standardButton = self.dbbButtons.standardButton(button)

        self.closeMessage()

    def showMessage(self):
        # get message
        msg = self.currentMessage()

        # update palette
        pal = self.lMessage.palette()
        pal.setBrush(self.lMessage.foregroundRole(), self.currentMessageForeground())
        self.lMessage.setPalette(pal)

        # format widget
        self.lPixmap.setPixmap(self.currentMessagePixmap())
        self.lMessage.setText(msg.message)
        self.lMessage.setToolTip(msg.message)
        self.lMessage.setWhatsThis(msg.message)

        # set buttons
        if not msg.buttons:
            msg.buttons[QDialogButtonBox.Close] = None

        self.dbbButtons.clear()

        for button in msg.buttons.keys():
            pb = self.dbbButtons.addButton(button)

            if button in msg.buttons:
                pb.setText(msg.buttons[button])

        # auto close if needed
        if msg.milliSeconds == -1:
            timeout = self._defaultTimeout
        else:
            timeout = msg.milliSeconds

        if timeout > 0:
            self._closeTimer.setInterval(timeout)
            self._closeTimer.start()

        # signal.emit
        self.shown.emit()

    @pyqtSlot()
    def closeMessage(self):
        # message.emit
        self.closed.emit()

        # remove remove current message from hash
        self._messages = self._messages[1:]

        # process next if possible, clear gui
        if self._messages:
            self.showMessage()
        else:
            self.clearMessage()

        # finished.emit message if needed
        if not self._messages:
            self.finished.emit()

    def clearMessage(self):
        self.lPixmap.clear()
        self.lMessage.clear()
        self.dbbButtons.clear()

class QueuedMessageToolBar(QToolBar):

    def __init__(self, *args):
        QToolBar.__init__(self, *args)
        self._queuedWidget = _QueuedMessageWidget(self)

        self.setObjectName(self.metaObject().className())
        self.setMovable(False)
        self.setFloatable(False)
        self.setAllowedAreas(Qt.TopToolBarArea)
        self.toggleViewAction().setEnabled(False)
        self.toggleViewAction().setVisible(False)

        self.addWidget(self._queuedWidget)
        self.layout().setContentsMargins(3, 3, 3, 3)

        # connections
        self._queuedWidget.shown.connect(self.messageShown)
        self._queuedWidget.finished.connect(self.messageFinished)

    def queuedMessageWidget(self):
        return self._queuedWidget

    def changeEvent(self, event):
        if event.type() == QEvent.FontChange:
            self._queuedWidget.setFont(self.font())

        QToolBar.changeEvent(self, event)

    def paintEvent(self, event):
        if self._queuedWidget.pendingMessageCount() == 0:
            QToolBar.paintEvent(self, event)
            return

        brush = self._queuedWidget.currentMessageBackground()
        painter = QPainter(self)
        painter.setPen(brush.color().darker(150))
        painter.setBrush(brush)
        painter.drawRect(self.contentsRect().adjusted(0, 0, -1, -1))

    def appendMessage(self, message, milliSeconds=-1):
        return self._queuedWidget.append(message, milliSeconds)

    def removeMessage(self, message):
        self._queuedWidget.remove(message)

    @pyqtSlot()
    def messageShown(self):
        if not self.isVisible():
            self.setVisible(True)

    @pyqtSlot()
    def messageFinished(self):
        if self.isVisible():
            self.setVisible(False)