#
# 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": (1, 00, 0),
    "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/u18.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, 5, 88)
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_2fa_need_no = 'no'

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_file_source_movie = 'MOVIE'
const_file_source_generated = 'GENERATED'


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

# Viribus_translations = {
#                          ('sk_SK', '2FA code'): '2FA kód',
#                          ('sk_SK', 'E-mail address'): 'E-mailová adresa',
#                          ('sk_SK', 'Project name'): 'Názov projektu'
#                        }

translations_tuple = (
    (("*", ""),
     ((), ()),
     ("sk_SK", "Project-Id-Version: Viribus-upload-addon 0.99.9 (r0)\nReport-Msgid-Bugs-To: contact@viribusfarm.com\nPOT-Creation-Date: 18.8.2025 9:41:00\nPO-Revision-Date: 18.8.2025 9:41:00\nLast-Translator: jan@viribusfarm.com\nLanguage: __POT__\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n",
               (False,
                ("Copyright (C) 2025 Quin.Online, s.r.o."))),
    ),
               
    (("*", "E-mail address"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_login_name",),
      ()),
     ("sk_SK", "E-mailová adresa",
               (False, ())),
    ),
     
    (("*", "Must be set before uploading"),
     (("*",),
      ()),
     ("sk_SK", "Potrebné nastaviť pred nahrávaním súborov",
               (False, ())),
    ),

    (("*", "Use e-mail address you used for registration"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_login_name",),
      ()),
     ("sk_SK", "Zadajte e-mailovú adresu, ktorú ste použili v procese registrácie",
               (False, ())),
    ),
     
    (("*", "If you leave password field empty, password will be asked before uploading."),
     (("*",),
      ()),
     ("sk_SK", "Ak heslo nezadáte sem, budete mať možnosť ho zadať pred nahrávaním súborov.",
               (False, ())),
    ),

    (("*", "If you put password here, it will be stored as PLAIN TEXT in file on your hard drive"),
     (("*",),
      ()),
     ("sk_SK", "Ak heslo zadáte sem, bude uložené, v čitateľnej podobe (nezašifrované), v súbore na disku",
               (False, ())),
    ),

    (("*", "and you do not need to put it before every upload."),
     (("*",),
      ()),
     ("sk_SK", "a nebudete ho musieť zadávať pred nahrávaním súborov.",
               (False, ())),
    ),

    (("*", "Password"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_login_pwd",),
      ()),
     ("sk_SK", "Heslo",
               (False, ())),
    ),
    (("*", "Password you set during registration"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_login_pwd",),
      ()),
     ("sk_SK", "Heslo, ktoré ste si zvolili v procese registrácie",
               (False, ())),
    ),


     
    (("*", "Always ask if to render animation / all frames"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.show_anim_img_selection",),
      ()),
     ("sk_SK", "Vždy sa opýtať či renderovať animáciu / všetky snímky",
               (False, ())),
    ),
    (("*", "Always ask if to render animation / all frames when there are more than one frame"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.show_anim_img_selection",),
      ()),
     ("sk_SK", "Vždy sa opýtať či renderovať animáciu / všetky snímky, keď je snímkov viac ako jeden",
               (False, ())),
    ),

    (("*", "Upload chunk size (in MB)"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_upload_chunk_size",),
      ()),
     ("sk_SK", "Veľkosť časti súboru, ktorá je prenášaná samostatne (v MB)",
               (False, ())),
    ),
    (("*", "Files will be uploaded chunk by chunk"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_upload_chunk_size",),
      ()),
     ("sk_SK", "Súbory budú prenášané po častiach, aby sa minimalizovali problémy. Tu môžete nastaviť veľkosť jednotlivých častí.",
               (False, ())),
    ),

    (("*", "Number of retry if upload fail"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_upload_retry_count",),
      ()),
     ("sk_SK", "Počet opakovaní ak prenos súboru zlyhá",
               (False, ())),
    ),
    (("*", "If connection fails retry will be done - this is max tries count."),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_upload_retry_count",),
      ()),
     ("sk_SK", "Ak prenos súboru zlyhá, add-on sa pokúsi prenos opakovať. Tu môžete nastaviť maximálny počet opakovaní (pokusov).",
               (False, ())),
    ),

    (("*", "Number of seconds between tries"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_upload_retry_delay",),
      ()),
     ("sk_SK", "Počet sekúnd medzi opakovaniami",
               (False, ())),
    ),
    (("*", "Delay to retry in case of connection fail"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_upload_retry_delay",),
      ()),
     ("sk_SK", "Počet sekúnd kým sa add-on opätovne pokúsi preniesť súbor.",
               (False, ())),
    ),

    (("*", "Use compress (zip) of files for upload - need extra space on disk (during upload)"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_use_zip",),
      ()),
     ("sk_SK", "Použiť kompresiu pri prenose - vyžaduje ďalší priestor na disku (počas prenosu)",
               (False, ())),
    ),
    (("*", "Files will be uploaded in compressed form"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_use_zip",),
      ()),
     ("sk_SK", "Súbory budú prenášané komprimované.",
               (False, ())),
    ),

    (("*", "Create log file (in temp directory)"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_create_log_file",),
      ()),
     ("sk_SK", "Vytvárať súbor s prehľadom aktivít (log súbor)",
               (False, ())),
    ),
    (("*", "Create detailed log file, in temp directory"),
     (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_create_log_file",),
      ()),
     ("sk_SK", "Vytvárať súbor s prehľadom aktivít (log súbor) počas nahrávania (v adresári temp).",
               (False, ())),
    ),

    (("*", "Project name"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_project_name",),
      ()),
     ("sk_SK", "Názov projektu",
               (False, ())),
    ),
    (("*", "Project name (optional)"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_project_name",),
      ()),
     ("sk_SK", "Názov projektu (voliteľná, nepovinná hodnota).",
               (False, ())),
    ),

    (("*", "Project description"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_project_name",),
      ()),
     ("sk_SK", "Opis projektu",
               (False, ())),
    ),
    (("*", "Project description (optional)"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_project_name",),
      ()),
     ("sk_SK", "Opis projektu (voliteľná, nepovinná hodnota).",
               (False, ())),
    ),

    (("*", "Start render on Viribus Farm"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_render",),
      ()),
     ("sk_SK", "Odoslať priamo na renderovací proces",
               (False, ())),
    ),
    (("*", "Start render after sending"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_render",),
      ()),
     ("sk_SK", "Odoslať priamo na renderovací proces.",
               (False, ())),
    ),

    (("*", "Set optimal tile sizes by Viribus Farm"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_set_optimal",),
      ()),
     ("sk_SK", "Nastaviť optimálnu veľkosť dlaždíc (farmou)",
               (False, ())),
    ),
    (("*", "Set optimal tile size by Viribus Farm"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_set_optimal",),
      ()),
     ("sk_SK", "Nastaviť optimálnu veľkosť dlaždíc (farmou).",
               (False, ())),
    ),

    (("*", "Render if error"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_render_if_error",),
      ()),
     ("sk_SK", "Vykonať renderovanie v prípade výskytu chyby",
               (False, ())),
    ),
    (("*", "Render if error occured (e.g. Failed to add relation 'Bone Parent')"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_render_if_error",),
      ()),
     ("sk_SK", "Vykonať renderovanie v prípade výskytu chyby (napr. Failed to add relation 'Bone Parent').",
               (False, ())),
    ),
    (("*", "Render if error occured (e.g. Added parent bone failed)"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_render_if_error",),
      ()),
     ("sk_SK", "Vykonať renderovanie v prípade výskytu chyby (napr. Added parent bone failed).",
               (False, ())),
    ),

    (("*", "Priority"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_priority",),
      ()),
     ("sk_SK", "Priorita úlohy",
               (False, ())),
    ),
    (("*", "Task priority. 1 - highest; 10 - lowest."),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_priority",),
      ()),
     ("sk_SK", "Priorita úlohy. 1 - najvyššia; 10 - najnižšia.",
               (False, ())),
    ),

    (("*", "Use dynamic priority"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_dynamic_priority",),
      ()),
     ("sk_SK", "Nastaviť prioritu dynamicky",
               (False, ())),
    ),
    (("*", "Adjust the priority of the task according to the circumstances"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_dynamic_priority",),
      ()),
     ("sk_SK", "Prispôsobiť prioritu okolnostiam - vykonať renderovací proces čo najrýchlejšie a najlacnejšie.",
               (False, ())),
    ),
    (("*", "Dynamic priority"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_dynamic_priority",),
      ()),
     ("sk_SK", "Dynamická priorita",
               (False, ())),
    ),

    (("*", "Read preferences"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_cycles_device",),
      ()),
     ("sk_SK", "Načítať z nastavení (predvolieb)",
               (False, ())),
    ),
    (("*", "Use cycles render device according your settings (if possible)"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_cycles_device",),
      ()),
     ("sk_SK", "Použiť výpočtové zariadenie podľa nastavenia vášho Blendera (ak je to možné).",
               (False, ())),
    ),
    (("*", "Use CPU as cycles render device"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_cycles_device",),
      ()),
     ("sk_SK", "Použiť CPU",
               (False, ())),
    ),
    (("*", "Use CUDA as cycles render device"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_cycles_device",),
      ()),
     ("sk_SK", "Použiť technológiu CUDA",
               (False, ())),
    ),
    (("*", "Use OptiX as cycles render device"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_cycles_device",),
      ()),
     ("sk_SK", "Použiť OptiX",
               (False, ())),
    ),
    (("*", "Cycles Render Device"),
     (("bpy.types.ViribusFarmUploadSettings.viribus_farm_cycles_device",),
      ()),
     ("sk_SK", "Renderovacie zariadenie pre Cycles",
               (False, ())),
    ),
     
    (("*", "Upload files to ViribusFarm.com"),
     (("bpy.types.VIRIBUSADDON_OT_upload",),
      ()),
     ("sk_SK", "Nahrať súbory na ViribusFarm.com",
               (False, ())),
    ),

)

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

def get_translate_text (p_text_code):
    return (p_text_code)

#     viribus_translations = {
#                             "en_US": {
#                                       "File has been uploaded successfully": "File has been uploaded successfully", 
#                                       "Login failed": "Login failed, check your e-mail address 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",
#                                       "E-mail address": "E-mail address",
#                                       "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 1": "If you put password here, it will be stored as PLAIN TEXT in file on your hard drive",
#                                       "Plain text password 2": "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 1": "Ak zadáte heslo sem, nemusíte ho zadávať pred každým nahrávaním (uploadom) súboru,",
#                                       "Plain text password 2":  "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 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,
                                    'blender_lang_code': bpy.context.preferences.view.language
                                    }
                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=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
                        operation_status = post_response_array ['translated_text']
                    else:  
                        if (post_response_array ['return_value'] == const_response_uploading_deleted):
                            show_message (p_message=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
                            operation_status = post_response_array ['translated_text']
                        else:
                            show_message (p_message="Error occured: {}".format (post_response_array ['translated_text']), 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,
                                'blender_lang_code': bpy.context.preferences.view.language
                                }
            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=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
                    operation_status = post_response_array ['translated_text']
                else:  
                    if (post_response_array ['return_value'] == const_response_uploading_deleted):
                        show_message (p_message=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
                        operation_status = post_response_array ['translated_text']
                    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'],
                        'blender_lang_code': bpy.context.preferences.view.language
                        }
    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=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
            operation_status = post_response_array ['translated_text']
        else:  
            if (post_response_array ['return_value'] == const_response_uploading_deleted):
                show_message (p_message=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
                operation_status = post_response_array ['translated_text']
            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' and p_file_list[one_file]['is_packed'] != '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,
                            'blender_lang_code': bpy.context.preferences.view.language
                            }
        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,
                            'blender_lang_code': bpy.context.preferences.view.language
                            }
        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 = bpy.app.translations.pgettext ('Error during resummig upload')
            # show_message_if_known (p_message=post_response_array ['return_value'], p_operator=p_operator)
            p_operator.report ({'ERROR'}, post_response_array ['return_value'])
            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)
    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()),
                            'blender_lang_code': bpy.context.preferences.view.language
                            }
        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 = post_response_array ['translated_text'] # 'Error during starting upload'
            # show_message_if_known (p_message=post_response_array ['return_value'], p_operator=p_operator)
            p_operator.report ({'ERROR'}, post_response_array ['translated_text'])
            # message_to_show = "Error during uploading, exiting; return value (from upload module): |{}|".format (post_response_array ['return_value'])
            show_message (p_message=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
        
    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'):
                    if (p_file_list[one_file_to_send]['is_packed'] != '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
                    else:
                        print ("File {} is packed, not need to send it".format(one_file_to_send))
                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)
                            'blender_lang_code': bpy.context.preferences.view.language
                            }
        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'}, post_response_array ['translated_text'])
            show_message (p_message='INFO: {}'.format (post_response_array ['translated_text']), 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=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
                operation_status = post_response_array ['translated_text']
                bpy.types.WindowManager.viribus_farm_last_upload_status = "cancelled"
            else:  
                if (post_response_array ['return_value'] == const_response_uploading_deleted):
                    show_message (p_message=post_response_array ['translated_text'], p_upload_tag=p_upload_tag)
                    operation_status = post_response_array ['translated_text']
                    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'}, operation_status)
    
    return (operation_status)

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

def replace_tag_by_number (p_file_name, p_number):
    return (p_file_name.replace('<UDIM>', str (p_number).replace('<UVTILE>', str (p_number))))

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

class DIALOG2FA_OT_show (bpy.types.Operator):
    bl_idname = "dialog2fa.show"
    bl_label = "Send 2FA code"

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

    def __init__ (self, *args, **kwargs):
        super ().__init__(*args, **kwargs)
        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="E-mail address", description="Use e-mail address you used for registration", default = "") 
    viribus_farm_login_pwd: bpy.props.StringProperty (name="Password", description="Password you set during registration", default = "", subtype='PASSWORD')
    show_anim_img_selection: bpy.props.BoolProperty (name="Always ask if to render animation / all frames", description="Always ask if to render animation / all frames when there are more than one frame", default = False)
    viribus_farm_upload_chunk_size: bpy.props.IntProperty (name="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="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="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="Use compress (zip) of files for upload - need extra space on disk (during upload)", description="Files will be uploaded in compressed form", default = True)
    viribus_farm_create_log_file: bpy.props.BoolProperty (name="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=bpy.app.translations.pgettext ("Must be set before uploading"))
        layout.label (text="Must be set before uploading")
        layout.prop (self, "viribus_farm_login_name")
        
        layout.label (text="If you leave password field empty, password will be asked before uploading.")
        layout.label (text="If you put password here, it will be stored as PLAIN TEXT in file on your hard drive")
        layout.label (text="and you do not need to put it before every upload.")

        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")


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

# def project_name_updated (self, context):
#    print ("Project name updated, current value: {}".format(self.viribus_farm_project_name))
# https://blenderartists.org/t/detect-stringproperty-textfield-change/1383920/2

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_name: bpy.props.StringProperty (name="Project name", description="Project name (optional)", default="", maxlen=50, update=project_name_updated)
    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 priority. 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 VIRIBUSADDON_OT_upload (bpy.types.Operator):
    bl_idname = "viribusaddon.upload"
    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, bpy.app.translations.pgettext ("All frames / Animation"), bpy.app.translations.pgettext ("Render all frames"), 1), (const_chosen_output_type_img, bpy.app.translations.pgettext ("One image"), bpy.app.translations.pgettext ("Render one picture"), 2)])

    def __init__ (self, *args, **kwargs):
        
        super ().__init__(*args, **kwargs)
        
        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 ('Nothing at all.')

    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,
                              p_is_file_packed
                              ):
        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,
                                                      'is_packed': p_is_file_packed,
                                                      '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)
        is_lib_packed = 'Y' if p_library.packed_file is not None else 'N'
        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,
                              p_is_file_packed=is_lib_packed
                              )
        
        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
                    is_image_packed = 'Y' if one_image.packed_file is not None else 'N'
                    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,
                                                                p_is_file_packed=is_image_packed
                                                                )                                    
                                    
                                    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,
                                                            p_is_file_packed=is_image_packed
                                                          )
                                
                                # -----------------------------------------------------------------------------------------------------------------------
                            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,
                                                              p_is_file_packed=is_image_packed
                                                              )
                                    
                                    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,
                                                            p_is_file_packed=is_image_packed
                                                          )
                                # -----------------------------------------------------------------------------------------------------------------------
                            
                        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=replace_tag_by_number (file_path_to_upload, one_tile.number), # 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=replace_tag_by_number (os.path.basename (file_path_to_upload), one_tile.number), # 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=replace_tag_by_number (file_path_to_upload, one_tile.number), # file_path_to_upload.replace ('<UDIM>', str (one_tile.number)),
                                                           p_fiction_file='N',
                                                           p_users=one_image.users,
                                                           p_is_file_packed=is_image_packed
                                                          )

                        if (one_image.source in [const_file_source_file, const_file_source_movie, const_file_source_generated]):
                            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,
                                                        p_is_file_packed=is_image_packed
                                                      )
                        
            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:
                    is_image_packed = 'Y' if (one_image.packed_file is not None) else 'N'
                    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 - processing file: {}; {};".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='no_upload_tag')
                            show_message (p_message="mask_to_find_files: {}".format (mask_to_find_files), p_upload_tag='no_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,
                                                          p_is_file_packed=is_image_packed
                                                          )
                                    
                    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=replace_tag_by_number (absolute_path, one_tile.number), # 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=replace_tag_by_number (os.path.basename (one_image.filepath), one_tile.number), #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=replace_tag_by_number (absolute_path, one_tile.number),  # absolute_path.replace ('<UDIM>', str (one_tile.number)),
                                                        p_fiction_file='N',
                                                        p_users=one_image.users,
                                                        p_is_file_packed=is_image_packed
                                                      )

                    if (one_image.source in [const_file_source_file, const_file_source_movie, const_file_source_generated]):
                        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,
                                                    p_is_file_packed=is_image_packed
                                                  )
                        

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

    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,
                              p_is_file_packed='N'
                              )
        
        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 and
                p_file_list[one_file]['is_packed'] != 'Y'):
                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], 'blender_lang_code': bpy.context.preferences.view.language }, 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, post_response_array ['translated_text'])
            post_response.close ()
                
            post_response = viribus_session.post (const_viribus_check_avail_url, data={ 'blender_lang_code': bpy.context.preferences.view.language }, 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, post_response_array ['translated_text']) # bpy.app.translations.pgettext ("Render service is not available at the moment."))
            post_response.close ()
            
        else:  
            error_occured = 1
            error_message = add_text (error_message, bpy.app.translations.pgettext ("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, "{}:{}". format (bpy.app.translations.pgettext ("Too new version of Blender."), bpy.app.version))
            
        if (user_login_name == "" or user_login_name is None):
            error_occured = 1
            error_message = add_text (error_message, bpy.app.translations.pgettext ("Fill e-mail address 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, bpy.app.translations.pgettext ("Save file first, please."))
        else:
            if (not os.path.exists(bpy.data.filepath)):
                error_occured = 1
                error_message = add_text (error_message, bpy.app.translations.pgettext ("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, bpy.app.translations.pgettext ("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) / bpy.context.scene.frame_step) > 1):
            self.show_keyframes_warning = True
            self.keyframes_warning_text_1 = bpy.app.translations.pgettext ("There is no key-frames / actions.")
            self.keyframes_warning_text_2 = bpy.app.translations.pgettext ("Are you sure you want to have") + " " + str ((bpy.context.scene.frame_end - bpy.context.scene.frame_start + 1) / bpy.context.scene.frame_step) + " " + bpy.app.translations.pgettext ("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 = "{} {}".format (missing_files_count, bpy.app.translations.pgettext ("file(s) missing."))
                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=(bpy.app.translations.pgettext ("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=bpy.app.translations.pgettext("What type of output do you want?"))
            row.prop(self, "output_type", text=bpy.app.translations.pgettext("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=bpy.app.translations.pgettext ("Those files has already been successfully uploaded."))
            row.label (text=bpy.app.translations.pgettext("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=bpy.app.translations.pgettext("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 or
            post_response_array ['return_value'] == const_2fa_need_no):
            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.dialog2fa.show('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))
        else:
            # show_message_if_known (p_message=post_response_array ['return_value'], p_operator=self)
            self.report ({'ERROR'}, post_response_array ['translated_text'])
          
        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="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 (VIRIBUSADDON_OT_upload.bl_idname, text=bpy.app.translations.pgettext ("Upload files to ViribusFarm.com"))

classes_to_process = (ViribusFarmUploadSettings, DIALOG2FA_OT_show, VIRIBUSADDON_OT_upload, ViribusFarmPanel, Viribus_Farm_Addon_Preferences)

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

def register():
    # bpy.app.translations.register(__name__, Viribus_translations)
    for cls in classes_to_process:
        bpy.utils.register_class(cls)

    bpy.types.Scene.ViribusFarmProperties = bpy.props.PointerProperty (type=ViribusFarmUploadSettings)
    
    print (__name__);
    print (bpy.context.preferences.view.language);
    
    translations_dict = {}
    for msg in translations_tuple:
        key = msg[0]
        for lang, trans, (is_fuzzy, comments) in msg[2:]:
            if trans and not is_fuzzy:
                translations_dict.setdefault(lang, {})[key] = trans
# 
# translations_tuple = (
#     (("*", ""),
#      ((), ()),
#     (("*", "E-mail address"),
#      (("bpy.types.Viribus_Farm_Addon_Preferences.viribus_farm_login_name",),
#       ()),
#      ("sk_SK", "E-mailová adresa",
#                (False, ())),
#     ),
# )
# 
# translations_dict = {}
# for msg in translations_tuple:
#     key = msg[0]
#     for lang, trans, (is_fuzzy, comments) in msg[2:]:
#         if trans and not is_fuzzy:
#             translations_dict.setdefault(lang, {})[key] = trans
# 
# bpy.app.translations.register(__name__, translations_dict)

                
    bpy.app.translations.register(__name__, translations_dict)


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

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

    del bpy.types.Scene.ViribusFarmProperties
    
    bpy.app.translations.unregister(__name__)

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

if __name__ == "__main__":
    register()

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