Malrey Lee

101 papers B 3C 2Misc 9Journal 51Unranked 36
YearRankTypeTitle / Venue / Authors
2023 J jnl
Eng. Appl. Artif. Intell.
Dadajon Jurakuziev, Sherozbek Jumaboev, Malrey Lee
2022 J jnl
Remote. Sens.
Sherozbek Jumaboev, Dadajon Jurakuziev, Malrey Lee
2021 J jnl
Eng. Appl. Artif. Intell.
Keyvan Golalipour, Ebrahim Akbari, Seyed Saeed Hamidi, Malrey Lee, Rasul Enayatifar
2020 J jnl
Appl. Intell.
Congcong Wu, Jianli Zhao, Yanhong Feng, Malrey Lee
2020 J jnl
J. Inf. Secur. Appl.
Mehdi Yadollahi, Rasul Enayatifar, Hossein Nematzadeh, Malrey Lee, Jae-Young Choi
2020 J jnl
Sensors
Shuli Xing, Malrey Lee
2018 J jnl
Int. J. Sens. Networks
Ulzii-Orshikh Dorj, Young Keun Lee, Sang-Seok Yun, Jae-Young Choi, Malrey Lee
2018 J jnl
Wirel. Commun. Mob. Comput.
Aamir Shahzad, Jae-Young Choi, Naixue Xiong, Young-Gab Kim, Malrey Lee
2018 J jnl
J. Sensors
Aamir Shahzad, Yang Sun Lee, Malrey Lee, Young-Gab Kim, Naixue Xiong
2018 J jnl
Multim. Tools Appl.
Ulzii-Orshikh Dorj, Keun Kwang Lee, Jae-Young Choi, Malrey Lee
2017 J jnl
Neural Comput. Appl.
Suntae Kim, Malrey Lee, Jongho Lee
2017 J jnl
Comput. Electron. Agric.
Ulzii-Orshikh Dorj, Malrey Lee, Sang-Seok Yun
2017 J jnl
J. Sensors
Ulzii-Orshikh Dorj, Malrey Lee, Jae-Young Choi, Young Keun Lee, Gisung Jeong
2016 J jnl
Sensors
Aamir Shahzad, René Landry, Malrey Lee, Naixue Xiong, Jongho Lee, Changhoon Lee
2016 J jnl
Sensors
Aamir Shahzad, Malrey Lee, Neal Naixue Xiong, Gisung Jeong, Young Keun Lee, Jae-Young Choi, Abdul Waheed Mahesar, Iftikhar Ahmad
2016 J jnl
Sensors
Aamir Shahzad, Malrey Lee, Suntae Kim, Kangmin Kim, Jae-Young Choi, Younghwa Cho, Keun Kwang Lee
2016 J jnl
Multim. Tools Appl.
Aamir Shahzad, Malrey Lee, Changhoon Lee, Naixue Xiong, Suntae Kim, Young Keun Lee, Kangmin Kim, Seon-Mi Woo, Gisung Jeong
2015 J jnl
Computing
Marjan Radi, Behnam Dezfouli, Kamalrulnizam Abu Bakar, Shukor Abd Razak, Malrey Lee
2015 J jnl
Symmetry
Aamir Shahzad, Malrey Lee, Hyung Doo Kim, Seon-Mi Woo, Naixue Xiong
2015 J jnl
Symmetry
Aamir Shahzad, Malrey Lee, Young Keun Lee, Suntae Kim, Naixue Xiong, Jae-Young Choi, Young-Hwa Cho
2015 J jnl
Int. J. Distributed Sens. Networks
Aamir Shahzad, Kalum Priyanath Udagepola, Young Keun Lee, Soojin Park, Malrey Lee
2014 J jnl
Int. J. Sens. Networks
Young Keun Lee, Malrey Lee, Suchul Hwang, Gisung Jeong
2014 conf
WAIM Workshops
Soonduck Yoo, Kwangdon Choi, Malrey Lee
2014 J jnl
Int. J. Pattern Recognit. Artif. Intell.
Young Keun Lee, Malrey Lee, Keun Kwang Lee, Gisung Jeong
2014 J jnl
Comput. J.
Marjan Radi, Behnam Dezfouli, Kamalrulnizam Abu Bakar, Shukor Abd Razak, Malrey Lee
2013 J jnl
Int. J. Pattern Recognit. Artif. Intell.
Ulzii-Orshikh Dorj, Malrey Lee, Keun Kwang Lee, Gisung Jeong
2013 J jnl
Int. J. Pattern Recognit. Artif. Intell.
Sukumar Senthilkumar, Malrey Lee, Gisung Jeong
2012 conf
FGIT-MulGraB/BSBT/IUrC
Ulzii-Orshikh Dorj, Malrey Lee, Diyan-ul-Imaan
2012 J jnl
Appl. Soft Comput.
Malrey Lee, Young Keun Lee, Robert G. Rittenhouse
2012 conf
ICONIP (1)
Junaid Ahsenali Chaudhry, Uvais A. Qidwai, Malrey Lee
2012 J jnl
Sensors
Marjan Radi, Behnam Dezfouli, Kamalrulnizam Abu Bakar, Malrey Lee
2012 conf
ICONIP (4)
Robert G. Rittenhouse, Malrey Lee, Junaid Ahsenali Chaudhry, Uvais A. Qidwai
2012 J jnl
Soft Comput.
Malrey Lee, Eun-Kwan Kang
2011 J jnl
Comput. Informatics
Malrey Lee, Nam-Deok Cho, Keun Kwang Lee, Kyoung Sook Ko
2010 J jnl
Int. J. Pattern Recognit. Artif. Intell.
Malrey Lee, Keun Kwang Lee
2010 J jnl
Sensors
Malrey Lee, Thomas M. Gatton, Keun Kwang Lee
2010 J jnl
Inf. Sci.
Malrey Lee, Nam-Deok Cho, Thomas M. Gatton
2010 J jnl
Artif. Intell. Rev.
Hyogun Yoon, Malrey Lee, Thomas M. Gatton
2010 J jnl
Sensors
Eungyeong Kim, Malrey Lee, Thomas M. Gatton, Jaewan Lee, Yupeng Zang
2010 J jnl
Int. J. Pattern Recognit. Artif. Intell.
Hadong Kim, Malrey Lee
2010 J jnl
Robotics Auton. Syst.
Mahmoud Tarokh, Kancherla Keerthi, Malrey Lee
2010 Misc conf
IC-AI
Thomas M. Gatton, Malrey Lee
2010 J jnl
Artif. Intell. Rev.
Mahmoud Tarokh, Matthew Cross, Malrey Lee
2010 J jnl
Artif. Intell. Rev.
Malrey Lee, Mahmoud Tarokh, Matthew Cross
2010 J jnl
Sensors
Malrey Lee, Thomas M. Gatton
2009 Misc conf
IKE
Altan Hagicumus, Thomas M. Gatton, Malrey Lee, Pradip Peter Dey, Arun K. Datta
2009 conf
SERVICES I
Yupeng Zhang, Malrey Lee, Thomas M. Gatton
2009 Misc conf
IC-AI
Matthew Cross, Mahmoud Tarokh, Malrey Lee
2009 conf
KES-AMSTA
Michael Angelo G. Salvo, Romeo Mark A. Mateo, Jaewan Lee, Malrey Lee
2008 Misc conf
IC-AI
Eungyeong Kim, Yupeng Zhang, Malrey Lee
2008 Misc conf
IC-AI
Yupeng Zhang, Malrey Lee, Eungyeong Kim, Thomas M. Gatton
2008 Misc conf
IKE
Thomas M. Gatton, Malrey Lee, Alton Hacigums, Yupeng Zhang
2008 conf
KES-AMSTA
Eungyeong Kim, Hyogun Yoon, Malrey Lee, Thomas M. Gatton
2007 Misc conf
IC-AI
Malrey Lee, Yupeng Zhang, ThaeEun Kim, Thomas M. Gatton
2007 C conf
SERA
Hyogun Yoon, Eungyeong Kim, Malrey Lee
2007 conf
KES-AMSTA
Thomas M. Gatton, Malrey Lee, Tae-Eun Kim, Young Keun Lee
2007 conf
KES-AMSTA
Bobby D. Gerardo, Jaewan Lee, Malrey Lee
2007 conf
MLMTA
Taeho Jo, Malrey Lee, Thomas M. Gatton
2007 C conf
SERA
Taeho Jo, Malrey Lee
2007 conf
JIISIC
Youngchul Bae, Malrey Lee, Sang Doo Shin, Thomas M. Gatton, Yigon Kim
2007 conf
ISNN (1)
Taeho Jo, Malrey Lee
2007 conf
SWWS
Keun-Sang Yi, Taeho Jo, Malrey Lee, Young-Keun Choi
2007 Misc conf
IKE
Taeho Jo, Malrey Lee, Thomas M. Gatton
2007 conf
DMIN
Taeho Jo, Malrey Lee, Hyogun Yoon, Chulgyu Song
2007 conf
MIRAGE
Krzysztof Slot, Piotr Korbel, Hyongsuk Kim, Malrey Lee, Suhong Ko
2007 Misc conf
IC-AI
Taeho Jo, Malrey Lee
2007 J jnl
J. Convergence Inf. Technol.
Taeho Jo, Malrey Lee, Yigon Kim
2007 conf
ISNN (2)
Taeho Jo, Malrey Lee
2007 conf
International Conference on Internet Computing
Taeho Jo, Malrey Lee, Eungyeong Kim
2006 conf
ICIC (1)
Malrey Lee, Kang Yun Jeong
2006 conf
ICCSA (5)
Malrey Lee, Jae-deuk Lee, Hye-Jin Jeong, YoungSoon Lee, Seong-Man Choi, Thomas M. Gatton
2006 conf
ISNN (2)
Malrey Lee, Hye-Jin Jeong, Young Joon Choi, Thomas M. Gatton
2006 J jnl
Artif. Intell. Rev.
Malrey Lee
2006 conf
ICCSA (3)
Youngchul Bae, Malrey Lee, Thomas M. Gatton
2006 conf
ICHIT
Romeo Mark A. Mateo, Malrey Lee, Jaewan Lee
2006 conf
FSKD
Romeo Mark A. Mateo, Malrey Lee, Su-Chong Joo, Jaewan Lee
2006 conf
ICIC (1)
Malrey Lee, Hye-Jin Jeong
2006 conf
ICCSA (5)
Yunjeong Kang, Malrey Lee, Yongseok Lee, Thomas M. Gatton
2006 conf
ICCSA (5)
Seong-Man Choi, Malrey Lee, Cheol-Jung Yoo, Ok-Bae Chang
2006 J jnl
J. Convergence Inf. Technol.
Taeho Jo, Malrey Lee
2006 conf
ICCSA (4)
Malrey Lee, Eun-Kwan Kang, Thomas M. Gatton
2005 conf
KES (3)
Sang-Hoon Kim, Tae-Eun Kim, Malrey Lee, Jong-Soo Choi
2005 J jnl
Int. J. Fuzzy Log. Intell. Syst.
Jae-deuk Lee, Hye-Jin Jeong, Hee-Suk Kim, Malrey Lee
2005 conf
ICIC (2)
Yangsun Lee, Heau-Jo Kang, Malrey Lee, Tai-Hoon Kim
2005 conf
ICCSA (2)
Malrey Lee, Dong-Ju Im, Young Keun Lee, Jae-deuk Lee, Suwon Lee, Keun Kwang Lee, HeeJo Kang
2005 conf
ICCSA (2)
Malrey Lee, Eun-Ser Lee, HeeJo Kang, HeeSook Kim
2005 conf
ICIC (2)
Heau-Jo Kang, Malrey Lee
2005 conf
ICCSA (2)
Eun-Ser Lee, Malrey Lee
2005 conf
ICCSA (2)
Dong-Ju Im, Malrey Lee, Young Keun Lee, Tae-Eun Kim, Suwon Lee, Jaewan Lee, Keun Kwang Lee, Kyung Dal Cho
2005 conf
ICCSA (2)
Bobby D. Gerardo, Jaewan Lee, Yeon-Sung Choi, Malrey Lee
2005 J jnl
Neural Comput. Appl.
Malrey Lee, Tae-Eun Kim
2004 J jnl
J. Intell. Robotic Syst.
Malrey Lee, Ok-Bae Chang, Cheol-Jung Yoo, Yong-Sung Kim, Oubong Gwun
2004 J jnl
Int. J. Artif. Intell. Tools
Malrey Lee, Heai-Jo Kang, Tae-Eun Kim
2004 J jnl
Neural Comput. Appl.
Malrey Lee, Su-Chong Joo, HeeSook Kim
2004 conf
ICCSA (1)
Bobby D. Gerardo, Jaewan Lee, Jung-sik Lee, Mingi Park, Malrey Lee
2003 B conf
KES
Malrey Lee, Dong-Ju Im, Sung Su Park, Jung-sik Lee, Jaewan Lee
2003 J jnl
Inf. Sci.
Malrey Lee
2003 J jnl
Inf. Sci.
Malrey Lee
2003 B conf
KES
Malrey Lee, Dong-Ju Im, Sung Su Park, Jung-sik Lee, Jaewan Lee, Bumjun Cho
2003 B conf
KES
Hyun-Cheol Jeong, Malrey Lee, Bumjun Cho
1999 J jnl
Int. J. Artif. Intell. Tools
Malrey Lee, Tae-Eun Kim
s3-storage/s3_uploader.py
← Index s3-storage/s3_uploader.py python
# python s3_uploader.py /path/to/files repository_name --notes "Optional notes"
import hashlib
import multiprocessing
from multiprocessing import Pool
import sys
import logging
from logging.handlers import QueueHandler
from datetime import datetime
import os
import time
import json
from enum import Enum
import tempfile
import magic
from typing import Optional, Dict, Any
from dotenv import load_dotenv
from datetime import timezone

# File handling
import py7zr
import pyzipper
from magika import Magika

# S3 and database
from minio import Minio
import clickhouse_connect

load_dotenv(override=True)

class UploadResult(Enum):
    CORRECTLY = 0
    FAILED = 1
    SKIPPED = 2  # For duplicates

class FileNameFormatter(logging.Formatter):
    def format(self, record):
        record.filenameinfo = getattr(record, "filenameinfo", "unknown")
        return super().format(record)

def setup_logger(log_file, filename):
    """Set up a logger for a specific file"""
    logger = logging.getLogger(filename)
    if not logger.handlers:
        if os.getenv("SERVER_ENV") == "prod":
            logger.setLevel(logging.INFO)
        else:
            logger.setLevel(logging.DEBUG)
            
        # Create file handler
        handler = logging.FileHandler(log_file)
        formatter = FileNameFormatter(
            "%(asctime)s - %(filenameinfo)s - %(levelname)s - %(message)s"
        )
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        logger.propagate = False
    return logger

def logger_thread(log_queue, log_file):
    handler = logging.FileHandler(log_file)
    formatter = FileNameFormatter(
        "%(asctime)s - %(filenameinfo)s - %(levelname)s - %(message)s"
    )
    handler.setFormatter(formatter)

    while True:
        try:
            record = log_queue.get()
            if record is None:
                break
            message = formatter.format(record)
            handler.stream.write(message + "\n")
            handler.stream.flush()
        except Exception:
            import traceback
            print("[ERR] Error in logger thread:", file=sys.stderr)
            traceback.print_exc(file=sys.stderr)

def get_s3_key(sha256: str, is_archived: bool, original_ext: str = None) -> str:
    """
    Generate S3 key using sharding pattern with appropriate extension
    
    Args:
        sha256: The file's SHA256 hash
        is_archived: Whether the file was already archived (zip/7z)
        original_ext: The original file extension (if any)
    """
    # If file was already archived, keep its extension
    if is_archived and original_ext and original_ext.lower() in ['.zip', '.7z']:
        extension = original_ext
    else:
        # For files we archived ourselves, use .zip
        extension = '.zip'
        
    return f"{sha256[:2]}/{sha256[2:4]}/{sha256}{extension}"

def process_single_file(args):
    """Independent worker function for processing a single file"""
    filepath, file_number, total_files, config, log_file = args
    filename = os.path.basename(filepath)
    base_logger = setup_logger(log_file, filename)
    extra = {"filenameinfo": filename}
    logger = logging.LoggerAdapter(base_logger, extra)
    s3_client = None
    ch_client = None

    try:
        logger.info(f"Processing file {file_number}/{total_files}: {filepath}")

        # Create connections for this process
        s3_client = Minio(
            config["s3_endpoint"],
            access_key=config["s3_access_key"],
            secret_key=config["s3_secret_key"],
            secure=True
        )
        
        ch_client = clickhouse_connect.get_client(
            host=config['clickhouse_host'],
            port=config['clickhouse_port'],
            username=config['clickhouse_user'],
            password=config['clickhouse_password'],
            database=config['clickhouse_database'],
            verify=config['clickhouse_verify']
        )

        # Process the file and get contents
        ext = os.path.splitext(filepath.lower())[1]
        is_archived = ext in ['.zip', '.7z']

        with tempfile.TemporaryDirectory() as temp_dir:
            try:
                if filename.startswith("."):
                    logger.debug(f"Skipping hidden file: {filename}")
                    return UploadResult.SKIPPED

                elif ext == ".zip":
                    logger.debug(f"Processing ZIP file: {filepath}")
                    with pyzipper.AESZipFile(filepath) as zf:
                        zf.pwd = b"infected"
                        filename = zf.namelist()[0]
                        zf.extractall(temp_dir)
                        extracted_path = os.path.join(temp_dir, filename)
                        
                        with open(extracted_path, "rb") as f:
                            file_content = f.read()
                        with open(filepath, "rb") as f:
                            archive_content = f.read()
                            
                elif ext == ".7z":
                    logger.debug(f"Processing 7z file: {filepath}")
                    with py7zr.SevenZipFile(filepath, mode="r", password="infected") as z:
                        z.extractall(path=temp_dir)
                        # Get first file in the archive
                        for root, _, files in os.walk(temp_dir):
                            if files:
                                filename = files[0]
                                extracted_path = os.path.join(root, filename)
                                break
                                
                        with open(extracted_path, "rb") as f:
                            file_content = f.read()
                        with open(filepath, "rb") as f:
                            archive_content = f.read()
                else:
                    logger.debug(f"Processing non-archived file: {filepath}")
                    # For non-archived files, create password protected zip
                    with open(filepath, "rb") as f:
                        file_content = f.read()
                    filename = os.path.basename(filepath)
                    
                    # Create password protected zip
                    with tempfile.NamedTemporaryFile(delete=False) as temp_zip:
                        with pyzipper.AESZipFile(
                            temp_zip.name,
                            'w',
                            compression=pyzipper.ZIP_LZMA,
                            encryption=pyzipper.WZ_AES
                        ) as zf:
                            zf.pwd = b'infected'
                            zf.writestr(filename, file_content)
                        
                        with open(temp_zip.name, 'rb') as f:
                            archive_content = f.read()
                    
                    os.unlink(temp_zip.name)

                # Calculate hash
                sha256 = hashlib.sha256(file_content).hexdigest()
                # s3_key = f"{sha256[:2]}/{sha256[2:4]}/{sha256}"
                s3_key = get_s3_key(sha256, is_archived, ext if is_archived else None)
                logger.debug(f"Calculated SHA256: {sha256}")

                # Check if exists in S3
                try:
                    s3_client.stat_object(config["s3_bucket"], s3_key)
                    exists = True
                    logger.debug(f"File exists in S3: {s3_key}")
                except:
                    exists = False
                    logger.debug(f"File does not exist in S3: {s3_key}")

                # Prepare metadata
                max_timestamp = int(datetime(9999, 12, 31, 23, 59, 59, tzinfo=timezone.utc).timestamp() * 1000)
                now = datetime.now(timezone.utc)
                now_timestamp = int(now.timestamp() * 1000)
                inverted_timestamp = datetime.fromtimestamp((max_timestamp - now_timestamp)/1000, tz=timezone.utc)


                data = [[
                    sha256,                     # String
                    filename,                   # String
                    config['repository'],       # String
                    len(file_content),         # UInt64
                    magic.from_buffer(file_content),        # String
                    magic.from_buffer(file_content, mime=True),  # String
                    Magika().identify_bytes(file_content).output.ct_label,  # String
                    now,                       # upload_date
                    now,                       # first_seen
                    inverted_timestamp,        # version_date for replacing
                    config['s3_bucket'],       # String
                    s3_key,                    # String
                    config['notes'] if config['notes'] else None  # Nullable(String)
                ]]

                column_names = [
                    'sha256', 'filename', 'repository', 'file_size',
                    'filetype', 'filetype_mime', 'filetype_magika',
                    'upload_date', 'first_seen', 'version_date', 's3_bucket', 's3_key', 'notes'
                ]

                column_type_names = [
                    'String', 'String', 'LowCardinality(String)', 'UInt64',
                    'String', 'String', 'String',
                    'DateTime64(3, \'UTC\')', 'DateTime64(3, \'UTC\')', 
                    'DateTime64(3, \'UTC\')', 'String', 'String', 'Nullable(String)'
                ]
                
                if exists:
                    # Just update database
                    try:
                        ch_client.insert(
                            'samples_catalog',
                            data,
                            column_names=column_names,
                            column_type_names=column_type_names,
                            settings={'input_format_values_interpret_expressions': 0}
                        )
                        logger.info(f"Updated metadata for existing file: {sha256}")
                    except Exception as e:
                        logger.error(f"Error inserting metadata: {str(e)}")
                        import traceback
                        logger.error(f"Full traceback: {traceback.format_exc()}")
                    return UploadResult.SKIPPED
                else:
                    try:    
                        # Upload to S3 and insert metadata
                        import io
                        file_data = io.BytesIO(archive_content)
                        s3_client.put_object(
                            bucket_name=config['s3_bucket'],
                                object_name=s3_key,
                                data=file_data,
                                length=len(archive_content)
                            )
                        logger.info(f"Uploaded to S3: {s3_key}")
                    except Exception as e:
                        logger.error(f"Error uploading to S3: {str(e)}")
                        import traceback
                        logger.error(f"Full traceback: {traceback.format_exc()}")
                        return UploadResult.FAILED
                    
                    try:
                        ch_client.insert(
                            'samples_catalog',
                            data,
                            column_names=column_names,
                            column_type_names=column_type_names,
                            settings={'input_format_values_interpret_expressions': 0}
                        )
                        logger.info(f"Successfully processed file: {sha256}")
                        return UploadResult.CORRECTLY
                    except Exception as e:
                        logger.error(f"Error inserting metadata: {str(e)}")
                        import traceback
                        logger.error(f"Full traceback: {traceback.format_exc()}")
                        return UploadResult.FAILED

            except Exception as e:
                logger.error(f"Error processing file: {str(e)}")
                import traceback
                logger.error(f"Full traceback: {traceback.format_exc()}")
                return UploadResult.FAILED

    except Exception as e:
        logger.error(f"Error in worker: {str(e)}")
        return UploadResult.FAILED
    finally:
        # if s3_client:
        #     s3_client.close()
        if ch_client:
            ch_client.close()
        if base_logger and base_logger.handlers:
            for handler in base_logger.handlers:
                handler.close()
            base_logger.handlers.clear()

class S3Uploader:
    def __init__(self, path: str, repository: str, notes: Optional[str] = None):
        """
        Initialize S3 uploader
        
        Args:
            path: Path to file or directory to process
            repository: Repository name
            notes: Optional notes to add to all files
        """
        self.path = path
        self.repository = repository
        self.notes = notes
        
        # Initialize multiprocessing components
        self.manager = multiprocessing.Manager()
        # self.log_queue = self.manager.Queue()
        self.total_results = self.manager.dict({result: 0 for result in UploadResult})
        
        # Set up logging
        self.today = datetime.today().strftime("%Y%m%dT%H%M%S")
        # self.log_file = os.getenv("LOG_FILE_PATH", "./logs/") + f"{self.today}-{self.repository}-upload.txt"
        log_base_path = os.getenv("LOG_FILE_PATH")
        if not log_base_path:
            log_base_path = os.path.join(os.getcwd(), "logs")  # Default to ./logs directory
        self.log_file = os.path.join(log_base_path, f"{self.today}-{self.repository}-upload.txt")
        os.makedirs(os.path.dirname(self.log_file), exist_ok=True)

        # Initialize S3 client
        self.s3_client = Minio(
            os.getenv("S3_ENDPOINT"),
            access_key=os.getenv("S3_ACCESS_KEY"),
            secret_key=os.getenv("S3_SECRET_KEY"),
            secure=True  # Set to False if not using HTTPS
        )
        self.bucket_name = os.getenv("S3_BUCKET")

        # Initialize ClickHouse client
        self.ch_client = clickhouse_connect.get_client(
            host=os.getenv('CLICKHOUSE_HOST'),
            port=os.getenv('CLICKHOUSE_PORT'),
            username=os.getenv('CLICKHOUSE_USER'),
            password=os.getenv('CLICKHOUSE_PASSWORD'),
            database=os.getenv('CLICKHOUSE_DATABASE'),
            verify=os.getenv('CLICKHOUSE_ENFORCE_SSL', 'False')
        )
        
        # Ensure database table exists
        self._create_table()

    def _create_table(self):
        """Create the file catalog table if it doesn't exist"""
        create_table_query = """
            CREATE TABLE IF NOT EXISTS samples_catalog (
                sha256 String,
                filename String,
                repository LowCardinality(String),
                file_size UInt64,
                filetype String,
                filetype_mime String,
                filetype_magika String,
                upload_date DateTime64(3, 'UTC'),
                first_seen DateTime64(3, 'UTC'),
                version_date DateTime64(3, 'UTC'),  -- This will be inverted timestamp
                s3_bucket String,
                s3_key String,
                notes Nullable(String),
                PRIMARY KEY (sha256, repository)
            ) ENGINE = ReplacingMergeTree(version_date)
            ORDER BY (sha256, repository)
        """
        self.ch_client.command(create_table_query)

    # # def upload(self):
    # #     """Main upload function with multiprocessing support"""
    # #     start_time = time.time()

    # #     config = {
    # #         "s3_endpoint": os.getenv("S3_ENDPOINT"),
    # #         "s3_access_key": os.getenv("S3_ACCESS_KEY"),
    # #         "s3_secret_key": os.getenv("S3_SECRET_KEY"),
    # #         "s3_bucket": os.getenv("S3_BUCKET"),
    # #         "clickhouse_host": os.getenv("CLICKHOUSE_HOST"),
    # #         "clickhouse_port": os.getenv("CLICKHOUSE_PORT"),
    # #         "clickhouse_user": os.getenv("CLICKHOUSE_USER"),
    # #         "clickhouse_password": os.getenv("CLICKHOUSE_PASSWORD"),
    # #         "clickhouse_database": os.getenv("CLICKHOUSE_DATABASE"),
    # #         "clickhouse_verify": os.getenv("CLICKHOUSE_ENFORCE_SSL", "False"),
    # #         "repository": self.repository,
    # #         "notes": self.notes
    # #     }

    # #     try:
    # #         # Get list of files
    # #         if os.path.isfile(self.path):
    # #             files = [self.path] if not self.path.endswith('.txt') else [
    # #                 line.strip() for line in open(self.path) 
    # #                 if line.strip() and not os.path.basename(line.strip()).startswith('.')
    # #             ]
    # #         elif os.path.isdir(self.path):
    # #             files = [
    # #                 os.path.join(root, file)
    # #                 for root, _, files in os.walk(self.path)
    # #                 for file in files
    # #                 if not file.startswith('.')
    # #             ]
    # #         else:
    # #             print(f"[ERR] Invalid path: {self.path}")
    # #             return

    # #         total_files = len(files)
    # #         print(f"Found {total_files} files to process")

    # #         with Pool(processes=max(1, multiprocessing.cpu_count() - 1)) as pool:
    # #             results = pool.map(
    # #                 process_single_file,
    # #                 [(f, i + 1, total_files, config, self.log_file) for i, f in enumerate(files)]
    # #             )

    # #         # Update statistics
    # #         for result in results:
    # #             if result is not None:
    # #                 self.total_results[result] += 1

    # #         # Generate summary
    # #         end_time = time.time()
    # #         elapsed_time = end_time - start_time
    # #         elapsed_time_pretty = time.strftime("%H:%M:%S", time.gmtime(elapsed_time))

    # #         summary = (
    # #             f"\n\nUpload finished for {self.path}"
    # #             f"\nTime required: {elapsed_time_pretty}"
    # #             f"\nResults:"
    # #             f"\n- Total processed: {sum(self.total_results.values())}"
    # #             f"\n- Successfully uploaded: {self.total_results[UploadResult.CORRECTLY]}"
    # #             f"\n- Skipped (already exists): {self.total_results[UploadResult.SKIPPED]}"
    # #             f"\n- Failed: {self.total_results[UploadResult.FAILED]}\n"
    # #         )

    # #         with open(self.log_file, "a") as f:
    # #             f.write(summary)

    # #         print(summary)

    # #     except Exception as e:
    # #         print(f"[ERR] Error in upload process: {str(e)}")

    # def upload(self):
    #     """Main upload function with multiprocessing support"""
    #     start_time = time.time()
    #     BATCH_SIZE = 1000  # Process files in batches of 1000

    #     config = {
    #         "s3_endpoint": os.getenv("S3_ENDPOINT"),
    #         "s3_access_key": os.getenv("S3_ACCESS_KEY"),
    #         "s3_secret_key": os.getenv("S3_SECRET_KEY"),
    #         "s3_bucket": os.getenv("S3_BUCKET"),
    #         "clickhouse_host": os.getenv("CLICKHOUSE_HOST"),
    #         "clickhouse_port": os.getenv("CLICKHOUSE_PORT"),
    #         "clickhouse_user": os.getenv("CLICKHOUSE_USER"),
    #         "clickhouse_password": os.getenv("CLICKHOUSE_PASSWORD"),
    #         "clickhouse_database": os.getenv("CLICKHOUSE_DATABASE"),
    #         "clickhouse_verify": os.getenv("CLICKHOUSE_ENFORCE_SSL", "False"),
    #         "repository": self.repository,
    #         "notes": self.notes
    #     }

    #     try:
    #         # Get list of files
    #         if os.path.isfile(self.path):
    #             files = [self.path] if not self.path.endswith('.txt') else [
    #                 line.strip() for line in open(self.path) 
    #                 if line.strip() and not os.path.basename(line.strip()).startswith('.')
    #             ]
    #         elif os.path.isdir(self.path):
    #             files = [
    #                 os.path.join(root, file)
    #                 for root, _, files in os.walk(self.path)
    #                 for file in files
    #                 if not file.startswith('.')
    #             ]
    #         else:
    #             print(f"[ERR] Invalid path: {self.path}")
    #             return

    #         total_files = len(files)
    #         print(f"Found {total_files} files to process")

    #         # Process files in batches
    #         for i in range(0, total_files, BATCH_SIZE):
    #             batch_files = files[i:i + BATCH_SIZE]
    #             batch_start = i + 1
    #             batch_end = min(i + BATCH_SIZE, total_files)
    #             print(f"\nProcessing batch {batch_start}-{batch_end} of {total_files}")
                
    #             with Pool(processes=max(1, multiprocessing.cpu_count() - 1)) as pool:
    #                 batch_results = pool.map(
    #                     process_single_file,
    #                     [(f, j + 1, total_files, config, self.log_file) 
    #                         for j, f in enumerate(batch_files, start=i)]
    #                 )
                    
    #                 # Update statistics for this batch
    #                 for result in batch_results:
    #                     if result is not None:
    #                         self.total_results[result] += 1

    #                 # Print intermediate summary
    #                 print(f"Batch {batch_start}-{batch_end} completed:")
    #                 print(f"- Successfully uploaded: {sum(1 for r in batch_results if r == UploadResult.CORRECTLY)}")
    #                 print(f"- Skipped (exists): {sum(1 for r in batch_results if r == UploadResult.SKIPPED)}")
    #                 print(f"- Failed: {sum(1 for r in batch_results if r == UploadResult.FAILED)}")

    #         # Generate final summary
    #         end_time = time.time()
    #         elapsed_time = end_time - start_time
    #         elapsed_time_pretty = time.strftime("%H:%M:%S", time.gmtime(elapsed_time))

    #         summary = (
    #             f"\n\nUpload finished for {self.path}"
    #             f"\nTime required: {elapsed_time_pretty}"
    #             f"\nResults:"
    #             f"\n- Total processed: {sum(self.total_results.values())}"
    #             f"\n- Successfully uploaded: {self.total_results[UploadResult.CORRECTLY]}"
    #             f"\n- Skipped (already exists): {self.total_results[UploadResult.SKIPPED]}"
    #             f"\n- Failed: {self.total_results[UploadResult.FAILED]}\n"
    #         )

    #         with open(self.log_file, "a") as f:
    #             f.write(summary)

    #         print(summary)

    #     except Exception as e:
    #         print(f"[ERR] Error in upload process: {str(e)}")

    def upload(self):
        """Main upload function with multiprocessing support"""
        start_time = time.time()
        BATCH_SIZE = 1000  # Process files in batches of 1000

        config = {
            "s3_endpoint": os.getenv("S3_ENDPOINT"),
            "s3_access_key": os.getenv("S3_ACCESS_KEY"),
            "s3_secret_key": os.getenv("S3_SECRET_KEY"),
            "s3_bucket": os.getenv("S3_BUCKET"),
            "clickhouse_host": os.getenv("CLICKHOUSE_HOST"),
            "clickhouse_port": os.getenv("CLICKHOUSE_PORT"),
            "clickhouse_user": os.getenv("CLICKHOUSE_USER"),
            "clickhouse_password": os.getenv("CLICKHOUSE_PASSWORD"),
            "clickhouse_database": os.getenv("CLICKHOUSE_DATABASE"),
            "clickhouse_verify": os.getenv("CLICKHOUSE_ENFORCE_SSL", "False"),
            "repository": self.repository,
            "notes": self.notes
        }

        try:
            # Get all existing filenames for this repository
            query = f"SELECT filename FROM samples_catalog WHERE repository = '{self.repository}'"
            # existing_files = set()
            # for row in self.ch_client.query(query).result_rows:
            #     filename = os.path.splitext(row[0])[0]  # Strip extension
            #     existing_files.add(filename)
            existing_files = set(row[0] for row in self.ch_client.query(query).result_rows)
            print(f"Found {len(existing_files)} existing files in repository")

            # Get list of files to process
            if os.path.isfile(self.path):
                files = [self.path] if not self.path.endswith('.txt') else [
                    line.strip() for line in open(self.path) 
                    if line.strip() and not os.path.basename(line.strip()).startswith('.')
                ]
            elif os.path.isdir(self.path):
                files = [
                    os.path.join(root, file)
                    for root, _, files in os.walk(self.path)
                    for file in files
                    if not file.startswith('.')
                ]
            else:
                print(f"[ERR] Invalid path: {self.path}")
                return

            # Filter out files that are already in the repository
            files_to_process = []
            for f in files:
                basename = os.path.splitext(os.path.basename(f))[0]
                if basename not in existing_files:
                    files_to_process.append(f)

            total_files = len(files_to_process)
            skipped_files = len(files) - total_files
            print(f"Found {total_files} new files to process (skipped {skipped_files} existing files)")

            if total_files == 0:
                print("No new files to process")
                return

            # Process files in batches
            for i in range(0, total_files, BATCH_SIZE):
                batch_files = files_to_process[i:i + BATCH_SIZE]
                batch_start = i + 1
                batch_end = min(i + BATCH_SIZE, total_files)
                print(f"\nProcessing batch {batch_start}-{batch_end} of {total_files}")
                
                with Pool(processes=max(1, multiprocessing.cpu_count() - 1)) as pool:
                    batch_results = pool.map(
                        process_single_file,
                        [(f, j + 1, total_files, config, self.log_file) 
                        for j, f in enumerate(batch_files, start=i)]
                    )
                    
                    # Update statistics for this batch
                    for result in batch_results:
                        if result is not None:
                            self.total_results[result] += 1

                    # Print intermediate summary
                    print(f"Batch {batch_start}-{batch_end} completed:")
                    print(f"- Successfully uploaded: {sum(1 for r in batch_results if r == UploadResult.CORRECTLY)}")
                    print(f"- Skipped (exists): {sum(1 for r in batch_results if r == UploadResult.SKIPPED)}")
                    print(f"- Failed: {sum(1 for r in batch_results if r == UploadResult.FAILED)}")

            # Generate final summary
            end_time = time.time()
            elapsed_time = end_time - start_time
            elapsed_time_pretty = time.strftime("%H:%M:%S", time.gmtime(elapsed_time))

            summary = (
                f"\n\nUpload finished for {self.path}"
                f"\nTime required: {elapsed_time_pretty}"
                f"\nResults:"
                f"\n- Files already in repository: {skipped_files}"
                f"\n- New files processed: {total_files}"
                f"\n- Successfully uploaded: {self.total_results[UploadResult.CORRECTLY]}"
                f"\n- Skipped (already exists): {self.total_results[UploadResult.SKIPPED]}"
                f"\n- Failed: {self.total_results[UploadResult.FAILED]}\n"
            )

            with open(self.log_file, "a") as f:
                f.write(summary)

            print(summary)

        except Exception as e:
            print(f"[ERR] Error in upload process: {str(e)}")
            import traceback
            print(traceback.format_exc())

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description='Upload malware samples to S3 and catalog them.')
    parser.add_argument('path', help='Path to file or directory to process')
    parser.add_argument('repository', help='Repository name')
    parser.add_argument('--notes', help='Optional notes to add to all files', default=None)
    
    args = parser.parse_args()
    
    uploader = S3Uploader(args.path, args.repository, args.notes)
    uploader.upload()