Oleg Sergiyenko

96 papers C 19Journal 29Unranked 48
YearRankTypeTitle / Venue / Authors
2025 J jnl
Biomed. Signal Process. Control.
Julio C. Rodríguez-Quiñonez, Gabriel Trujillo-Hernández, Wendy Flores-Fuentes, Moisés Jesús Castro-Toscano, Oleg Sergiyenko, Daniel Hernández-Balbuena, Fabian Natanael Murrieta-Rico
2025 J jnl
Entropy
Eder Alejandro Rodríguez Martínez, Jesús Elías Miranda-Vega, Farouk Achakir, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena, Wendy Flores-Fuentes
2025 J jnl
Integr.
Ruben Alaniz-Plata, Fernando Lopez-Medina, Oleg Sergiyenko, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, César A. Sepúlveda-Valdez, José A. Núñez-López, David Meza-García, José Fabián Villa-Manríquez, Humberto Andrade-Collazo, Vera Tyrsa
2024 J jnl
Entropy
Ivan Yeniseysk Alba Corpus, Wendy Flores-Fuentes, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Jesús Elías Miranda-Vega, Wendy Garcia-Gonzalez, José A. Núñez-López
2024 J jnl
IEEE Trans. Instrum. Meas.
Julio C. Rodríguez-Quiñonez, Gabriel Trujillo-Hernández, Wendy Flores-Fuentes, Oleg Sergiyenko, Jesús Elías Miranda-Vega, Jonathan J. Sanchez-Castro, Moisés Jesús Castro-Toscano, Oscar Real-Moreno
2024 J jnl
Entropy
Wendy Flores-Fuentes, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Jesús Elías Miranda-Vega
2024 C conf
IECON
José A. Núñez-López, David Meza-García, Oleg Sergiyenko, Ruben Alaniz-Plata, César A. Sepúlveda-Valdez, José Fabián Villa-Manríquez, Fernando Lopez-Medina, Dennis Molina-Quiroz, Vera Tyrsa, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Fabian Natanael Murrieta-Rico, Paolo Mercorelli
2024 C conf
IECON
José Fabián Villa-Manríquez, Oleg Sergiyenko, César A. Sepúlveda-Valdez, Ruben Alaniz-Plata, José A. Núñez-López, Paolo Mercorelli, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Vera Tyrsa, David Meza-García, Fernando Lopez-Medina, Humberto Andrade-Collazo, Moisés Jesús Castro-Toscano
2024 C conf
IECON
Ruben Alaniz-Plata, Fernando Lopez-Medina, Oleg Sergiyenko, José A. Núñez-López, César A. Sepúlveda-Valdez, David Meza-García, José Fabián Villa-Manríquez, Humberto Andrade-Collazo, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Vera Tyrsa, Moisés Jesús Castro-Toscano, Paolo Mercorelli
2024 J jnl
IEEE Trans. Instrum. Meas.
Julio Cesar Rodríguez-Quiñonez, Leonardo D. Medina-Madrazo, Moisés Jesús Castro-Toscano, Wendy Flores-Fuentes, Oleg Sergiyenko, Daniel Hernández-Balbuena, Gabriel Trujillo-Hernández
2024 conf
ISIE
Andrè I. Herrera-Chavez, Eder Alejandro Rodríguez Martínez, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Juan C. García-Gallegos, Oscar H. Montiel-Ross, Félix Fernando González-Navarro, Oleg Sergiyenko
2023 C conf
IECON
José A. Núñez-López, Oleg Sergiyenko, Ruben Alaniz-Plata, César A. Sepúlveda-Valdez, Oscar M. Pérez Landeros, Vera Tyrsa, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Fabian Natanael Murrieta-Rico, Paolo Mercorelli, Vladimir M. Kartashov, Marina Kolendovska
2023 conf
ENC
Fabian Natanael Murrieta-Rico, Elia Ivette Cota Rivera, María E. Raygoza-L, Roxana Jiménez Sanchez, Guillermo M. Limon-Molina, Jesús Heriberto Orduño-Osuna, Juan Carlos Ling, Abelardo Mercado-Herrera, Oleg Sergiyenko
2023 conf
ISIE
Luis A. Frem-Sosa, Monica Valenzuela-Delgado, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Oleg Sergiyenko, Daniel Hernández-Balbuena, Fabian Natanael Murrieta-Rico, Jesús Elías Miranda-Vega, Paolo Mercorelli
2023 C conf
IECON
César A. Sepúlveda-Valdez, Oleg Sergiyenko, Ruben Alaniz-Plata, José A. Núñez-López, Vera Tyrsa, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Paolo Mercorelli, Marina Kolendovska, Vladimir M. Kartashov, Jesús Elías Miranda-Vega, Fabian Natanael Murrieta-Rico
2023 conf
ISIE
Leonardo D. Medina-Madrazo, Julio C. Rodríguez-Quiñonez, Oscar Real-Moreno, Dayanna Ortiz-Villaseñor, Wendy Flores-Fuentes, Oleg Sergiyenko, Moisés Jesús Castro-Toscano, Paolo Mercorelli
2023 J jnl
Entropy
Wendy Garcia-Gonzalez, Wendy Flores-Fuentes, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Jesús Elías Miranda-Vega, Daniel Hernández-Balbuena
2023 J jnl
Symmetry
Oleg Sergiyenko, Alexey N. Zhirabok, Ibrahim A. Hameed, Ahmad Taher Azar, Alexander V. Zuev, Vladimir F. Filaretov, Vera Tyrsa, Ibraheem Kasim Ibraheem
2022 C conf
IECON
Oscar Real-Moreno, Julio C. Rodríguez-Quiñonez, Oleg Sergiyenko, Wendy Flores-Fuentes, Moisés Jesús Castro-Toscano, Jesús Elías Miranda-Vega, Paolo Mercorelli, Jorge Alejandro Valdez-Rodríguez, Gabriel Trujillo-Hernández, Jonathan J. Sanchez-Castro
2022 J jnl
Int. J. Distributed Sens. Networks
Wendy Flores-Fuentes, Ivan Yeniseysk Alba Corpus, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez
2022 C conf
IECON
Jesus O. Santos-Sanchez, Mauricio A. Rojas-Casas, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Wendy Flores-Fuentes, César A. Sepúlveda-Valdez, Ruben Alaniz-Plata, Vera Tyrsa, Paolo Mercorelli
2022 conf
ENC
Ivan Yeniseysk Alba Corpus, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena, Félix Fernando González-Navarro, Oleg Sergiyenko, Ruben Alaniz-Plata
2022 conf
ISIE
Fabian Natanael Murrieta-Rico, Paolo Mercorelli, Julio C. Rodríguez-Quiñonez, Wendy Flores-Fuentes, Oleg Sergiyenko, Moises Rivas-López, Abelardo Mercado-Herrera, Araceli Gárate-García, Vera Tyrsa
2022 J jnl
Autom. Remote. Control.
Alexey N. Zhirabok, Alexander V. Zuev, Oleg Sergiyenko, Alexey E. Shumsky
2022 J jnl
Symmetry
Oleg Sergiyenko, Alexey N. Zhirabok, Ibraheem Kasim Ibraheem, Alexander V. Zuev, Vladimir F. Filaretov, Ahmad Taher Azar, Ibrahim A. Hameed
2022 conf
ISIE
Xavier de J. García-Gutierrez, Ruben Alaniz-Plata, Gabriel Trujillo-Hernández, Ivan Yeniseysk Alba Corpus, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena, Oleg Sergiyenko, Félix F. González-Navarro, Paolo Mercorelli, Jesús Elías Miranda-Vega, Fabian Natanael Murrieta-Rico
2021 conf
ISIE
Wendy Garcia-Gonzalez, Wendy Flores-Fuentes, Oleg Sergiyenko, Julio Cesar Rodríguez-Quiñonez, Jesús Elías Miranda-Vega, Arnoldo Díaz-Ramírez, Alejandra Flores-Buruel
2021 conf
ISIE
Oscar Real-Moreno, Julio C. Rodríguez-Quiñonez, Oleg Sergiyenko, Wendy Flores-Fuentes, Paolo Mercorelli, Luis R. Ramírez-Hernández
2021 C conf
IECON
José A. Núñez-López, Lars Lindner, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Wendy Flores-Fuentes, Paolo Mercorelli
2021 conf
ISIE
Mikhail V. Ivanov, Oleg Sergiyenko, Lars Lindner, Paolo Mercorelli, Julio Cesar Rodríguez-Quiñonez, Wendy Flores-Fuentes
2021 J jnl
J. Autom. Mob. Robotics Intell. Syst.
Katharina Benz, Claus Rech, Paolo Mercorelli, Oleg Sergiyenko
2021 conf
ISIE
Fabian Natanael Murrieta-Rico, Lars Lindner, Vitalii Petranovskii, Donald H. Galván, Joel Antúnez-Garcia, Rosario I. Yocupicio-Gaxiola, Oleg Sergiyenko, Moises Rivas-López
2020 conf
ISIE
Jonathan J. Sanchez-Castro, Julio C. Rodríguez-Quiñonez, Luis R. Ramírez-Hernández, Guillermo Galaviz, Daniel Hernández-Balbuena, Gabriel Trujillo-Hernández, Wendy Flores-Fuentes, Paolo Mercorelli, Wilmar Hernández-Perdomo, Oleg Sergiyenko, Félix Fernando González-Navarro
2020 conf
ISIE
Wilmar Hernandez, Alfredo Méndez, Omar Flor-Unda, Vicente González Posadas, José Luis Jiménez Martin, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Mykhailo Ivanov, Ivan Menes Camejo, Marina Kolendovska
2020 conf
ISIE
Cuauhtémoc Mariscal-García, Wendy Flores-Fuentes, Daniel Hernández-Balbuena, Julio C. Rodríguez-Quiñonez, Oleg Sergiyenko, Félix Fernando González-Navarro, Jesús Elías Miranda-Vega
2020 conf
ISIE
César A. Sepúlveda-Valdez, Oleg Sergiyenko, Vera Tyrsa, Wendy Flores-Fuentes, Julio Cesar Rodríguez-Quiñonez, Fabian Natanael Murrieta-Rico, Jesús Elías Miranda-Vega, Paolo Mercorelli, Marina Kolendovska
2020 conf
ISIE
Fabian Natanael Murrieta-Rico, Vitalii Petranovskii, Donald H. Galván, Oleg Sergiyenko, Joel Antúnez-Garcia, Rosario I. Yocupicio-Gaxiola, Juan De Dios Sánchez-López
2020 conf
ISIE
Valerii Semenets, Vladimir M. Kartashov, Oleg Sergiyenko, Vyacheslav Tikhonov, Paolo Mercorelli, Sergiy Sheiko, Nataliya Chmelarova Kudriavtseva, Julio C. Rodríguez-Quiñonez, Wendy Flores-Fuentes
2020 conf
ISIE
Ivan Yeniseysk Alba Corpus, Lars Lindner, Oleg Sergiyenko, Mykhailo Ivanov, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Daniel Hernandez Balbuena, Fabian Natanael Murrieta-Rico, Alexander Gurko
2019 C conf
IECON
Gabriel Trujillo-Hernández, Julio C. Rodríguez-Quiñonez, Luis R. Ramírez-Hernández, Moisés Jesús Castro-Toscano, Daniel Hernández-Balbuena, Wendy Flores-Fuentes, Oleg Sergiyenko, Lars Lindner, Paolo Mercorelli
2019 C conf
IECON
Wendy Flores-Fuentes, Monica Valenzuela-Delgado, Félix Fernando González-Navarro, Danilo Cáceres Hernández, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Moises Rivas-López, Miguel Enrique Bravo-Zanoguera, Daniel Hernandez Balbuena
2019 conf
ISIE
Fabian Natanael Murrieta-Rico, Vitalii Petranovskii, Juan De Dios Sánchez-López, Juan I. Nieto-Hipólito, Mabel Vázquez-Briseño, Oleg Sergiyenko, Daniel Hernandez Balbuena, Oleg Zabula
2019 conf
ISIE
Oleksandr Poliarus, Yevhen Poliakov, Oleg Sergiyenko, Vera Tyrsa, Wilmar Hernandez, Julia Nechitailo
2019 conf
ISIE
César A. Sepúlveda-Valdez, Oleg Sergiyenko, Daniel Hernandez Balbuena, Vera Tyrsa, Paolo Mercorelli, Wendy Flores-Fuentes, Miguel Reyes-García, Lars Lindner, Viktor Melnik
2019 conf
ISIE
Miguel Reyes-García, Oleg Sergiyenko, Mykhailo Ivanov, Lars Lindner, Julio C. Rodríguez-Quiñonez, Daniel Hernandez Balbuena, Wendy Flores-Fuentes, Vera Tyrsa, Luis Omar Moreno-Ahedo, Fabian Natanael Murrieta-Rico
2019 conf
ISIE
Mykhailo Ivanov, Oleg Sergiyenko, Paolo Mercorelli, Wilmar Hernandez, Vera Tyrsa, Daniel Hernandez Balbuena, Julio Cesar Rodríguez-Quiñonez, Vladimir M. Kartashov, Marina Kolendovska, Iryna S. Tabakova
2019 C conf
IECON
Fabian Natanael Murrieta-Rico, Vitalii Petranovskii, Oleg Sergiyenko, Paolo Mercorelli, Joel Antúnez-Garcia, Juan De Dios Sánchez-López, Rosario I. Yocupicio-Gaxiola
2019 J jnl
Program. Comput. Softw.
Mikhail V. Ivanov, Oleg Sergiyenko, Vera Tyrsa, Lars Lindner, Julio Cesar Rodríguez-Quiñonez, Wendy Flores-Fuentes, Moises Rivas-López, Daniel Hernández-Balbuena, Juan Ivan Nieto-Hipólito
2018 C conf
IECON
Daniel Avalos-Gonzalez, Daniel Hernandez Balbuena, Vera Tyrsa, Vladimir M. Kartashov, Marina Kolendovska, Sergiy Sheiko, Oleg Sergiyenko, Viktor Melnyk, Fabian Natanael Murrieta-Rico
2018 J jnl
Sensors
Wendy Flores-Fuentes, Jesús Elías Miranda-Vega, Moises Rivas-López, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Lars Lindner
2018 J jnl
Sensors
Monica Valenzuela-Delgado, Wendy Flores-Fuentes, Moises Rivas-López, Oleg Sergiyenko, Lars Lindner, Daniel Hernández-Balbuena, Julio C. Rodríguez-Quiñonez
2018 C conf
IECON
Mykhailo Ivanov, Oleg Sergiyenko, Vera Tyrsa, Paolo Mercorelli, Vladimir M. Kartashov, Wilmar Hernandez, Sergiy Sheiko, Marina Kolendovska
2018 J jnl
J. Sensors
Oleg Sergiyenko, Vera Tyrsa, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Paolo Mercorelli
2018 J jnl
Microprocess. Microsystems
Jesús Elías Miranda-Vega, Wendy Flores-Fuentes, Oleg Sergiyenko, Moises Rivas-López, Lars Lindner, Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena
2017 conf
AFRICON
Paolo Mercorelli, Theo Van Niekerk, Oleg Sergiyenko
2017 conf
ISIE
Moisés Jesús Castro-Toscano, Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena, Lars Lindner, Oleg Sergiyenko, Moises Rivas-López, Wendy Flores-Fuentes
2017 conf
ISIE
Oscar Real-Moreno, Julio C. Rodríguez-Quiñonez, Oleg Sergiyenko, Luis C. Basaca-Preciado, Daniel Hernández-Balbuena, Moises Rivas-López, Wendy Flores-Fuentes
2017 conf
CARLA
Luis-Angel Galaviz-Alejos, Fermin-Alberto Armenta-Cano, Andrei Tchernykh, Gleb I. Radchenko, Alexander Yu. Drozdov, Oleg Sergiyenko, Ramin Yahyapour
2017 J jnl
Ind. Robot
Lars Lindner, Oleg Sergiyenko, Moises Rivas-López, Daniel Hernández-Balbuena, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Fabian Natanael Murrieta-Rico, Mykhailo Ivanov, Vera Tyrsa, Luis Basaca-Preciado
2017 conf
ISIE
Lars Lindner, Oleg Sergiyenko, Moises Rivas-López, Mykhailo Ivanov, Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena, Wendy Flores-Fuentes, Vera Tyrsa, Fabian Natanael Murrieta-Rico, Paolo Mercorelli
2017 conf
ISIE
Monica Valenzuela-Delgado, Wendy Flores-Fuentes, Miguel Enrique Bravo-Zanoguera, Alejandro S. Ortiz-Perez, Daniel Hernández-Balbuena, Moises Rivas-López, Oleg Sergiyenko, Félix F. González-Navarro
2017 conf
ISIE
Paolo Mercorelli, Thomas Voß, Daniel Strassberger, Oleg Sergiyenko, Lars Lindner
2017 conf
ISIE
Wendy Flores-Fuentes, Daniel Hernández-Balbuena, Julio C. Rodríguez-Quiñonez, Moises Rivas-López, Oleg Sergiyenko, Lars Lindner, Félix F. González-Navarro, Jesús Elías Miranda-Vega
2016 J jnl
Robotics Auton. Syst.
Oleg Sergiyenko, Mikhail V. Ivanov, Vera Tyrsa, Vladimir M. Kartashov, Moises Rivas-López, Daniel Hernández-Balbuena, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Juan I. Nieto-Hipólito, Wilmar Hernandez, Andrei Tchernykh
2016 conf
ISIE
Fabian Natanael Murrieta-Rico, Daniel Hernandez Balbuena, Julio C. Rodríguez-Quiñonez, Paolo Mercorelli, Vitalii Petranovskii, Oscar Raymond-Herrera, Juan I. Nieto-Hipólito, Vera Tyrsa, Oleg Sergiyenko, Lars Lindner, Wilmar Hernandez
2016 conf
ISIE
Lars Lindner, Oleg Sergiyenko, Moises Rivas-López, Julio C. Rodríguez-Quiñonez, Daniel Hernandez Balbuena, Wendy Flores-Fuentes, Vera Tyrsa, Juan I. Nieto-Hipólito, Fabian Natanael Murrieta-Rico, Vladimir M. Kartashov
2016 J jnl
Ind. Robot
Lars Lindner, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Moises Rivas-López, Daniel Hernandez Balbuena, Wendy Flores-Fuentes, Fabian Natanael Murrieta-Rico, Vera Tyrsa
2016 conf
ISIE
Wendy Flores-Fuentes, Moises Rivas-López, Daniel Hernandez Balbuena, Julio C. Rodríguez-Quiñonez, Eduardo M. Hoyos-Perales, Javier Rivera-Castillo, Oleg Sergiyenko, Juan Ivan Nieto-Hipólito, Fabian Natanael Murrieta-Rico, Wilmar Hernandez
2016 C conf
IECON
Fabian Natanael Murrieta-Rico, Daniel Hernandez Balbuena, Julio C. Rodríguez-Quiñonez, Vitalii Petranovskii, Oscar Raymond-Herrera, Alexander G. Gurko, Paolo Mercorelli, Oleg Sergiyenko, Lars Lindner, Benjamín Valdez-Salas, Vera Tyrsa
2016 conf
ISIE
Oleg Sergiyenko, Vladimir M. Kartashov, Mikhail V. Ivanov, Daniel Hernández-Balbuena, Vera Tyrsa, Juan I. Nieto-Hipólito
2016 C conf
IECON
Lars Lindner, Oleg Sergiyenko, Moises Rivas-López, Benjamín Valdez-Salas, Julio C. Rodríguez-Quiñonez, Daniel Hernandez Balbuena, Wendy Flores-Fuentes, Vera Tyrsa, Misael Medina Barrera, Fabian Natanael Murrieta-Rico, Paolo Mercorelli, Alexander Gurko
2015 conf
ISIE
Lars Lindner, Oleg Sergiyenko, Julio C. Rodríguez-Quiñonez, Vera Tyrsa, Paolo Mercorelli, Wendy Flores-Fuentes, Fabian Natanael Murrieta-Rico, Juan Ivan Nieto-Hipólito
2015 conf
ICINCO (2)
Alexander Gurko, Oleg Sergiyenko, Juan Ivan Nieto-Hipólito, Igor Kirichenko, Vera Tyrsa, Juan-de-Dios Sánchez-López
2015 conf
ISIE
Fabian Natanael Murrieta-Rico, Daniel Hernandez Balbuena, Julio C. Rodríguez-Quiñonez, Vitalii Petranovskii, Oscar Raymond-Herrera, Juan Ivan Nieto-Hipólito, Oleg Sergiyenko, Lars Lindner, Vera Tyrsa, Viktor Melnyk
2015 C conf
IECON
Wendy Flores-Fuentes, Daniel Hernandez Balbuena, Julio C. Rodríguez-Quiñonez, Daniel Olivas-Ugalde, Félix Fernando González-Navarro, Oleg Sergiyenko, Moises Rivas-López, Fabian Natanael Murrieta-Rico, Vladimir M. Kartashov
2015 conf
ISIE
Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Daniel Hernandez Balbuena, Moises Rivas-López, Oleg Sergiyenko, Javier Rivera-Castillo, Lars Lindner, Luis C. Basaca-Preciado, Pedro Mayorga-Ortiz
2015 C conf
IECON
Fabian Natanael Murrieta-Rico, Andrei Tchernykh, Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena, Vitalii Petranovskii, Oscar Raymond-Herrera, Juan Ivan Nieto-Hipólito, Vladimir M. Kartashov, Oleg Sergiyenko, Wendy Flores-Fuentes, Vera Tyrsa
2015 conf
ICINCO (Selected Papers)
Alexander Gurko, Oleg Sergiyenko, Juan Ivan Nieto-Hipólito, Igor Kirichenko, Vera Tyrsa, Wilmar Hernandez
2015 conf
ISIE
Moises Rivas-López, Carlos A. Gomez-Sanchez, Javier Rivera-Castillo, Oleg Sergiyenko, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Pedro Mayorga-Ortiz
2014 conf
ISIE
Fabian Natanael Murrieta-Rico, Daniel Hernandez Balbuena, Vitalii Petranovskii, Juan Ivan Nieto-Hipólito, Alexey Pestryakov, Oleg Sergiyenko, Mayra Molina, Vera Tyrsa
2014 C conf
IECON
Luis C. Basaca-Preciado, Julio C. Rodríguez-Quiñonez, Oleg Sergiyenko, Wendy Flores-Fuentes, Paolo Mercorelli, Fabian Natanael Murrieta-Rico
2014 C conf
IECON
Paolo Mercorelli, Nils Werner, Oleg Sergiyenko
2014 conf
ISIE
Lars Lindner, Oleg Sergiyenko, Vera Tyrsa, Paolo Mercorelli
2014 J jnl
Signal Process.
Wendy Flores-Fuentes, Moises Rivas-López, Oleg Sergiyenko, Félix F. González-Navarro, Javier Rivera-Castillo, Daniel Hernandez Balbuena, Julio C. Rodríguez-Quiñonez
2014 conf
ISIE
Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena, Oleg Sergiyenko, Moises Rivas-López, Wendy Flores-Fuentes, Luis C. Basaca-Preciado
2014 conf
ISIE
Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Daniel Hernández-Balbuena, Moises Rivas-López, Oleg Sergiyenko, Félix Fernando González-Navarro, Javier Rivera-Castillo
2014 conf
ISIE
Moises Rivas-López, Oleg Sergiyenko, Javier Rivera-Castillo, Wendy Flores-Fuentes, Julio C. Rodríguez-Quiñonez, Daniel Hernandez Balbuena, Jorge L. Taddei-Bringas
2014 conf
ISIE
Javier Rivera-Castillo, Moises Rivas-López, Juan I. Nieto-Hipólito, Oleg Sergiyenko, Wendy Flores-Fuentes, Daniel Hernandez Balbuena, Julio C. Rodríguez-Quiñonez, Jesus A. Platt-Carrillo
2013 J jnl
Signal Process.
Julio C. Rodríguez-Quiñonez, Oleg Sergiyenko, Félix F. González-Navarro, Luis Basaca-Preciado, Vera Tyrsa
2012 conf
ICINCO (2)
Alexander Gurko, Wilmar Hernandez, Oleg Sergiyenko, Vera Tyrsa, Juan I. Nieto-Hipólito, Daniel Hernandez Balbuena, Paolo Mercorelli
2011 J jnl
Computación y Sistemas
Oleg Starostenko, Renan Contreras, Vicente Alarcón Aquino, Leticia Flores-Pulido, Jorge Rodríguez-Asomoza, Oleg Sergiyenko, Vera Tyrsa
2010 J jnl
Int. J. Mob. Learn. Organisation
Oleg Starostenko, Vicente Alarcón Aquino, Humberto Lobato-Morales, Oleg Sergiyenko
2010 J jnl
Sensors
Wilmar Hernandez, Jesús de Vicente, Oleg Sergiyenko, Eduardo Fernández
2010 J jnl
Sensors
Wilmar Hernandez, Jesús de Vicente, Oleg Sergiyenko, Eduardo Fernández
2009 J jnl
Sensors
Oleg Starostenko, Vicente Alarcón Aquino, Wilmar Hernandez, Oleg Sergiyenko, Vera Tyrsa
2009 J jnl
Sensors
Oleg Sergiyenko, Wilmar Hernandez, Vera Tyrsa, Luis Felipe Devia Cruz, Oleg Starostenko, Mario Peña-Cabrera
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()