#
# Blender Add-on to upload blender files to Viribus Render Farm (www.viribusfarm.com)
#
# (c) 2025 Quin.Online, s.r.o.
#
# You can copy this script, re-distribute it, use it for personal or commercial purposes, you can modify it.
# But you cannot sell it.
#
# Preverit moznost pouzit: bpy.ops.wm.infoprint(message='hello')
#

bl_info = {
    "name": "Viribus Render Farm Files Upload",
    "author": "Quin.Online, s.r.o.",
    "version": (0, 99, 1),
    "blender": (2, 80, 0),
    "location": "Properties > Render > ViribusFarm panel",
    "description": "Upload files to Blender dedicated render farm",
    "warning": "",
    "doc_url": "",
    "category": "Render farm service",
}


import bpy
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

import random
import string
from bpy.types import (Panel, Operator, AddonPreferences)
from datetime import datetime
import os
import re
import textwrap
import hashlib
import csv
import zipfile
import tempfile
import json
import glob
import platform


const_viribus_farm_upload_url = 'https://www.viribusfarm.com/upload_by_script/u16.php'
const_viribus_farm_ping_url = 'https://www.viribusfarm.com/upload_by_script/ping.php'
const_viribus_check_avail_url = 'https://www.viribusfarm.com/upload_by_script/check_avail.php'
const_viribus_check_version_supported = 'https://www.viribusfarm.com/upload_by_script/is_addon_version_supported.php'

supported_up_to_version = (4, 3, 32)
const_web_ping_ok = 'ok'
const_service_available = 'available'
const_service_upload_finished = 'upload_finished'
const_service_upload_failed = 'upload_failed'
const_2fa_need_yes = 'yes'

const_service_response_error_received = 'Error message has been received.'

const_operation_UPLOAD = 'UPLOAD'
const_operation_CONTINUE_UPLOADING = 'CONTINUE_UPLOADING'
const_operation_CHECK_EXIST = 'CHECK EXIST'
const_operation_CREATE_FILE_LIST = 'CREATE FILE LIST'

const_uploading_progress_file_name = 'uploading_files.tmp'
const_no_chunk_uploaded = -1
const_chunks_complete = 999999999
const_err_file_content_size = 32000

const_addon_version_supported = 'yes'

const_param_name_params_hash = 'post_params_hash'
const_param_name_chunk_hash = 'chunk_hash'
const_param_name_number_of_params = 'number_of_params'

const_file_source_sequence = 'SEQUENCE'
const_file_source_file = 'FILE'
const_file_source_tiled = 'TILED'

const_chosen_output_type_anim = 'ANIMATION'
const_chosen_output_type_img = 'IMAGE'
const_chosen_output_type_not_chsn = 'NOT CHOSEN'

const_response_uploading_started = 'Uploading started'
const_response_uploading_resumed = 'Uploading resumed'
const_response_file_uploaded_success = 'File has been uploaded successfully'
const_response_task_uploaded_successfully = 'Render task has been uploaded successfully.'
const_response_uploading_cancelled = 'Cancelled'
const_response_uploading_deleted = 'Deleted'


const_verify_ssl = True

create_log_file = True

def get_work_dir_for_zip_files ():
    return (tempfile.gettempdir())

def get_translate_text (p_text_code):
    viribus_translations = {
                            "en_US": {
                                      "File has been uploaded successfully": "File has been uploaded successfully", 
                                      "Login failed": "Login failed, check your login name and password", 
                                      "Account locked": "Your account is locked. Visit our web page - www.viribusfarm.com - to unlock it.",
                                      "Login failed, account has been locked": "Login failed. Your account has been locked. Visit our web page - www.viribusfarm.com - to unlock it.",
                                      "Verify needed": "Your account has not been activated yet. You can activate it using the link we sent you by email message.",
                                      "File does not exist": "File does not exist", 
                                      "Error during upload": "Error during upload file",
                                      "Must be set before upload": "Must be set before upload",
                                      "Login name": "Login name",
                                      "Password": "Password",
                                      # "I am aware plain text password": "I am aware the password is stored in an open form (plain text) in file on my hard drive.",
                                      # "Upload files via web to avoid plain password": "If you want to avoid storing password in plain text format, upload files via our web interface (www.viribusfarm.com)",
                                      "Empty password -> you will be asked": "If you leave password field empty, password will be asked before uploading.",
                                      "Plain text password": "If you put password here, it will be stored as PLAIN TEXT in file on your hard drive and you do not need to put it before every upload.",
                                      "Save file first, please.": "Save file first, please.",
                                      "File ": "File ",
                                      " is too big. Max file size is: ": " is too big. Max file size is: ",
                                      "Render task has been uploaded successfully.": "Render task has been uploaded successfully.",
                                      "You do NOT have permission to create a task. Contact your group administrator.": "You do NOT have permission to create a task. Contact your group administrator."
                                     },
                            "sk_SK": {
                                      "File has been uploaded successfully": "Súbor bol nahraný úspešne", 
                                      "Login failed": "Prihlásenie neúspešné, skontrolujte prihlasovacie mena a heslo", 
                                      "Account locked": "Váš účet je zamknutý. Navštívte našu webovú stránku - www.viribusfarm.com - kde si môžete účet odomknúť",
                                      "Login failed, account has been locked": "Prihlásenie nebolo úspešné. Váš účet bol zamknutý. Navštívte našu webovú stránku - www.viribusfarm.com - kde si môžete účet odomknúť.",
                                      "Verify needed": "Váš účet nebol ešte aktivovaný. Aktivovať si ho môžete linkou, ktorú sme Vám zaslali emailom.",
                                      "File does not exist": "Súbor neexistuje", 
                                      "Error during upload": "Chyba pri nahrávaní súboru",
                                      "Must be set before upload": "Prihlasovacie údaje musíte zadať pred nahrávaním súboru",
                                      "Empty password -> you will be asked": "Ak necháte heslo nevyplnené, budete mať možnosť ho zadať pred každým nahraním (uploadom) súboru.",
                                      "Plain text password": "Ak zadáte heslo sem, nemusíte ho zadávať pred každým nahrávaním (uploadom) súboru, ale bude uložené na vašom disku v čitatelnej forme.",
                                      "Save file first, please.": "Najprv uložte súbor.",
                                      "File ": "Súbor ",
                                      " is too big. Max file size is: ": " je príliš veľký. Maximálna veľkosť je: ",
                                      "Render task has been uploaded successfully.": "Projekt bol odoslaný úspešne.",
                                      "You do NOT have permission to create a task. Contact your group administrator.": "Nemáte právo na vytvorenie úlohy. Ak potrebujete vytvárať úlohy, kontaktujte administrátora Vašej skupiny."
                                     },
                            "cs_CZ": {
                                      "File has been uploaded successfully": "Soubor byl přenesen úspěšne", 
                                      "Login failed": "Přihlášení neuspéšné, prověřte vaše přihašovací údaje - jméno a heslo", 
                                      "File does not exist": "Soubor neexistuje", 
                                      "Error during upload": "Chyba počas nahrávaní souboru",
                                      "Must be set before upload": "Přihlasovací údaje musíte zadat před nahrávaním souboru" 
                                     }
                           }

    lang_code = 'en_US'
    use_trans_intf = False

    lang_code = bpy.context.preferences.view.language
    use_trans_intf = bpy.context.preferences.view.use_translate_interface

    if (lang_code not in viribus_translations or
        not use_trans_intf):
        lang_code = 'en_US'
    
    if (not p_text_code in viribus_translations[lang_code]):
        return (p_text_code)
      
    return (viribus_translations[lang_code][p_text_code])
            
def show_message_if_known (p_message, p_operator):
    if (p_message == "Login failed" or 
        p_message == "Account locked" or
        p_message == "Login failed, account has been locked" or
        p_message == "Verify needed" or
        p_message == "You do NOT have permission to create a task. Contact your group administrator." or
        p_message == "Wrong 2FA code." or
        p_message == "Error during upload" or
        p_message == "Expired 2FA code"):
        # self.report ({'ERROR'}, p_message)
        # self.report ({'ERROR'}, get_translate_text (p_text_code=p_message))
        p_operator.report ({'ERROR'}, get_translate_text (p_text_code=p_message))
    else:
        p_operator.report ({'ERROR'}, p_message)
      

def add_dir_delimiter_if_needed (p_target_string):
    directory_delimiter = os.path.sep
    return_value = p_target_string
    if (p_target_string is not None and p_target_string != "" and len(p_target_string) > 0 and p_target_string[-1] != directory_delimiter):
        return_value = "{}{}".format (p_target_string, directory_delimiter)
    return (return_value)


def add_text (p_target, p_text):
    if (p_target == ""):
        return (p_text)
    return ("{}\n{}".format (p_target, p_text))



def get_hash_for_dict (p_input_dict):
    dict_keys = list (p_input_dict.keys())
    dict_keys.sort()
    params_hash = hashlib.md5()
    for dict_key in dict_keys:
        if (dict_key != 'chunk_data'):
            params_hash.update(str(p_input_dict[dict_key]).encode('utf-8'));
    return (params_hash.hexdigest())


    # -----------------------------------------------------------------------------------------------------------------------

def get_mask_for_files (p_file_name, p_string_to_add):

    file_name_length = len (p_file_name)-1
    dot_index = file_name_length
    if (dot_index <= 0):
        return None

    while (dot_index > 0 and p_file_name[dot_index] != '.'):
      dot_index -= 1

    if (dot_index <= 0 or p_file_name[dot_index] != '.'):
      return None

    mask_to_char_index = dot_index - 1

    while (mask_to_char_index >= 0 and p_file_name[mask_to_char_index].isnumeric()):
      mask_to_char_index -= 1

    return ('^{}{}{}$'.format (p_file_name[0:mask_to_char_index+1], p_string_to_add, p_file_name[dot_index:file_name_length+1]))

# -----------------------------------------------------------------------------------------------------------------------

def get_file_name_to_upload (p_file_name, p_file_index):

    file_name_length = len (p_file_name)-1
    dot_index = file_name_length
    if (dot_index <= 0):
        return None

    while (dot_index > 0 and p_file_name[dot_index] != '.'):
      dot_index -= 1

    if (dot_index <= 0 or p_file_name[dot_index] != '.'):
      return None

    mask_to_char_index = dot_index - 1

    while (mask_to_char_index >= 0 and p_file_name[mask_to_char_index].isnumeric()):
      mask_to_char_index -= 1

    return ('{}{}{}'.format (p_file_name[0:mask_to_char_index+1], p_file_index, p_file_name[dot_index:file_name_length+1]))

# -----------------------------------------------------------------------------------------------------------------------

def get_number_from_file_name (p_file_name):
    file_name_length = len (p_file_name)-1
    dot_index = file_name_length
    if (dot_index <= 0):
        return None

    while (dot_index > 0 and p_file_name[dot_index] != '.'):
      dot_index -= 1

    if (dot_index <= 0 or p_file_name[dot_index] != '.'):
      return None

    mask_to_char_index = dot_index - 1

    while (mask_to_char_index >= 0 and p_file_name[mask_to_char_index].isnumeric()):
      mask_to_char_index -= 1

    return (p_file_name[mask_to_char_index+1:dot_index])

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def get_full_relative_path (p_obj):
    return_value = ''
    if (p_obj.filepath.startswith('//')):
        if (hasattr (p_obj, 'library')):
            if (p_obj.library is not None):
                tmp_lib_pathfile = get_full_relative_path (p_obj.library)
                if (tmp_lib_pathfile != '' and (tmp_lib_pathfile.startswith('//') or tmp_lib_pathfile[1] == ':')):
                    tmp_norm_path = os.path.normpath (os.path.join (os.path.dirname(tmp_lib_pathfile[2:]), p_obj.filepath[2:]))
                    return_value = "{}{}".format (tmp_lib_pathfile[0:2], tmp_norm_path)
                else:
                    return_value = os.path.normpath (os.path.join (os.path.dirname(tmp_lib_pathfile), p_obj.filepath[2:]))
            else:
                return_value = p_obj.filepath
        else:
            return_value = p_obj.filepath
    else:
        return_value = p_obj.filepath
    return (return_value)

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def get_path_from_main_file (p_obj):
    return (get_full_relative_path (p_obj))

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def get_absolute_path (p_obj):
    if (p_obj.filepath.startswith ('//')):
        tmp_full_rel_path = get_full_relative_path (p_obj)
        if (tmp_full_rel_path == '' or tmp_full_rel_path.startswith ('//')):
            return_value = os.path.normpath (os.path.join (os.path.dirname (bpy.data.filepath), tmp_full_rel_path[2:]))
        else:
            return_value = tmp_full_rel_path
    else:
        return_value = p_obj.filepath
    return (return_value)

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def read_file_in_chunks(file_object, chunk_size):
    while True:
        data = file_object.read(chunk_size)
        if not data:
            break
        yield data

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def construct_relative_path (p_library_file_path, p_texture_filepath):
    return_value = ''

    if (p_texture_filepath.startswith('//') and p_library_file_path is not None and p_library_file_path != ''):
        if (p_library_file_path.startswith('//') or p_library_file_path[1] == ':'):
            tmp_path = os.path.join (os.path.dirname (p_library_file_path[2:]), p_texture_filepath[2:])
            return_value = "{}{}".format (p_library_file_path[0:2], os.path.normpath (tmp_path))
        else:
            tmp_path = os.path.join (os.path.dirname (p_library_file_path), p_texture_filepath[2:])
            return_value = os.path.normpath (tmp_path)
    else:
        return_value = p_texture_filepath

    return (return_value.replace('\\', '/'))

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def get_log_file (p_upload_tag):
    return (os.path.join (tempfile.gettempdir(), 'viribus_farm_{}.log'.format(p_upload_tag)))

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def add_message_to_log_file (p_message, p_upload_tag):
    # with open (get_log_file (p_upload_tag), 'a') as file_to_write:
    file_to_write = open (get_log_file (p_upload_tag), 'a')
    file_to_write.write ("[" + datetime.now().strftime("%d.%m.%Y %H:%M:%S") + "] " + p_message + "\n")
    file_to_write.close ()

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def show_message (p_message, p_upload_tag):
    print (p_message);
    if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
        add_message_to_log_file (p_message, p_upload_tag);

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def get_zip_file_name (p_base_file_name):
    return_value = os.path.join (tempfile.gettempdir(), '{}.zip'.format (p_base_file_name));
    file_number = 0
    while (os.path.exists (return_value)):
        return_value = os.path.join (tempfile.gettempdir(), '{}.{}.zip'.format (p_base_file_name, file_number));
        file_number = file_number + 1;

    return (return_value);

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

def send_one_file (p_one_file, p_https_session, p_upload_tag, p_user_login_name, p_user_password):
    show_message (p_message="Sending file: {} {} {}".format (p_one_file['absolute_file_name'], p_one_file['file_type'], p_one_file['path_to_upload']), p_upload_tag=p_upload_tag)
    # ak je zvolene, ze posielat zipovane subory, tak ho najprv skomprimuj a potom posli. Ak je zvolene ne-komprimovat, posli subor rovno
    # prechadzaj subor po nastavenych velkostiach (chunkoch) a postupne ich posielaj
    # po poslani vsetkych chunkov pre dany subor posli spravu o dokonceni uploadu suboru
    
    operation_status = "OK"
    
    one_file_to_upload = p_one_file['absolute_file_name']
    param_to_send_use_zip = 'N'
    if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_use_zip == True):
        # one_file_to_upload = os.path.join (tempfile.gettempdir(), '{}.zip'.format (os.path.basename (p_one_file['absolute_file_name'])));
        one_file_to_upload = get_zip_file_name (os.path.basename (p_one_file['absolute_file_name']))
        show_message (p_message='Creating zip file: {}'.format (one_file_to_upload), p_upload_tag=p_upload_tag)

        with zipfile.ZipFile (one_file_to_upload, mode='w') as zip_file_to_write:
            zip_file_to_write.write (p_one_file['absolute_file_name'], os.path.basename (p_one_file['absolute_file_name']))
        param_to_send_use_zip = 'Y'

    chunk_number = 1        
    megabytes_sent = 0
    chunk_size = bpy.context.preferences.addons[__name__].preferences.viribus_farm_upload_chunk_size * 1024 * 1024
    # with open(one_file_to_upload, 'rb') as file_to_upload:
    file_to_upload = open(one_file_to_upload, 'rb')
    for one_file_chunk in read_file_in_chunks(file_to_upload, chunk_size):
        if (operation_status == "OK"):
            if (chunk_number > p_one_file['last_chunk_number']):

                actual_chunk_size = len (one_file_chunk);

                post_req_params = { 'username': p_user_login_name, 
                                    'password': p_user_password, 
                                    'operation_type': 'upload_file', 
                                    'upload_tag': p_upload_tag, 
                                    'file_type': p_one_file['file_type'], 
                                    'chunk_number': chunk_number, 
                                    'chunk_data': one_file_chunk, 
                                    'hash_value': hashlib.md5(one_file_chunk).hexdigest(),
                                    'path_from_main_file': p_one_file ['path_from_main_file'],
                                    'file_number': p_one_file ['file_number'],
                                    'chunk_size': actual_chunk_size
                                    }
                number_of_params = len (post_req_params)
                post_req_params[const_param_name_params_hash] = get_hash_for_dict (post_req_params)
                post_req_params[const_param_name_chunk_hash] = hashlib.md5(one_file_chunk).hexdigest()
                post_req_params[const_param_name_number_of_params] = number_of_params

                post_response = p_https_session.post (const_viribus_farm_upload_url, data = post_req_params, verify=const_verify_ssl)
                show_message (p_message='post_response.text: {}'.format (post_response.text), p_upload_tag=p_upload_tag)
                post_response_array = json.loads (post_response.text)

                if (post_response_array ['return_value'] == hashlib.md5(one_file_chunk).hexdigest()):
                    
                    p_one_file['last_chunk_number'] = chunk_number
                    
                    megabytes_sent = megabytes_sent + (actual_chunk_size / 1024 / 1024)
                    sent_size_string = "{} MB has been sent".format(round (megabytes_sent, 4))
                    show_message (p_message=sent_size_string, p_upload_tag=p_upload_tag)
                else:
                    if (post_response_array ['return_value'] == const_response_uploading_cancelled):
                        show_message (p_message="Uploading has been cancelled", p_upload_tag=p_upload_tag)
                        operation_status = "Uploading has been cancelled"
                    else:  
                        if (post_response_array ['return_value'] == const_response_uploading_deleted):
                            show_message (p_message="Uploading has been deleted", p_upload_tag=p_upload_tag)
                            operation_status = "Uploading has been deleted"
                        else:
                            show_message (p_message="Error occured: {}".format (post_response_array ['return_value']), p_upload_tag=p_upload_tag)
                            operation_status = "Error during upload (9)"

                post_response.close ()

            chunk_number = chunk_number + 1
    file_to_upload.close ()
    
    if (operation_status == "OK"):
        if (const_chunks_complete > p_one_file['last_chunk_number']):

            # with open(one_file_to_upload, 'rb') as file_to_upload:
            file_to_upload = open(one_file_to_upload, 'rb')
            file_contents = file_to_upload.read()
            file_md5_hash = hashlib.md5(file_contents).hexdigest()
            file_to_upload.close ()

            if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True): 
                add_message_to_log_file (p_message="Sending message (uploading finished) for file: {}".format (p_one_file['absolute_file_name']), p_upload_tag=p_upload_tag)
                add_message_to_log_file (p_message="Path from main file: {}".format (p_one_file['path_from_main_file']), p_upload_tag=p_upload_tag)

            post_req_params = { 'username': p_user_login_name, 
                                'password': p_user_password, 
                                'operation_type': 'finish_uploading_one_file', 
                                'upload_tag': p_upload_tag, 
                                'file_type': p_one_file['file_type'], 
                                'path_to_file': p_one_file['filepath'], 
                                'library_path': p_one_file['library_path'],
                                'original_file_name': os.path.basename(p_one_file['absolute_file_name']), 
                                'file_is_zip': param_to_send_use_zip,
                                'file_source': p_one_file['file_source'],
                                'file_name_from_node': p_one_file['file_name_from_node'],
                                'file_number': p_one_file['file_number'],
                                'hash_value': p_one_file['file_hash'],
                                'chunks_count': chunk_number - 1,
                                'path_from_main_file': p_one_file['path_from_main_file'], 
                                'path_to_upload': one_file_to_upload
                                }
            number_of_params = len (post_req_params)
            post_req_params[const_param_name_params_hash] = get_hash_for_dict (post_req_params)
            post_req_params[const_param_name_number_of_params] = number_of_params

            post_response = p_https_session.post (const_viribus_farm_upload_url, data = post_req_params, verify=const_verify_ssl)
            if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
                add_message_to_log_file (p_message='finish_uploading_one_file - post_response.text: {}'.format (post_response.text), p_upload_tag=p_upload_tag)
                
            post_response_array = json.loads (post_response.text)

            if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True): 
                add_message_to_log_file (p_message="(D) finish_uploading_one_file " + post_response_array ['return_value'] + '; ' + p_one_file['absolute_file_name'], p_upload_tag=p_upload_tag)
        
            if (post_response_array ['return_value'] == const_response_file_uploaded_success):
                p_one_file['last_chunk_number'] = const_chunks_complete
                
            else:           
                if (post_response_array ['return_value'] == const_response_uploading_cancelled):
                    show_message (p_message="Uploading has been cancelled", p_upload_tag=p_upload_tag)
                    operation_status = "Uploading has been cancelled"
                else:  
                    if (post_response_array ['return_value'] == const_response_uploading_deleted):
                        show_message (p_message="Uploading has been deleted", p_upload_tag=p_upload_tag)
                        operation_status = "Uploading has been deleted"
                    else:  
                        show_message (p_message="Error occured: {}".format (post_response_array ['return_value']), p_upload_tag=p_upload_tag)
                        operation_status = "Error during upload (10)"
                        if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True): 
                            add_message_to_log_file (p_message="finish_uploading_one_file - Sent NOT successfull - Sending message (uploading finished) for file: {}; response: {}".format (p_one_file['absolute_file_name'], post_response_array ['return_value']), p_upload_tag=p_upload_tag)
            post_response.close ()
            
    if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_use_zip == True):
        show_message (p_message="Deleting file: {}".format (one_file_to_upload), p_upload_tag=p_upload_tag)
        os.remove (one_file_to_upload);
            
    return (operation_status)
        
# --------------------------------------------------------------------------------------------------------------------------------

def send_missing_file_info (p_missing_file, p_https_session, p_upload_tag, p_user_login_name, p_user_password):
     
    show_message (p_message="Sending file: {} {} {}".format (p_missing_file['absolute_file_name'], p_missing_file['file_type'], p_missing_file['path_to_upload']), p_upload_tag=p_upload_tag)
    operation_status = "OK"
    
    
    post_req_params = { 'username': p_user_login_name, 
                        'password': p_user_password, 
                        'operation_type': 'missing_file_info', 
                        'upload_tag': p_upload_tag, 
                        'file_type': p_missing_file['file_type'], 
                        'path_to_file': p_missing_file['filepath'],
                        'library_path': p_missing_file['library_path'],
                        'original_file_name': os.path.basename(p_missing_file['absolute_file_name']), 
                        'file_source': p_missing_file['file_source'],
                        'file_name_from_node': p_missing_file['file_name_from_node'],
                        'file_number': p_missing_file['file_number'],
                        'path_from_main_file': p_missing_file['path_from_main_file'],
                        'path_to_upload': p_missing_file['absolute_file_name']
                        }
    number_of_params = len (post_req_params)
    post_req_params[const_param_name_params_hash] = get_hash_for_dict (post_req_params)
    post_req_params[const_param_name_number_of_params] = number_of_params

    post_response = p_https_session.post (const_viribus_farm_upload_url, data = post_req_params, verify=const_verify_ssl)
    if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
        add_message_to_log_file (p_message='missing_file_info - post_response.text: {}'.format (post_response.text), p_upload_tag=p_upload_tag)    
    post_response_array = json.loads (post_response.text)

    if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True): 
        add_message_to_log_file (p_message="(E) missing_file_info - " + post_response_array ['return_value'] + '; ' + p_missing_file['absolute_file_name'], p_upload_tag=p_upload_tag)

    if (post_response_array ['return_value'] == const_response_file_uploaded_success):
        p_missing_file['last_chunk_number'] = const_chunks_complete
    else:              
        if (post_response_array ['return_value'] == const_response_uploading_cancelled):
            show_message (p_message="Uploading has been cancelled", p_upload_tag=p_upload_tag)
            operation_status = "Uploading has been cancelled"
        else:  
            if (post_response_array ['return_value'] == const_response_uploading_deleted):
                show_message (p_message="Uploading has been deleted", p_upload_tag=p_upload_tag)
                operation_status = "Uploading has been deleted"
            else:  
                show_message (p_message="Error occured: {}".format (post_response_array ['return_value']), p_upload_tag=p_upload_tag)
                operation_status = "Error during upload (10)"
                if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True): 
                    add_message_to_log_file (p_message="missing_file_info - Sent NOT successfull - Sending message (missing file) for file: {}; response: {}".format (p_missing_file['absolute_file_name'], post_response_array ['return_value']), p_upload_tag=p_upload_tag)
    post_response.close ()
    
    return (operation_status)

# --------------------------------------------------------------------------------------------------------------------------------

def get_count_of_uploading_files (p_file_list):
    return_value = 0;
    for one_file in p_file_list:
        if (p_file_list[one_file]['file_exists'] == 'Y'):
            return_value = return_value + 1;
    return (return_value)
  
# --------------------------------------------------------------------------------------------------------------------------------

def get_files_size (p_file_list):
    return_value = 0;
    for one_file in p_file_list:
        if (p_file_list[one_file]['file_exists'] == 'Y'):
            return_value = return_value + p_file_list[one_file]['file_size'];
    return (return_value)
  

# --------------------------------------------------------------------------------------------------------------------------------

def generate_new_upload_tag ():
    return (datetime.now().strftime("%Y%m%d_%H%M%S_{}".format ("".join(random.choice(string.ascii_lowercase) for i in range(10)))))

# --------------------------------------------------------------------------------------------------------------------------------

def reset_last_chunk_numbers (p_file_list):
    for file_index in p_file_list.keys():
        p_file_list[file_index]['last_chunk_number'] = const_no_chunk_uploaded
    
# --------------------------------------------------------------------------------------------------------------------------------
    
def upload_files (p_file_list, p_upload_tag, p_user_name, p_password, p_2FA_code, p_chosen_output_type, p_operator):

    operation_status = 'OK'
    count_of_sent_files = 0

    # ziskaj parametre
    instant_render = 'N'
    if (bpy.context.scene.ViribusFarmProperties.viribus_farm_render == True):
        instant_render = 'Y'
        
    set_optimal = 'N'
    if (bpy.context.scene.ViribusFarmProperties.viribus_farm_set_optimal == True):
        set_optimal = 'Y'

    render_if_error = 'N'
    if (bpy.context.scene.ViribusFarmProperties.viribus_farm_render_if_error == True):
        render_if_error = 'Y'
        
    use_dynamic_priority = 'N'
    if (bpy.context.scene.ViribusFarmProperties.viribus_farm_dynamic_priority == True):
        use_dynamic_priority = 'Y'
        
    # if bpy.app.version >= (2, 80, 0):
    project_name = bpy.context.scene.ViribusFarmProperties.viribus_farm_project_name
    project_description = bpy.context.scene.ViribusFarmProperties.viribus_farm_project_description
    
    counted_files = get_count_of_uploading_files (p_file_list);
    computed_files_size = get_files_size (p_file_list);

    # otvor session
    viribus_session = requests.Session()
    viribus_session.mount ('https://', HTTPAdapter (max_retries=Retry (connect=bpy.context.preferences.addons[__name__].preferences.viribus_farm_upload_retry_count, backoff_factor=bpy.context.preferences.addons[__name__].preferences.viribus_farm_upload_retry_delay)))
    bpy.types.WindowManager.viribus_farm_last_upload_status = 'uploading'

    print ("continue_uploading (1): {}".format (bpy.types.WindowManager.continue_uploading));
    print ("viribus_farm_upload_tag (1): {}".format (bpy.types.WindowManager.viribus_farm_upload_tag));
    print ("p_upload_tag (1): {}".format (p_upload_tag));

    # over ci existuje prislusny zaznam na serveri
    if (bpy.types.WindowManager.continue_uploading == True):
        # does_upload_tag_exist_on_server = check_upload_tag_on_server (p_session=viribus_session, p_addon_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
        post_req_params = { 
                            'username': p_user_name, 
                            'password': p_password, 
                            # 'user_2fa_code': p_2FA_code,
                            'operation_type': 'check_existing_tag', 
                            'upload_tag': p_upload_tag
                            }
        number_of_params = len (post_req_params)
        post_req_params[const_param_name_params_hash] = get_hash_for_dict (post_req_params)
        post_req_params[const_param_name_number_of_params] = number_of_params

        post_response = viribus_session.post (const_viribus_farm_upload_url, data = post_req_params, verify=const_verify_ssl)
        if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
            add_message_to_log_file (p_message='check upload tag - post_response.text: {}'.format(post_response.text), p_upload_tag=p_upload_tag)    
        print ('check upload tag - post_response.text: {}'.format(post_response.text))
        post_response_array = json.loads (post_response.text)
        
        post_response.close ()
        
        
        if (post_response_array ['return_value'] != 'yes'):
            bpy.types.WindowManager.viribus_farm_upload_tag = generate_new_upload_tag()
            p_upload_tag = bpy.types.WindowManager.viribus_farm_upload_tag
            reset_last_chunk_numbers (bpy.types.WindowManager.viribus_farm_uploading_file_list)
            bpy.types.WindowManager.continue_uploading = False
    
    print ("continue_uploading (2): {}".format (bpy.types.WindowManager.continue_uploading));
    print ("viribus_farm_upload_tag (2): {}".format (bpy.types.WindowManager.viribus_farm_upload_tag));
    print ("p_upload_tag (2): {}".format (p_upload_tag));
    
    # postupne posielaj https requesty
    if (bpy.types.WindowManager.continue_uploading == True):
        post_req_params = { 
                            'username': p_user_name, 
                            'password': p_password, 
                            'user_2fa_code': p_2FA_code,
                            'operation_type': 'resume_upload', 
                            'upload_tag': p_upload_tag
                            }
        number_of_params = len (post_req_params)
        post_req_params[const_param_name_params_hash] = get_hash_for_dict (post_req_params)
        post_req_params[const_param_name_number_of_params] = number_of_params

        post_response = viribus_session.post (const_viribus_farm_upload_url, data = post_req_params, verify=const_verify_ssl)
        if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
            add_message_to_log_file (p_message='resume_upload - post_response.text: {}'.format(post_response.text), p_upload_tag=p_upload_tag)    
        
        post_response_array = json.loads (post_response.text)
        
        post_response.close ()

        if (post_response_array ['return_value'] != const_response_uploading_resumed):
            operation_status = 'Error during resumig upload'
            show_message_if_known (p_message=post_response_array ['return_value'], p_operator=p_operator)
            message_to_show = "Error during uploading, exiting; return value (from upload module): |{}|".format (post_response_array ['return_value'])
            show_message (p_message=message_to_show, p_upload_tag=p_upload_tag)
        print ("Pokracujem v uploade")
    else:
        # zaciatok uploadu
        post_req_params = { 
                            'username': p_user_name, 
                            'password': p_password, 
                            'user_2fa_code': p_2FA_code,
                            'operation_type': 'start_upload', 
                            'upload_tag': p_upload_tag, 
                            'blender_version_major': bpy.app.version[0], 
                            'blender_version_minor': bpy.app.version[1], 
                            'blender_version_patch': bpy.app.version[2], 
                            'addon_version_major': bl_info['version'][0],
                            'addon_version_minor': bl_info['version'][1],
                            'addon_version_patch': bl_info['version'][2],
                            'project_name': project_name, 
                            'project_description': project_description, 
                            'instant_render': instant_render,
                            'set_optimal': set_optimal, 
                            'render_if_error': render_if_error,
                            'priority': bpy.context.scene.ViribusFarmProperties.viribus_farm_priority,
                            'dynamic_priority': use_dynamic_priority,
                            'cycles_render_device_user': bpy.context.scene.ViribusFarmProperties.viribus_farm_cycles_device,
                            'cycles_render_device_prefs': bpy.context.preferences.addons["cycles"].preferences.compute_device_type,
                            'chosen_output_type': p_chosen_output_type,
                            'files_to_send_count': counted_files,
                            'files_to_send_size': computed_files_size,
                            'os_info': "{}; {}; {};".format (platform.system(), platform.release(), platform.version())
                            }
        number_of_params = len (post_req_params)
        post_req_params[const_param_name_params_hash] = get_hash_for_dict (post_req_params)
        post_req_params[const_param_name_number_of_params] = number_of_params

        post_response = viribus_session.post (const_viribus_farm_upload_url, data = post_req_params, verify=const_verify_ssl)
        if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
            add_message_to_log_file (p_message='start_upload - post_response.text: {}'.format(post_response.text), p_upload_tag=p_upload_tag)    
        
        post_response_array = json.loads (post_response.text)
        
        post_response.close ()
    
        if (post_response_array ['return_value'] != const_response_uploading_started):
            operation_status = 'Error during starting upload'
            show_message_if_known (p_message=post_response_array ['return_value'], p_operator=p_operator)
            message_to_show = "Error during uploading, exiting; return value (from upload module): |{}|".format (post_response_array ['return_value'])
            show_message (p_message=message_to_show, p_upload_tag=p_upload_tag)
        
        print ("Novy upload")
    if (operation_status == 'OK'):
        for one_file_to_send in p_file_list:
            if (operation_status == 'OK'):
                if (p_file_list[one_file_to_send]['file_exists'] == 'Y'):
                    operation_status = send_one_file (p_one_file=p_file_list[one_file_to_send], p_https_session=viribus_session, p_upload_tag=p_upload_tag, p_user_login_name=p_user_name, p_user_password=p_password)
                    count_of_sent_files = count_of_sent_files + 1
                if (p_file_list[one_file_to_send]['file_exists'] != 'Y'):
                    operation_status = send_missing_file_info (p_missing_file=p_file_list[one_file_to_send], p_https_session=viribus_session, p_upload_tag=p_upload_tag, p_user_login_name=p_user_name, p_user_password=p_password)

    if (operation_status == "OK"):
        post_req_params = { 
                            'username': p_user_name, 
                            'password': p_password, 
                            'operation_type': const_service_upload_finished, 
                            'upload_tag': p_upload_tag,
                            'files_count': count_of_sent_files # len (p_file_list)
                            }
        number_of_params = len (post_req_params)
        post_req_params[const_param_name_params_hash] = get_hash_for_dict (post_req_params)
        post_req_params[const_param_name_number_of_params] = number_of_params

        post_response = viribus_session.post (const_viribus_farm_upload_url, data = post_req_params, verify=const_verify_ssl)
        
        if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
            add_message_to_log_file (p_message='{} - post_response.text: {}'.format (const_service_upload_finished, post_response.text), p_upload_tag=p_upload_tag)
        
        post_response_array = json.loads (post_response.text)

        if (post_response_array ['return_value'] == const_response_task_uploaded_successfully):
            p_operator.report ({'INFO'}, get_translate_text (post_response_array ['return_value']))
            show_message (p_message='INFO: {}'.format (get_translate_text (post_response_array ['return_value'])), p_upload_tag=p_upload_tag)
            
            bpy.types.WindowManager.viribus_farm_last_upload_status = "success"
        else:  
            if (post_response_array ['return_value'] == const_response_uploading_cancelled):
                show_message (p_message="Uploading has been cancelled", p_upload_tag=p_upload_tag)
                operation_status = "Uploading has been cancelled"
                bpy.types.WindowManager.viribus_farm_last_upload_status = "cancelled"
            else:  
                if (post_response_array ['return_value'] == const_response_uploading_deleted):
                    show_message (p_message="Uploading has been deleted", p_upload_tag=p_upload_tag)
                    operation_status = "Uploading has been deleted"
                    bpy.types.WindowManager.viribus_farm_last_upload_status = "deleted"
                else:  
                    show_message (p_message='ERROR: {}'.format (operation_status + '; ' + post_response_array ['return_value']), p_upload_tag=p_upload_tag)
                    operation_status = post_response_array ['return_value']
                    bpy.types.WindowManager.viribus_farm_last_upload_status = "error"
        post_response.close ()

    if (operation_status != "OK"):
        p_operator.report ({'WARNING'}, get_translate_text (operation_status))
    
    return (operation_status)































class dialog_2FA_code_class (bpy.types.Operator):
    bl_idname = "object.dialog_vf_sec_lvl_auth"
    bl_label = "Send 2FA code"

    user_2FA_code_input: bpy.props.StringProperty (name=get_translate_text ("2FA code"), description="2FA code", default = "")
    # user_2FA_code_input: bpy.props.StringProperty (subtype="PASSWORD")

    def __init__ (self):
        self.type_of_2FA = ''
        self.contact_for_2FA = ''

    def execute (self, context):
        
        user_login_name = bpy.context.preferences.addons[__name__].preferences.viribus_farm_login_name
        user_password = bpy.types.WindowManager.viribusfarm_password
        chosen_output_type = bpy.types.WindowManager.viribusfarm_output_type
        upload_tag = bpy.types.WindowManager.viribus_farm_upload_tag
        
        uploading_status = upload_files (p_file_list=bpy.types.WindowManager.viribus_farm_uploading_file_list, p_upload_tag=upload_tag, p_user_name=user_login_name, p_password=user_password, p_2FA_code=self.user_2FA_code_input, p_chosen_output_type=chosen_output_type, p_operator=self)
        # print ('uploading_status: {}'.format (uploading_status))
        
        return {'FINISHED'}
    
    def cancel (self, context):
        return {'CANCELLED'}

    def invoke (self, context, event):
        self.type_of_2FA = bpy.types.WindowManager.viribusfarm_type_of_2FA
        self.contact_for_2FA = bpy.types.WindowManager.viribusfarm_contact_for_2FA
        return context.window_manager.invoke_props_dialog (self)

    def draw (self, context):
        row = self.layout
        if (self.type_of_2FA == "OTP"):
          row.label (text="Put your OTP (from your application) into input box below")
        else:  
          row.label (text="2FA code has been sent (by " + self.type_of_2FA + ") to: ")
          row.label (text=self.contact_for_2FA)
        row.prop(self, "user_2FA_code_input", text="2FA code")

# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        

class Viribus_Farm_Addon_Preferences (AddonPreferences):
    bl_idname = __name__

    viribus_farm_login_name: bpy.props.StringProperty (name=get_translate_text ("Login name"), description="Use e-mail address you used for registration", default = "") 
    viribus_farm_login_pwd: bpy.props.StringProperty (name=get_translate_text ("Password"), description="Password you set during registration", default = "", subtype='PASSWORD')
    show_anim_img_selection: bpy.props.BoolProperty (name=get_translate_text ("Always ask if to render animation / all frames"), description="Always ask if to render animation / all frames when frame_start and frame_end is different", default = False)
    viribus_farm_upload_chunk_size: bpy.props.IntProperty (name=get_translate_text ("Upload chunk size (in MB)"), description="Files will be uploaded chunk by chunk", default = 25, min = 1, max = 250)
    viribus_farm_upload_retry_count: bpy.props.IntProperty (name=get_translate_text ("Number of retry if upload fail"), description="If connection fails retry will be done - this is max tries count.", default = 10, min = 1, max = 100)
    viribus_farm_upload_retry_delay: bpy.props.IntProperty (name=get_translate_text ("Number of seconds between tries"), description="Delay to retry in case of connection fail", default = 10, min = 1, max = 100)
    viribus_farm_use_zip: bpy.props.BoolProperty (name=get_translate_text ("Use compress (zip) of files for upload - need extra space on disk"), description="Files will be uploaded in compressed form", default = True)
    viribus_farm_create_log_file: bpy.props.BoolProperty (name=get_translate_text ("Create log file (in temp directory)"), description="Create detailed log file, in temp directory", default = True)
    
    def draw (self, context):
        layout = self.layout

        layout.label (text=get_translate_text ("Must be set before upload"))
        layout.prop (self, "viribus_farm_login_name")
        
        layout.label (text=get_translate_text ("Empty password -> you will be asked"))
        layout.label (text=get_translate_text ("Plain text password"))

        layout.prop(self, "viribus_farm_login_pwd")

        layout.prop(self, "viribus_farm_upload_chunk_size")

        layout.prop(self, "viribus_farm_upload_retry_count")
        layout.prop(self, "viribus_farm_upload_retry_delay")
        
        layout.prop(self, "show_anim_img_selection")
        layout.prop(self, "viribus_farm_use_zip")
        layout.prop(self, "viribus_farm_create_log_file")



class ViribusFarmUploadSettings (bpy.types.PropertyGroup):
    viribus_farm_project_name: bpy.props.StringProperty (name="Project name", description="Project name (optional)", default="", maxlen=50)
    viribus_farm_project_description: bpy.props.StringProperty (name="Project description", description="Project description (optional)", default="", maxlen=1000)
    viribus_farm_render: bpy.props.BoolProperty (name="Render", description="Start render after sending", default = True)
    viribus_farm_set_optimal: bpy.props.BoolProperty (name="Set optimal tile size", description="Set optimal tile size by Viribus Farm", default = True)
    viribus_farm_render_if_error: bpy.props.BoolProperty (name="Render if error", description="Render if error occured (e.g. Failed to add relation 'Bone Parent')", default = True)
    viribus_farm_priority: bpy.props.IntProperty (name = "Priority", description="Task pPriority. 1 - highest; 10 - lowest.", default = 1, min = 1, max = 10)
    viribus_farm_dynamic_priority: bpy.props.BoolProperty (name="Use dynamic priority", description="Adjust the priority of the task according to the circumstances", default = False)
    viribus_farm_cycles_device: bpy.props.EnumProperty (items=[("READ PREFERENCES", "Read preferences", "Use cycles render device according your settings (if possible)", 1), ("NONE", "CPU", "Use CPU as cycles render device", 2), ("CUDA", "CUDA", "Use CUDA as cycles render device", 3), ("OPTIX", "OptiX", "Use OptiX as cycles render device", 4)])

class upload_file_to_farm (bpy.types.Operator):
    bl_idname = "upload.fnc1"
    bl_label = "Upload files to ViribusFarm.com"
    
    user_pwd_input: bpy.props.StringProperty (subtype="PASSWORD")
    user_2FA_code: bpy.props.StringProperty (name="2FA code")
    output_type: bpy.props.EnumProperty (items=[(const_chosen_output_type_anim, "All frames / Animation", "Render all frames", 1), (const_chosen_output_type_img, "One image", "Render one picture", 2)])

    def __init__ (self):
        
        self.show_keyframes_warning = False
        self.keyframes_warning_text_1 = ""
        self.keyframes_warning_text_2 = ""

        self.need_2FA_code = False
        self.type_of_2FA = ''
        self.contact_for_2FA = ''
        
        self.show_missing_file_warning = False
        self.show_missing_file_text = ""

        self.are_files_the_same = False
        self.user_chose_output_type = False

    @classmethod
    def poll(cls, context):
        return True


          


    def get_error_file (self, p_upload_tag):
        return (os.path.join (tempfile.gettempdir(), 'viribus_farm_{}.err'.format(p_upload_tag)))

    def add_message_to_err_file (self, p_message, p_upload_tag):
        # with open(self.get_error_file (p_upload_tag), 'a') as file_to_add:
        file_to_add = open(self.get_error_file (p_upload_tag), 'a')
        file_to_add.write (p_message + "\n")
        file_to_add.close ()

    def get_size_of_err_file (self, p_upload_tag):
        error_file_name = self.get_error_file (p_upload_tag)
        if (os.path.exists (error_file_name)):
            return (os.path.getsize (error_file_name))
        else:
            return (0)

    def get_error_message_content (self, p_upload_tag):
        error_file_name = self.get_error_file (p_upload_tag)
        if (os.path.exists (error_file_name)):
            # with open(p_file_name, "rb") as f:
            f = open(p_file_name, "rb")
            err_content = f.read (const_err_file_content_size)
            f.close ()
            return (err_content)
        else:
            return ('Nic tam nie je')

    def remove_err_file_if_exist (self, p_upload_tag):
        error_file_name = self.get_error_file (p_upload_tag)
        if (os.path.exists (error_file_name)):
            os.remove (error_file_name)

    # --------------------------------------------------------------------------------------------------------------------------------

    def add_to_list_of_files (self,
                              p_target_list, 
                              p_absolute_file_name,
                              p_path_from_main_file,
                              p_file_type,
                              p_filepath,
                              p_library_path,
                              p_original_file_name,
                              p_file_source,
                              p_file_name_from_node,
                              p_file_number,
                              p_path_to_upload,
                              p_fiction_file,
                              p_users
                              ):
        if (p_target_list is not None):
            if (p_absolute_file_name not in p_target_list):
                file_hash = hashlib.md5()
                file_size = 0
                file_exists = 'N'
                if (os.path.exists (p_absolute_file_name)):
                    # with open(p_absolute_file_name, "rb") as f:
                    f = open(p_absolute_file_name, "rb")
                    for one_file_chunk in read_file_in_chunks (file_object=f, chunk_size=8192):
                    # while one_file_chunk := f.read(8192):
                        file_hash.update(one_file_chunk)
                    f.close ()
                    file_size = os.stat (p_absolute_file_name).st_size        
                    file_exists = 'Y'
                p_target_list[p_absolute_file_name] = {
                                                      'absolute_file_name': p_absolute_file_name, 
                                                      'file_size': file_size, 
                                                      'file_hash': file_hash.hexdigest(), 
                                                      'last_chunk_number': const_no_chunk_uploaded,
                                                      'path_from_main_file': p_path_from_main_file,
                                                      'file_type': p_file_type,
                                                      'filepath': p_filepath,
                                                      'library_path': p_library_path,
                                                      'original_file_name': p_original_file_name,
                                                      'file_source': p_file_source,
                                                      'file_name_from_node': p_file_name_from_node,
                                                      'file_number': p_file_number,
                                                      'path_to_upload': p_path_to_upload,
                                                      'fiction_file': p_fiction_file,
                                                      'file_exists': file_exists,
                                                      'users': p_users
                                                      }
            #else: # nemam tu hodnotu pre upload_tag
                #if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
                    #add_message_to_log_file (p_message="File is already on the list: {}".format (p_absolute_file_name), p_upload_tag=p_upload_tag)
                    
    # --------------------------------------------------------------------------------------------------------------------------------

    def add_library_file (self, p_file_list, p_library):
        lib_path_from_main_file = get_path_from_main_file (p_obj=p_library)
        lib_absolute_filepath = get_absolute_path (p_obj=p_library)
        self.add_to_list_of_files (p_target_list=p_file_list,
                              p_absolute_file_name=lib_absolute_filepath,
                              p_path_from_main_file=lib_path_from_main_file,
                              p_file_type='library',
                              p_filepath=bpy.data.filepath,
                              p_library_path=lib_path_from_main_file,
                              p_original_file_name=os.path.basename(bpy.data.filepath),
                              p_file_source='FILE',
                              p_file_name_from_node=' ',
                              p_file_number=-99,
                              p_path_to_upload=lib_absolute_filepath,
                              p_fiction_file='N',
                              p_users=p_library.users
                              )
        
        for one_sub_library in bpy.data.libraries:
            if (one_sub_library.parent == p_library and one_sub_library.packed_file is None):
                self.add_library_file (p_file_list=p_file_list, p_library=one_sub_library)
                
      
    # --------------------------------------------------------------------------------------------------------------------------------

    def add_images_from_materials (self, p_file_list, p_material_nodes):
        for one_node in p_material_nodes:
            if (hasattr (one_node, 'image')):
                if (one_node.image is not None):
                    one_image = one_node.image
                    if (one_image.type == 'IMAGE'):   # and one_image.packed_file):  # nechapem co som tymto chcel povedat
                        image_library_file_path = os.path.dirname (get_full_relative_path (one_node.image))
                        file_path_to_upload = get_absolute_path (one_node.image)
                        path_from_main_file = get_full_relative_path (one_node.image)

                        if (one_image.source == const_file_source_sequence):

                            path_to_search_file = os.path.dirname (file_path_to_upload)
                            
                            use_blender_method = True
                            if (use_blender_method == True):
                                # -----------------------------------------------------------------------------------------------------------------------
                                # implementovane podla: https://devtalk.blender.org/t/retrieve-image-sequence-filepaths-from-bpy-types-texturenode/21413
                                current_frame_number = 1 # one_node.image_user.frame_start
                                original_current_frame_number = one_node.image_user.frame_current
                                
                                
                                while (current_frame_number <= one_node.image_user.frame_duration):
                                    one_node.image_user.frame_current = current_frame_number
                                    # print ('one_node.image_user.frame_current: {}'.format (one_node.image_user.frame_current))
                                    # print (one_node.image.filepath)
                                    # print (one_node.image.filepath_from_user(image_user=one_node.image_user))
                                    
                                    # pouzit: one_node.image.filepath_from_user(image_user=one_node.image_user)
                                    file_name_without_path = os.path.basename (one_node.image.filepath_from_user(image_user=one_node.image_user))
                                    self.add_to_list_of_files (p_target_list=p_file_list, 
                                                                p_absolute_file_name=os.path.join (path_to_search_file, file_name_without_path),
                                                                p_path_from_main_file=path_from_main_file,
                                                                p_file_type='image',
                                                                p_filepath=one_image.filepath,
                                                                p_library_path=image_library_file_path,
                                                                p_original_file_name=file_name_without_path, # os.path.basename (file_name_to_upload),
                                                                p_file_source=one_image.source,
                                                                p_file_name_from_node=one_image.filepath,
                                                                p_file_number=current_frame_number, # get_number_from_file_name (p_file_name=file_name_to_upload),
                                                                p_path_to_upload=os.path.join (path_to_search_file, file_name_without_path),
                                                                p_fiction_file='N',
                                                                p_users=one_image.users
                                                                )                                    
                                    
                                    current_frame_number = current_frame_number + 1
                                #    dump (one_node.image_user)
                                # if (one_node.image is not None):
                                #    dump (one_node.image)
                                one_node.image_user.frame_current = original_current_frame_number
                                
                                self.add_to_list_of_files (p_target_list=p_file_list, 
                                                            p_absolute_file_name=file_path_to_upload,
                                                            p_path_from_main_file=path_from_main_file,
                                                            p_file_type='image',
                                                            p_filepath=one_image.filepath,
                                                            p_library_path=image_library_file_path,
                                                            p_original_file_name=os.path.basename (file_path_to_upload),
                                                            p_file_source=one_image.source,
                                                            p_file_name_from_node=one_image.filepath,
                                                            p_file_number=get_number_from_file_name (p_file_name=file_path_to_upload),
                                                            p_path_to_upload=file_path_to_upload,
                                                            p_fiction_file='Y',
                                                            p_users=one_image.users
                                                          )
                                
                                # -----------------------------------------------------------------------------------------------------------------------
                            else:  
                                # -----------------------------------------------------------------------------------------------------------------------
                                file_name_without_path = os.path.basename (file_path_to_upload)

                                current_frame_number = bpy.context.scene.frame_start
                                current_file_index = -99
                                while (current_frame_number <= bpy.context.scene.frame_end):
                                    if (one_node.image_user.use_cyclic == True):
                                        current_file_index = ((current_frame_number - one_node.image_user.frame_start) % one_node.image_user.frame_duration) + one_node.image_user.frame_offset + 1
                                    else:   # one_node.image_user.use_cyclic == True
                                        if (current_frame_number < one_node.image_user.frame_start):
                                            current_file_index = one_node.image_user.frame_offset
                                        if (current_frame_number >= one_node.image_user.frame_start and current_frame_number <= (one_node.image_user.frame_start + one_node.image_user.frame_duration - 1)):
                                            current_file_index = ((current_frame_number - one_node.image_user.frame_start) % one_node.image_user.frame_duration) + one_node.image_user.frame_offset + 1
                                        if (current_frame_number > (one_node.image_user.frame_start + one_node.image_user.frame_duration - 1)):
                                            current_file_index = one_node.image_user.frame_duration + one_node.image_user.frame_offset

                                    file_name_to_upload = get_file_name_to_upload (p_file_name=file_name_without_path, p_file_index=current_file_index)
                                    modified_tex_filepath = get_file_name_to_upload (p_file_name=one_image.filepath, p_file_index=current_file_index)
                                    
                                    self.add_to_list_of_files (p_target_list=p_file_list, 
                                                              p_absolute_file_name=os.path.join (path_to_search_file, file_name_to_upload),
                                                              p_path_from_main_file=path_from_main_file,
                                                              p_file_type='image',
                                                              p_filepath=one_image.filepath,
                                                              p_library_path=image_library_file_path,
                                                              p_original_file_name=os.path.basename (file_name_to_upload),
                                                              p_file_source=one_image.source,
                                                              p_file_name_from_node=one_image.filepath,
                                                              p_file_number=get_number_from_file_name (p_file_name=file_name_to_upload),
                                                              p_path_to_upload=file_name_to_upload,
                                                              p_fiction_file='N',
                                                              p_users=one_image.users
                                                              )
                                    
                                    current_frame_number = current_frame_number + bpy.context.scene.frame_step
                                    
                                self.add_to_list_of_files (p_target_list=p_file_list, 
                                                            p_absolute_file_name=file_path_to_upload,
                                                            p_path_from_main_file=path_from_main_file,
                                                            p_file_type='image',
                                                            p_filepath=one_image.filepath,
                                                            p_library_path=image_library_file_path,
                                                            p_original_file_name=os.path.basename (file_path_to_upload),
                                                            p_file_source=one_image.source,
                                                            p_file_name_from_node=one_image.filepath,
                                                            p_file_number=get_number_from_file_name (p_file_name=file_path_to_upload),
                                                            p_path_to_upload=file_path_to_upload,
                                                            p_fiction_file='Y',
                                                            p_users=one_image.users
                                                          )
                                # -----------------------------------------------------------------------------------------------------------------------
                            
                        if (one_image.source == const_file_source_tiled):
                            for one_tile in one_image.tiles:
                                self.add_to_list_of_files (p_target_list=p_file_list,
                                                           p_absolute_file_name=file_path_to_upload.replace ('<UDIM>', str (one_tile.number)),
                                                           p_path_from_main_file=path_from_main_file,
                                                           p_file_type='image',
                                                           p_filepath=one_image.filepath,
                                                           p_library_path=image_library_file_path,
                                                           p_original_file_name=os.path.basename (file_path_to_upload.replace ('<UDIM>', str (one_tile.number))),
                                                           p_file_source=one_image.source,
                                                           p_file_name_from_node=one_image.filepath,
                                                           p_file_number=one_tile.number,
                                                           p_path_to_upload=file_path_to_upload.replace ('<UDIM>', str (one_tile.number)),
                                                           p_fiction_file='N',
                                                           p_users=one_image.users
                                                          )

                        if (one_image.source == const_file_source_file):
                            self.add_to_list_of_files (p_target_list=p_file_list, 
                                                        p_absolute_file_name=file_path_to_upload,
                                                        p_path_from_main_file=path_from_main_file,
                                                        p_file_type='image',
                                                        p_filepath=one_image.filepath,
                                                        p_library_path=image_library_file_path,
                                                        p_original_file_name=os.path.basename (file_path_to_upload),
                                                        p_file_source=one_image.source,
                                                        p_file_name_from_node=one_image.filepath,
                                                        p_file_number=-99,
                                                        p_path_to_upload=file_path_to_upload,
                                                        p_fiction_file='N',
                                                        p_users=one_image.users
                                                      )
                        
            if (hasattr (one_node, 'node_tree') and one_node.node_tree is not None):
                self.add_images_from_materials (p_file_list=p_file_list, p_material_nodes=one_node.node_tree.nodes)
        
    # --------------------------------------------------------------------------------------------------------------------------------

    def add_missing_images_to_list (self, p_file_list):
        for one_image in bpy.data.images:
            if one_image.users >= 1:

                absolute_path = get_absolute_path (one_image)
                if (absolute_path is None or absolute_path == ''):
                    pass
                else:
                    if (one_image.source == const_file_source_sequence):
                        if (absolute_path not in list(p_file_list.keys())):
                          
                            show_message (p_message="add_missing_images_to_list - spracovavam subor: {}; {};".format (absolute_path, one_image.filepath), p_upload_tag='nemam_upload_tag')
                          
                            absolute_path_to_file = get_absolute_path (p_obj=one_image)
                            mask_to_find_files = get_mask_for_files (p_file_name=absolute_path_to_file, p_string_to_add='*')
                            reg_exp_mask = get_mask_for_files (p_file_name=absolute_path_to_file, p_string_to_add='[0-9]+')
                            # path_to_search_file = os.path.dirname (absolute_path_to_file)
                            
                            show_message (p_message="reg_exp_mask: {}".format (reg_exp_mask), p_upload_tag='nemam_upload_tag')
                            show_message (p_message="mask_to_find_files: {}".format (mask_to_find_files), p_upload_tag='nemam_upload_tag')
                            
                            file_name_pattern = re.compile (reg_exp_mask.replace('\\', '\\\\'))
                            for one_file in glob.glob (mask_to_find_files):
                                if (file_name_pattern.match (one_file)):
                                    self.add_to_list_of_files (p_target_list=p_file_list, 
                                                          p_absolute_file_name=absolute_path,
                                                          p_path_from_main_file=get_path_from_main_file (p_obj=one_image),
                                                          p_file_type='image',
                                                          p_filepath=one_image.filepath,
                                                          p_library_path=os.path.dirname (get_full_relative_path (one_image)),
                                                          p_original_file_name=os.path.basename(one_image.filepath),
                                                          p_file_source=one_image.source,
                                                          p_file_name_from_node=os.path.basename(one_image.filepath),
                                                          p_file_number=-1,
                                                          p_path_to_upload=absolute_path,
                                                          p_fiction_file='N',
                                                          p_users=one_image.users
                                                          )
                                    
                    if (one_image.source == const_file_source_tiled):
                        for one_tile in one_image.tiles:
                            self.add_to_list_of_files (p_target_list=p_file_list,
                                                        p_absolute_file_name=absolute_path.replace ('<UDIM>', str (one_tile.number)),
                                                        p_path_from_main_file=get_path_from_main_file (p_obj=one_image),
                                                        p_file_type='image',
                                                        p_filepath=one_image.filepath,
                                                        p_library_path=os.path.dirname (get_full_relative_path (one_image)),
                                                        p_original_file_name=os.path.basename(one_image.filepath).replace ('<UDIM>', str (one_tile.number)),
                                                        p_file_source=one_image.source,
                                                        p_file_name_from_node=one_image.filepath,
                                                        p_file_number=one_tile.number,
                                                        p_path_to_upload=absolute_path.replace ('<UDIM>', str (one_tile.number)),
                                                        p_fiction_file='N',
                                                        p_users=one_image.users
                                                      )

                    if (one_image.source == const_file_source_file):
                        self.add_to_list_of_files (p_target_list=p_file_list, 
                                                    p_absolute_file_name=absolute_path,
                                                    p_path_from_main_file=get_path_from_main_file (p_obj=one_image),
                                                    p_file_type='image',
                                                    p_filepath=one_image.filepath,
                                                    p_library_path=os.path.dirname (get_full_relative_path (one_image)),
                                                    p_original_file_name=os.path.basename(one_image.filepath),
                                                    p_file_source=one_image.source,
                                                    p_file_name_from_node=os.path.basename(one_image.filepath),
                                                    p_file_number=-1,
                                                    p_path_to_upload=absolute_path,
                                                    p_fiction_file='N',
                                                    p_users=one_image.users
                                                  )
                        

    # --------------------------------------------------------------------------------------------------------------------------------

    def write_file_list_into_log_file (self, p_file_list, p_upload_tag):
        if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):
            add_message_to_log_file (p_message="List of files:", p_upload_tag=p_upload_tag)
            for one_file in p_file_list:
                add_message_to_log_file (p_message="{}; {}; {}; {};".format (p_file_list[one_file]['absolute_file_name'], p_file_list[one_file]['file_type'], p_file_list[one_file]['file_exists'], p_file_list[one_file]['users']), p_upload_tag=p_upload_tag)

    # --------------------------------------------------------------------------------------------------------------------------------

    def create_file_list (self, p_file_list, p_upload_tag, p_log_records):
        self.add_to_list_of_files (p_target_list=p_file_list, 
                              p_absolute_file_name=bpy.data.filepath,
                              p_path_from_main_file=' ',
                              p_file_type='main_file',
                              p_filepath=bpy.data.filepath,
                              p_library_path=' ',
                              p_original_file_name=os.path.basename(bpy.data.filepath),
                              p_file_source='FILE',
                              p_file_name_from_node=' ',
                              p_file_number=-1,
                              p_path_to_upload=bpy.data.filepath,
                              p_fiction_file='N',
                              p_users=1
                              )
        
        for one_library in bpy.data.libraries:
            if (one_library.parent is None and one_library.packed_file is None):
                self.add_library_file (p_file_list=p_file_list, p_library=one_library)
                
        for one_material in bpy.data.materials:
            if (one_material.node_tree is not None):
                self.add_images_from_materials (p_file_list=p_file_list, p_material_nodes=one_material.node_tree.nodes)

        self.add_missing_images_to_list (p_file_list=p_file_list)

        self.write_file_list_into_log_file (p_file_list=p_file_list, p_upload_tag=p_upload_tag)

    # --------------------------------------------------------------------------------------------------------------------------------

    def get_missing_files (self, p_file_list, p_upload_tag):
        return_value = 'OK'
        for one_file in p_file_list:
            if (p_file_list[one_file]['file_exists'] != 'Y' and p_file_list[one_file]['fiction_file'] != 'Y' and p_file_list[one_file]['users'] > 0):
                if (return_value == 'OK'):
                    return_value = os.path.basename (p_file_list[one_file]['absolute_file_name'])
                else:  
                    return_value = "{}; {}".format (return_value, os.path.basename (p_file_list[one_file]['absolute_file_name']))
        return (return_value)

    # --------------------------------------------------------------------------------------------------------------------------------
    
    def get_count_of_missing_files (self, p_file_list, p_upload_tag):
        return_value = 0
        for one_file in p_file_list:
            if (p_file_list[one_file]['file_exists'] != 'Y' and p_file_list[one_file]['fiction_file'] != 'Y' and p_file_list[one_file]['users'] > 0):
                return_value += 1
        return (return_value)

    # --------------------------------------------------------------------------------------------------------------------------------
    

    def compare_file_lists (self, first_list, second_list):
        return_value = True
      
        if (len(first_list) != len (second_list)):
            return (False)
          
        for first_index in first_list.keys():
            if (first_index in second_list):
                if (first_list[first_index]['file_size'] != second_list[first_index]['file_size'] or
                    first_list[first_index]['file_hash'] != second_list[first_index]['file_hash']):
                    return_value = False
            else:
                return_value = False
                
        return (return_value)

    # --------------------------------------------------------------------------------------------------------------------------------
    
    def has_been_all_files_uploaded (self, p_file_list):
        return_value = 'yes'
        for file_index in p_file_list.keys():
            if (p_file_list[file_index]['last_chunk_number'] != const_chunks_complete):
                return_value = 'no'
        return (return_value)        

    # --------------------------------------------------------------------------------------------------------------------------------
    
    def invoke(self, context, event):
        error_occured = 0
        error_message = ""
      
        user_login_name = context.preferences.addons[__name__].preferences.viribus_farm_login_name

        viribus_session = requests.Session()
        viribus_session.mount ('https://', HTTPAdapter (max_retries=Retry (connect=context.preferences.addons[__name__].preferences.viribus_farm_upload_retry_count, backoff_factor=context.preferences.addons[__name__].preferences.viribus_farm_upload_retry_delay)))

        post_response = viribus_session.post (const_viribus_farm_ping_url, verify=const_verify_ssl)
        # show_message (p_message='post_response.text: {}'.format (post_response.text), p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
        # if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
        #    add_message_to_log_file (p_message='ViribusFarm ping - post_response.text: {}'.format (post_response.text), p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
        post_response_array = json.loads (post_response.text)
        post_response.close ()
        if (post_response_array ['return_value'] == const_web_ping_ok):
            post_response = viribus_session.post (const_viribus_check_version_supported, data = { 'addon_version_major': bl_info['version'][0], 'addon_version_minor': bl_info['version'][1], 'addon_version_patch': bl_info['version'][2] }, verify=const_verify_ssl)
            # show_message (p_message='post_response.text: {}'.format (post_response.text), p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
            # if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
            #    add_message_to_log_file (p_message='Check version support - post_response.text: {}'.format (post_response.text), p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
            post_response_array = json.loads (post_response.text)
            if (post_response_array ['return_value'] != const_addon_version_supported):
                error_occured = 1
                error_message = add_text (error_message, get_translate_text ("This version of add-on is no longer supported ({}).".format(post_response_array ['return_value'])))
            post_response.close ()
                
            post_response = viribus_session.post (const_viribus_check_avail_url, verify=const_verify_ssl)
            # show_message (p_message='post_response.text: {}'.format (post_response.text), p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
            # if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
            #    add_message_to_log_file (p_message='Service available - post_response.text: {}'.format (post_response.text), p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
            post_response_array = json.loads (post_response.text)
            if (post_response_array ['return_value'] != const_service_available):
                error_occured = 1
                error_message = add_text (error_message, get_translate_text ("Render service is not available at the moment."))
            post_response.close ()
            
        else:  
            error_occured = 1
            error_message = add_text (error_message, get_translate_text ("Web is not accessible. Check your internet connection."))
            
                
        if (bpy.app.version > supported_up_to_version):
            error_occured = 1
            error_message = add_text (error_message, get_translate_text ("Too new version: {}".format(bpy.app.version)))
            
        if (user_login_name == "" or user_login_name is None):
            error_occured = 1
            error_message = add_text (error_message, get_translate_text ("Fill username (email) in add-on configuration for Viribus Farm"))
      
        if (bpy.data.is_saved != True or bpy.data.is_dirty == True):
            error_occured = 1
            error_message = add_text (error_message, get_translate_text ("Save file first, please."))
        else:
            if (not os.path.exists(bpy.data.filepath)):
                error_occured = 1
                error_message = add_text (error_message, "Main blend file does not exist.")
                
        if (bpy.context.scene.use_nodes == True):
            for node in bpy.context.scene.node_tree.nodes:
                if (node.bl_label == 'Render Layers'):
                    if (not hasattr (node, 'layer') or node.layer == None or node.layer == ""):
                        error_occured = 1
                        error_message =  add_text (error_message, get_translate_text ("Attribute `layer name` is not defined in compositor node `Render Layers`."))


        number_of_keyframes = 0

        for action_index in range(len(bpy.data.actions)):
            number_of_keyframes = number_of_keyframes + len (bpy.data.actions[action_index].fcurves)

        self.show_keyframes_warning = False
        if (number_of_keyframes == 0 and (bpy.context.scene.frame_end - bpy.context.scene.frame_start + 1) > 1):
            self.show_keyframes_warning = True
            self.keyframes_warning_text_1 = "There is no key-frames / actions."
            self.keyframes_warning_text_2 = "Are you sure you want to have " + str (bpy.context.scene.frame_end - bpy.context.scene.frame_start + 1) + " frames in animation?"

# pridat kontrolu: prejst vsetky objekty v projekte, zistit ci existuje pre objekt rigid body, ak ano, overit ci pre objekt existuje key-frame. Ak neexistuje, vypisat upozornenie
# prejst vsetky objekty v projekte:
# for l in bpy.data:
#     if is callable(l):
#         continue
#     for item in l:
#          print(item)
#
# overenie ci je objekt typu mesh: meshes = set(o.data for o in scene.objects if o.type == 'MESH')
#
# if bpy.context.object.rigid_body is None:
#     print("no rigid_body")
# else:
#     print("has rigid_body")

        if (hasattr (bpy.types.WindowManager, 'viribus_farm_test')):
          bpy.types.WindowManager.viribus_farm_test = bpy.types.WindowManager.viribus_farm_test + 1
        else:  
          bpy.types.WindowManager.viribus_farm_test = 0
        
        bpy.types.WindowManager.continue_uploading = True
        if (error_occured == 0):
            if (hasattr (bpy.types.WindowManager, 'viribus_farm_last_upload_status') and bpy.types.WindowManager.viribus_farm_last_upload_status != "n/a"):
            
                new_file_list = {}
                log_records = {}
                self.create_file_list (p_file_list=new_file_list, p_upload_tag="File_list", p_log_records=log_records)
                self.are_files_the_same = self.compare_file_lists (bpy.types.WindowManager.viribus_farm_uploading_file_list, new_file_list)
            
                if (self.are_files_the_same == True):
                    bpy.types.WindowManager.viribus_farm_all_files_uploaded = self.has_been_all_files_uploaded (bpy.types.WindowManager.viribus_farm_uploading_file_list)
                    
                    if (bpy.types.WindowManager.viribus_farm_last_upload_status == "success"):
                        bpy.types.WindowManager.viribus_farm_upload_tag = generate_new_upload_tag()
                        reset_last_chunk_numbers (bpy.types.WindowManager.viribus_farm_uploading_file_list)
                        bpy.types.WindowManager.continue_uploading = False
                        
                else:  
                    bpy.types.WindowManager.viribus_farm_uploading_file_list = {}
                    bpy.types.WindowManager.viribus_farm_uploading_file_list = new_file_list.copy()
                    bpy.types.WindowManager.viribus_farm_upload_tag = generate_new_upload_tag()
                    bpy.types.WindowManager.continue_uploading = False
            else:
                bpy.types.WindowManager.viribus_farm_last_upload_status = "n/a"
                self.are_files_the_same = None
                bpy.types.WindowManager.viribus_farm_uploading_file_list = {}
                bpy.types.WindowManager.viribus_farm_upload_tag = generate_new_upload_tag()
                self.create_file_list (p_file_list=bpy.types.WindowManager.viribus_farm_uploading_file_list, p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag, p_log_records=None)
                bpy.types.WindowManager.continue_uploading = False
                
            self.show_missing_file_warning = False
            # self.show_missing_file_text = self.get_missing_files (p_file_list=bpy.types.WindowManager.viribus_farm_uploading_file_list, p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
            # if (self.show_missing_file_text != "OK"):
            #     self.show_missing_file_text = "Some file(s) missing:\n{}".format (self.show_missing_file_text)
            #     self.show_missing_file_warning = True
            
            missing_files_count = self.get_count_of_missing_files (p_file_list=bpy.types.WindowManager.viribus_farm_uploading_file_list, p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
            if (missing_files_count > 0):
                self.show_missing_file_text = "{} file(s) missing.".format (missing_files_count)
                self.show_missing_file_warning = True
                
            return context.window_manager.invoke_props_dialog(self)
        else:
            self.report ({'ERROR'}, error_message)
            return {'FINISHED'}
        
    # --------------------------------------------------------------------------------------------------------------------------------
    
    def draw(self, context):
        
        row = self.layout
        
        if (self.show_missing_file_warning == True):
             wrapper = textwrap.TextWrapper(width=int(context.region.width / 7)) # 7 pix on 1 character
             text_lines = wrapper.wrap(text=self.show_missing_file_text)
             for text_line in text_lines:
                 row.label(text=text_line)
             
             row.label (text=("Missing file(s) may cause unexpected results."))
        
        self.user_chose_output_type = False
        if (
            (
             context.preferences.addons[__name__].preferences.show_anim_img_selection == True or
             self.show_keyframes_warning == True
            ) and
            bpy.context.scene.frame_start != bpy.context.scene.frame_end
           ):
            if (self.show_keyframes_warning == True):
                row.label (text=self.keyframes_warning_text_1)
                row.label (text=self.keyframes_warning_text_2)
            row.label(text="What type of output do you want?")
            row.prop(self, "output_type", text="Render")
            self.user_chose_output_type = True

        if (bpy.types.WindowManager.viribus_farm_last_upload_status == "success" and self.are_files_the_same == True):
            row.label (text="Those files has already been successfully uploaded.")
            row.label (text="Do you really want to upload again?")
            
        user_password = ""
        user_password = context.preferences.addons[__name__].preferences.viribus_farm_login_pwd
        if (user_password == "" or user_password is None):
            row.prop(self, "user_pwd_input", text="Password")
        
    # --------------------------------------------------------------------------------------------------------------------------------
    
    def execute(self, context):
        
        viribus_session = requests.Session()
        viribus_session.mount ('https://', HTTPAdapter (max_retries=Retry (connect=bpy.context.preferences.addons[__name__].preferences.viribus_farm_upload_retry_count, backoff_factor=bpy.context.preferences.addons[__name__].preferences.viribus_farm_upload_retry_delay)))
        bpy.types.WindowManager.viribus_farm_last_upload_status = 'uploading'

        user_login_name = bpy.context.preferences.addons[__name__].preferences.viribus_farm_login_name
        user_password = ""
        user_password = context.preferences.addons[__name__].preferences.viribus_farm_login_pwd
        if (user_password == "" or user_password is None):
            user_password = self.user_pwd_input
        
        # overenie potreby 2FA
        post_req_params = { 
                            'username': user_login_name, 
                            'password': user_password, 
                            'operation_type': 'check_2FA_need',
                            'upload_tag': bpy.types.WindowManager.viribus_farm_upload_tag
                          }
        number_of_params = len (post_req_params)
        post_req_params[const_param_name_params_hash] = get_hash_for_dict (post_req_params)
        post_req_params[const_param_name_number_of_params] = number_of_params
        
        post_response = viribus_session.post (const_viribus_farm_upload_url, data = post_req_params, verify=const_verify_ssl)
        self.need_2FA_code = False;
        # show_message (p_message="post_response.text: {}".format (post_response.text), p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
        if (bpy.context.preferences.addons[__name__].preferences.viribus_farm_create_log_file == True):    
            add_message_to_log_file (p_message="check 2FA needed - post_response.text: {}".format (post_response.text), p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag)
        post_response_array = json.loads (post_response.text)
        
        chosen_output_type = const_chosen_output_type_not_chsn
        if (self.user_chose_output_type == True):
            chosen_output_type = self.output_type
        
        if (post_response_array ['return_value'] == const_2fa_need_yes):
            self.need_2FA_code = True;
            bpy.types.WindowManager.viribusfarm_type_of_2FA = post_response_array ['type of 2FA']
            bpy.types.WindowManager.viribusfarm_contact_for_2FA = post_response_array ['contact for 2FA']
            bpy.types.WindowManager.viribusfarm_password = user_password
            bpy.types.WindowManager.viribusfarm_output_type = chosen_output_type
                
            bpy.ops.object.dialog_vf_sec_lvl_auth('INVOKE_DEFAULT')
            
        else:
            uploading_status = upload_files (p_file_list=bpy.types.WindowManager.viribus_farm_uploading_file_list, p_upload_tag=bpy.types.WindowManager.viribus_farm_upload_tag, p_user_name=user_login_name, p_password=user_password, p_2FA_code='', p_chosen_output_type=chosen_output_type, p_operator=self)
            # print ('uploading_status: {}'.format (uploading_status))

          
        return {'FINISHED'}


    
# ------------------------------------------------------------------------------------------------------------------------------------    

class ViribusFarmPanel (bpy.types.Panel):
    bl_label = "Viribus Farm Upload"
    bl_idname = "VIRIBUS_FARM_PT_PANEL"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_category = "Category"
    bl_context = "render"

    def draw(self, context):
        layout = self.layout
        # layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_login_name", text="Login name / e-mail address")
        # layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_login_pwd", text="Password")
        layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_project_name", text="Project name")
        layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_project_description", text="Project description")
        layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_render", text="Start render on Viribus Farm")
        layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_set_optimal", text="Set optimal tile sizes by Viribus Farm")
        layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_render_if_error", text="Render if error occured (e.g. Added parent bone failed)")
        layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_priority", text="Priority")
        layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_dynamic_priority", text="Dynamic priority")
        layout.prop (context.scene.ViribusFarmProperties, "viribus_farm_cycles_device", text="Cycles Render Device")
        layout.operator (upload_file_to_farm.bl_idname)

classes_to_process = (ViribusFarmUploadSettings, dialog_2FA_code_class, upload_file_to_farm, ViribusFarmPanel, Viribus_Farm_Addon_Preferences)


def register():
    for cls in classes_to_process:
        bpy.utils.register_class(cls)

    bpy.types.Scene.ViribusFarmProperties = bpy.props.PointerProperty (type=ViribusFarmUploadSettings)


def unregister():
    for cls in classes_to_process:
        bpy.utils.unregister_class(cls)

    del bpy.types.Scene.ViribusFarmProperties

if __name__ == "__main__":
    register()
