Neha Kumar

159 papers A* 37A 8B 4C 11Misc 1Journal 58Unranked 40
YearRankTypeTitle / Venue / Authors
2026 conf
CHI Extended Abstracts
Vishal Sharma, Hongjin Lin, Jasmine Lu, Han Qiao, Asra Sakeen Wani, Christina Bremer, Philip Engelbutzeder, Christoph Becker, Neha Kumar, Rikke Hagensby Jensen, Anupriya Tuli
2026 A* conf
CHI
Anupriya Tuli, Madeline Balaam, Pushpendra Singh, Neha Kumar, Airi Lampinen
2026 conf
CHI Extended Abstracts
Neelima Sailaja, Joel E. Fischer, Rishub Jain, Neha Kumar, Simone Stumpf, Min Kyung Lee, Heloisa Candello, Raquel Iniesta
2026 A* conf
CHI
Vishal Sharma, Anjali Karol Mohan, Neha Kumar
2025 J jnl
Proc. ACM Hum. Comput. Interact.
Karthik S. Bhat, Neha Madhiwalla, Neha Kumar
2025 A conf
ASSETS
Tamanna Motahar, Vaishnav Kameswaran, Sara Moin, Vikram Kamath Cannanure, Maitraye Das, Giulia Barbareschi, Laura Sanely Gaytán-Lugo, Aditya Vashistha, Kurtis Heimerl, Syed Ishtiaque Ahmed, Neha Kumar, Nova Ahmed, Maya Cakmak
2025 conf
CHI Extended Abstracts
Vishal Sharma, Hongjin Lin, Asra Sakeen Wani, Jared Lee Katzman, Anupriya Tuli, Naveena Karusala, Shaowen Bardzell, Christoph Becker, Martin Tomitsch, Neha Kumar
2025 conf
CHI Extended Abstracts
Sachin R. Pendse, Novia Wong, Munmun De Choudhury, Jaydon Farao, Neha Kumar, Nasalifya Namwinga, Giovanni Ramos, Asra Sakeen Wani, Jessica Lee Schleider, Madhu C. Reddy
2025 J jnl
XRDS
Vishal Sharma, Asra Sakeen Wani, Raphaël Marée, Christoph Becker, Douglas Schuler, Aparajita Marathe, Neha Kumar, Han Qiao, Rikke Hagensby Jensen, Anupriya Tuli
2025 A* conf
CHI
Aman Khullar, Nikhil Nalin, Abhishek Prasad, Ann John Mampilli, Neha Kumar
2025 J jnl
Interactions
Vishal Sharma, Bonnie A. Nardi, Neha Kumar
2025 A* conf
CHI
Vishal Sharma, Neha Kumar
2025 J jnl
CoRR
Sachin R. Pendse, Ben Rochford, Neha Kumar, Munmun De Choudhury
2025 J jnl
Proc. ACM Hum. Comput. Interact.
Sachin R. Pendse, Ben Rochford, Neha Kumar, Munmun De Choudhury
2025 J jnl
Proc. ACM Hum. Comput. Interact.
Inhwa Song, Sachin R. Pendse, Neha Kumar, Munmun De Choudhury
2025 conf
CHI Extended Abstracts
Ding Wang, Remi Denton, Anoop K. Sinha, Shruti Sheth, Lauren Wilcox, Maryam Mustafa, Motahhare Eslami, Ken Holstein, Maarten Sap, Angela D. R. Smith, Andrea G. Parker, Neha Kumar, Naveena Karusala, Tawanna R. Dillahunt, Jared Lee Katzman
2024 J jnl
CoRR
Sachin R. Pendse, Logan Stapleton, Neha Kumar, Munmun De Choudhury, Stevie Chancellor
2024 A conf
ASSETS
Laleh Nourian, Yulia Goldenberg, Muhammad Adamu, Vikram Kamath Cannanure, Catherine Holloway, Neha Kumar, Katharina Reinecke, Garreth W. Tigwell
2024 conf
CSCW Companion
Tamara L. Clegg, Tawanna R. Dillahunt, Sheena Erete, Oliver L. Haimson, Neha Kumar, Amanda Lazar, Yolanda A. Rankin, Katta Spiel
2024 J jnl
Interactions
Naveena Karusala, Neha Kumar
2024 conf
CSCW Companion
Vishal Sharma, Neha Kumar, Pejman Mirza-Babaei, Aakash Gautam, Cindy Lin, Volker Wulf, Nazanin Andalibi
2024 conf
CHI Extended Abstracts
Vishal Sharma, Anupriya Tuli, Asra Sakeen Wani, Anjali Karol Mohan, Bonnie A. Nardi, Marc Hassenzahl, Morgan Vigil-Hayes, Rikke Hagensby Jensen, Shaowen Bardzell, Neha Kumar
2024 J jnl
ACM Trans. Comput. Hum. Interact.
Vishal Sharma, Neha Kumar, Bonnie A. Nardi
2024 A* conf
CHI
Sachin R. Pendse, Neha Kumar, Munmun De Choudhury
2024 J jnl
CoRR
Inhwa Song, Sachin R. Pendse, Neha Kumar, Munmun De Choudhury
2024 J jnl
Proc. ACM Hum. Comput. Interact.
Vishal Sharma, Shivani A. Mehta, Neha Kumar, Aaditeshwar Seth
2023 A* conf
CHI
Anupriya Tuli, Azra Ismail, Karthik S. Bhat, Pushpendra Singh, Neha Kumar
2023 J jnl
Proc. ACM Hum. Comput. Interact.
Karthik S. Bhat, Amanda K. Hall, Tiffany Kuo, Neha Kumar
2023 J jnl
CoRR
Munmun De Choudhury, Sachin R. Pendse, Neha Kumar
2023 conf
CSCW Companion
Dipto Das, Parboti Roy, Carlos Toxtli, Kagonya Awori, Morgan Vigil-Hayes, Monojit Choudhury, Neha Kumar, Syed Ishtiaque Ahmed, Bryan C. Semaan
2023 conf
CHI Extended Abstracts
Vikram Kamath Cannanure, Delvin Varghese, Cuauhtémoc Rivera-Loaiza, Faria Noor, Dipto Das, Pranjal Jain, Meiyin Chang, Marisol Wong-Villacres, Naveena Karusala, Nova Ahmed, Sarina C. Till, Bernard Ijesunor Akhigbe, Melissa Densmore, Susan Dray, Christian Sturm, Neha Kumar
2023 A* conf
CHI
Vishal Sharma, Kirsten Bray, Neha Kumar, Rebecca E. Grinter
2023 A* conf
CHI
Hannah Tam, Karthik S. Bhat, Priyanka Mohindra, Neha Kumar
2023 J jnl
Proc. ACM Hum. Comput. Interact.
Sachin R. Pendse, Neha Kumar, Munmun De Choudhury
2023 J jnl
Proc. ACM Hum. Comput. Interact.
Azra Ismail, Divy Thakkar, Neha Madhiwalla, Neha Kumar
2023 conf
CHI Extended Abstracts
Reem Talhouk, Ebtisam Abdullah Alabdulqader, Cat Kutay, Kagonya Awori, Marisol Wong-Villacres, Neha Kumar, Tariq Zaman, Volker Wulf, Zainab AlMeraj, Shaimaa Lazem
2023 J jnl
Interactions
Neha Kumar, Andrew L. Kun
2023 conf
CSCW Companion
Adriana S. Vivacqua, Neha Kumar, Nicola J. Bidwell, Kagonya Awori, Kathrin Gerling, Dhruv Jain, Andrew L. Kun, Naomi Yamashita
2023 J jnl
Interactions
Luigi De Russis, Neha Kumar
2023 conf
CHI Extended Abstracts
Karthik S. Bhat, Azra Ismail, Amanda K. Hall, Naveena Karusala, Helena M. Mentis, John Vines, Neha Kumar
2023 A* conf
CHI
Karthik S. Bhat, Neha Kumar, Karthik Shamanna, Nipun Kwatra, Mohit Jain
2023 J jnl
IEEE Trans. Vis. Comput. Graph.
Yixuan Zhang, Yifan Sun, Joseph D. Gaggiano, Neha Kumar, Clio Andris, Andrea G. Parker
2023 J jnl
Interactions
Neha Kumar
2022 J jnl
Interactions
Neha Kumar, Julie A. Adams, Bill Buxton, Linda Candy, Pablo César, Leigh Clark, Benjamin R. Cowan, Anind K. Dey, Phoebe O. Toups Dugas, Ernest A. Edmonds, Michael A. Goodrich, Mark Green, Jonathan Grudin, Yoshifumi Kitamura, Joe Konstan, Celine Latulipe, Minha Lee, Tom Malone, Regan L. Mandryk, Panos Markopoulos, Michael J. Muller, Lennart E. Nacke, Yukiko I. Nakano, Marianna Obrist, Martin Porcheron, Aleksandra Sarcevic, Johannes Schöning, Stacey D. Scott, Bonita Sharif, Frank Steinicke, Simone Stumpf, Edward Tse, Vinoba Vinayagamoorthy
2022 J jnl
Interactions
Citational Justice Collective, Syed Ishtiaque Ahmed, Sareeta Amrute, Jeffrey Bardzell, Shaowen Bardzell, Nicola J. Bidwell, Tawanna Dillahunt, Sane Gaytán, Naveena Karusala, Neha Kumar, Rigoberto Lara Guzmán, Maryam Mustafa, Bonnie A. Nardi, Lisa P. Nathan, Nassim Parvin, Beth Patin, Pedro Reynolds-Cuéllar, Rebecca Rouse, Katta Spiel, Soraia Silva Prietch, Ding Wang, Marisol Wong-Villacrés
2022 J jnl
Interactions
Marisol Wong-Villacres, Sheena Erete, Aakash Gautam, Azra Ismail, Neha Kumar, Lucy Pei, Wendy Roldan, Veronica Ahumada Newhart, Karla A. Badillo-Urquiola, J. Maya Hernandez, Anthony Poon, Pedro Reynolds-Cuéllar, Vivian Genaro Motti
2022 conf
CHI Extended Abstracts
SIGCHI Executive Committee, Adriana S. Vivacqua, Andrew L. Kun, Cale J. Passmore, Helena M. Mentis, Josh Andres, Kashyap Todi, Matt Jones, Luigi De Russis, Naomi Yamashita, Neha Kumar, Nicola J. Bidwell, Pejman Mirza-Babaei, Priya C. Kumar, Shaowen Bardzell, Simone Kriglstein, Susan M. Dray, Susanne Boll, Stacy M. Branham, Tamara L. Clegg
2022 conf
CHI Extended Abstracts
SIGCHI Executive Committee, Adriana S. Vivacqua, Andrew L. Kun, Cale J. Passmore, Helena M. Mentis, Josh Andres, Kashyap Todi, Luigi De Russis, Matt Jones, Naomi Yamashita, Neha Kumar, Nicola J. Bidwell, Pejman Mirza-Babaei, Priya C. Kumar, Shaowen Bardzell, Simone Kriglstein, Stacy M. Branham, Susan Dray, Susanne Boll, Tamara L. Clegg
2022 A* conf
CHI
Sachin R. Pendse, Daniel Nkemelu, Nicola J. Bidwell, Sushrut Jadhav, Soumitra Pathare, Munmun De Choudhury, Neha Kumar
2022 conf
CHI Extended Abstracts
Vikram Kamath Cannanure, Naveena Karusala, Cuauhtémoc Rivera-Loaiza, Annu Sible Prabhakar, Rama Adithya Varanasi, Anupriya Tuli, Dilrukshi Gamage, Faria Noor, David Nemer, Dipto Das, Susan M. Dray, Christian Sturm, Neha Kumar
2022 conf
CHI Extended Abstracts
CHIWORK Collective, Naveena Karusala, Nabil Al Nahin Ch, Diana Tosca, Alberta A. Ansah, Emeline Brulé, Nadia Fereydooni, Le-En Huang, Azra Ismail, Pranjal Jain, Yi Xuan Khoo, Isabel Muñoz, Clemens Schartmüller, Himanshu Verma, Preeti Vyas, Susanne C. J. Boll, Sarah E. Fox, Noopur Raval, Max L. Wilson, Anna L. Cox, Christian P. Janssen, Helena M. Mentis, Neha Kumar, Orit Shaer, Andrew L. Kun
2022 J jnl
Proc. ACM Hum. Comput. Interact.
Azra Ismail, Deepika Yadav, Meghna Gupta, Kirti Dabas, Pushpendra Singh, Neha Kumar
2022 conf
CSCW Companion
Dilisha Patel, Sachin R. Pendse, Munmun De Choudhury, Sarah Dsane, Kaylee Payne Kruzan, Neha Kumar, Aneesha Singh, Mark Warner
2022 A* conf
CHI
Georgianna E. Lin, Elizabeth D. Mynatt, Neha Kumar
2022 A* conf
CHI
Anupriya Tuli, Surbhi Singh, Rikita Narula, Neha Kumar, Pushpendra Singh
2022 J jnl
Proc. ACM Hum. Comput. Interact.
Vishal Sharma, Kirsten E. Bray, Neha Kumar, Rebecca E. Grinter
2022 conf
CHI Extended Abstracts
SIGCHI Executive Committee, Adriana S. Vivacqua, Andrew L. Kun, Cale J. Passmore, Helena M. Mentis, Josh Andres, Kashyap Todi, Luigi De Russis, Matt Jones, Naomi Yamashita, Neha Kumar, Nicola J. Bidwell, Pejman Mirza-Babaei, Priya C. Kumar, Shaowen Bardzell, Simone Kriglstein, Stacy M. Branham, Susan M. Dray, Susanne Boll, Tamara L. Clegg
2022 C conf
ICTD
Gauri Bhatnagar, Pushpendra Singh, Neha Kumar, Anupriya Tuli
2022 J jnl
CoRR
Yixuan Zhang, Yifan Sun, Joseph D. Gaggiano, Neha Kumar, Clio Andris, Andrea G. Parker
2022 A* conf
CHI
Divy Thakkar, Azra Ismail, Pratyush Kumar, Alex Hanna, Nithya Sambasivan, Neha Kumar
2021 A* conf
CHI
Sachin R. Pendse, Amit Sharma, Aditya Vashistha, Munmun De Choudhury, Neha Kumar
2021 A* conf
CHI
Azra Ismail, Neha Kumar
2021 conf
CHI Extended Abstracts
Neha Kumar, Naveena Karusala
2021 conf
CHI Extended Abstracts
Miriam Sturdee, Joseph Lindley, Conor Linehan, Chris Elsden, Neha Kumar, Tawanna Dillahunt, Regan L. Mandryk, John Vines
2021 conf
CHI Extended Abstracts
Vikram Kamath Cannanure, Dilrukshi Gamage, Christian Sturm, Heike Winschiers-Theophilus, Juan Fernando Maestre, Naveena Karusala, Pedro Reynolds-Cuéllar, Neha Kumar
2021 J jnl
Interactions
Quincy Brown, Neha Kumar, Jakita Owensby Thomas, Alexandra To, Yolanda A. Rankin
2021 J jnl
Interactions
Jakita Owensby Thomas, Neha Kumar, Alexandra To, Quincy Brown, Yolanda A. Rankin
2021 conf
CSCW Companion
Citational Justice Collective, Gabriela Molina León, Lynn Kirabo, Marisol Wong-Villacres, Naveena Karusala, Neha Kumar, Nicola J. Bidwell, Pedro Reynolds-Cuéllar, Pranjal Protim Borah, Radhika Garg, Sushil K. Oswal, Tya S. Chuanromanee, Vishal Sharma
2021 J jnl
Interactions
Neha Kumar, Eunice Ratna Sari, Adriana S. Vivacqua, Naveena Karusala, Vikram Kamath Cannanure, Luigi De Russis, Loren Terveen
2021 J jnl
Proc. ACM Hum. Comput. Interact.
Savanthi Murthy, Karthik S. Bhat, Sauvik Das, Neha Kumar
2021 J jnl
Proc. ACM Hum. Comput. Interact.
Karthik S. Bhat, Mohit Jain, Neha Kumar
2021 conf
CSCW Companion
Adriana S. Vivacqua, Naomi Yamashita, Shaowen Bardzell, Neha Kumar
2021 J jnl
Interactions
Andrew L. Kun, Neha Kumar, David A. Shamma
2021 conf
CSCW Companion
Naveena Karusala, Azra Ismail, Karthik S. Bhat, Aakash Gautam, Sachin R. Pendse, Neha Kumar, Richard Anderson, Madeline Balaam, Shaowen Bardzell, Nicola J. Bidwell, Melissa Densmore, Elizabeth Kaziunas, Anne Marie Piper, Noopur Raval, Pushpendra Singh, Austin Toombs, Nervo Verdezoto Dias, Ding Wang
2021 J jnl
Proc. ACM Hum. Comput. Interact.
Prerna Ravi, Azra Ismail, Neha Kumar
2020 A* conf
CHI
Sachin R. Pendse, Faisal M. Lalani, Munmun De Choudhury, Amit Sharma, Neha Kumar
2020 J jnl
Interactions
Divy Thakkar, Neha Kumar, Nithya Sambasivan
2020 A* conf
CHI
Marisol Wong-Villacres, Carl DiSalvo, Neha Kumar, Betsy DiSalvo
2020 B conf
AVI
Luigi De Russis, Neha Kumar, Akhil Mathur
2020 J jnl
Interactions
Neha Kumar
2020 conf
CSCW Companion
Marisol Wong-Villacres, Aakash Gautam, Wendy Roldan, Lucy Pei, Jessa Dickinson, Azra Ismail, Betsy DiSalvo, Neha Kumar, Tammy Clegg, Sheena Lewis Erete, Emily Roden, Nithya Sambasivan, Jason C. Yip
2020 conf
CHI Extended Abstracts
Neha Kumar, Vikram Kamath Cannanure, Dilrukshi Gamage, Annu Sible Prabhakar, Christian Sturm, Cuauhtémoc Rivera Loaiza, Dina Sabie, Md. Moinuddin Bhuiyan, Mario Alberto Moreno Rocha
2020 Misc conf
CHI PLAY
Neha Kumar
2020 A* conf
CHI
Anupriya Tuli, Shaan Chopra, Pushpendra Singh, Neha Kumar
2020 J jnl
Interactions
Helena M. Mentis, David Ayman Shamma, Andrew L. Kun, Neha Kumar, Julie R. Williamson, Regan L. Mandryk
2020 J jnl
Proc. ACM Hum. Comput. Interact.
Karthik S. Bhat, Neha Kumar
2020 J jnl
ACM Trans. Comput. Hum. Interact.
Neha Kumar, Naveena Karusala, Azra Ismail, Anupriya Tuli
2020 A* conf
CHI
Divy Thakkar, Neha Kumar, Nithya Sambasivan
2020 A* conf
CHI
Hayley I. Evans, Udaya Lakshmi, Hue Watson, Azra Ismail, Andrew M. Sherrill, Neha Kumar, Rosa I. Arriaga
2019 J jnl
ACM Trans. Comput. Hum. Interact.
Anupriya Tuli, Shruti Dalvi, Neha Kumar, Pushpendra Singh
2019 C conf
ICTD
Naveena Karusala, Neha Kumar, Rosa I. Arriaga
2019 C conf
ICTD
Neha Kumar, Marisol Wong-Villacres, Naveena Karusala, Aditya Vishwanath, Arkadeep Kumar, Azra Ismail
2019 conf
CHI Extended Abstracts
Manasee Narvilkar, Josiah Mangiameli, Adriana Alvarado Garcia, Azra Ismail, Daniel S. Schiff, Danielle Schechter, Jordan Chen, Karthik S. Bhat, Marisol Wong-Villacres, Anusha Vasudeva, Aparna Ramesh, Michaelanne Dye, Naveena Karusala, Pragati Singh, Savanthi Murthy, Shubhangi Gupta, Udaya Lakshmi, Neha Kumar
2019 C conf
ICTD
Marisol Wong-Villacres, Hayley I. Evans, Danielle Schechter, Betsy DiSalvo, Neha Kumar
2019 A* conf
CHI
Azra Ismail, Neha Kumar
2019 J jnl
Proc. ACM Hum. Comput. Interact.
Neha Kumar, Naveena Karusala, Azra Ismail, Marisol Wong-Villacres, Aditya Vishwanath
2019 A* conf
CHI
Aditya Vishwanath, Naveena Karusala, Marisol Wong-Villacres, Neha Kumar
2019 A* conf
CHI
Philippe Kimura-Thollander, Neha Kumar
2019 conf
CHI Extended Abstracts
Neha Kumar, Christian Sturm, Syed Ishtiaque Ahmed, Naveena Karusala, Marisol Wong-Villacres, Leonel Morales Díaz, Rita Orji, Michaelanne Dye, Nova Ahmed, Laura S. Gaytán-Lugo, Aditya Vashistha, David Nemer, Kurtis Heimerl, Susan M. Dray
2019 J jnl
Proc. ACM Hum. Comput. Interact.
Michaelanne Dye, David Nemer, Neha Kumar, Amy S. Bruckman
2019 J jnl
Interactions
Neha Kumar, Naveena Karusala
2019 J jnl
Proc. ACM Hum. Comput. Interact.
Maya Holikatti, Shagun Jhaver, Neha Kumar
2019 C conf
COMPASS
Sachin R. Pendse, Naveena Karusala, Divya Siddarth, Pattie Gonsalves, Seema Mehrotra, John A. Naslund, Mamta Sood, Neha Kumar, Amit Sharma
2019 C conf
ICTD
Neha Kumar, Rajesh Chandwani, Julie A. Kientz
2019 A conf
Conference on Designing Interactive Systems
Naveena Karusala, Apoorva Bhalla, Neha Kumar
2019 C ed.
ICTD
Rajesh Chandwani, Pushpendra Singh, Neha Kumar, Rajesh Veeraraghavan
2019 J jnl
Proc. ACM Hum. Comput. Interact.
Neha Kumar, Azra Ismail, Samyukta Manjayya Sherugar, Rajesh Chandwani
2019 A* conf
CHI
Marisol Wong-Villacres, Neha Kumar, Betsy DiSalvo
2019 J jnl
Proc. ACM Hum. Comput. Interact.
Marisol Wong-Villacres, Neha Kumar, Betsy DiSalvo
2019 J jnl
Interactions
Michaelanne Dye, David Nemer, Josiah Mangiameli, Amy S. Bruckman, Neha Kumar
2019 conf
CSCW Companion
Neha Kumar, Daniel A. Epstein, Catherine D'Ignazio, Amanda Lazar, Andrea G. Parker, Müge Haseki, Anupriya Tuli
2018 A* conf
CHI
Naveena Karusala, Aditya Vishwanath, Aditya Vashistha, Sunita Kumar, Neha Kumar
2018 J jnl
Proc. ACM Hum. Comput. Interact.
Azra Ismail, Naveena Karusala, Neha Kumar
2018 A conf
Conference on Designing Interactive Systems
Marisol Wong-Villacres, Arkadeep Kumar, Aditya Vishwanath, Naveena Karusala, Betsy DiSalvo, Neha Kumar
2018 A* conf
CHI
Michaelanne Dye, David Nemer, Josiah Mangiameli, Amy S. Bruckman, Neha Kumar
2018 J jnl
Proc. ACM Hum. Comput. Interact.
Azra Ismail, Neha Kumar
2018 conf
CHI Extended Abstracts
Neha Kumar, Kurtis Heimerl, David Nemer, Naveena Karusala, Aditya Vashistha, Susan M. Dray, Christian Sturm, Laura S. Gaytán-Lugo, Anicia Peters, Nova Ahmed, Nicola Dell, Jay Chen
2018 conf
CHI Extended Abstracts
Pamela J. Wisniewski, Neha Kumar, Christine Bassem, Sarah Clinch, Susan M. Dray, Geraldine Fitzpatrick, Cliff Lampe, Michael J. Muller, Anicia N. Peters
2018 J jnl
Interactions
Neha Kumar, Aditya Vishwanath
2018 C conf
COMPASS
Samia Ibtasam, Lubna Razaq, Haider W. Anwar, Hamid Mehmood, Kushal Shah, Jennifer R. Webster, Neha Kumar, Richard Anderson
2018 J jnl
Proc. ACM Hum. Comput. Interact.
Anupriya Tuli, Shaan Chopra, Neha Kumar, Pushpendra Singh
2018 conf
CHI Extended Abstracts
Sheelagh Carpendale, Shaowen Bardzell, Margaret M. Burnett, Neha Kumar, Madeline Balaam
2018 conf
CHI Extended Abstracts
Azalea Irani, Kriti Nelavelli, Kristin Hare, Paula Bondal, Neha Kumar
2018 conf
CSCW Companion
Michaelanne Dye, Neha Kumar, Ari Schlesinger, Marisol Wong-Villacres, Morgan G. Ames, Rajesh Veeraraghavan, Jacki O'Neill, Joyojeet Pal, Mary L. Gray
2018 A* conf
CHI
Rajesh Chandwani, Neha Kumar
2018 J jnl
Proc. ACM Hum. Comput. Interact.
Neha Kumar, Nicola Dell
2018 J jnl
Proc. ACM Hum. Comput. Interact.
Neha Kumar, Nassim Jafarinaimi, Mehrab Bin Morshed
2017 J jnl
Proc. ACM Hum. Comput. Interact.
Naveena Karusala, Aditya Vishwanath, Arkadeep Kumar, Aman Mangal, Neha Kumar
2017 J jnl
Interactions
Neha Kumar, Susan M. Dray
2017 A conf
Conference on Designing Interactive Systems
Aditya Vashistha, Neha Kumar, Anil Mishra, Richard Anderson
2017 A conf
Conference on Designing Interactive Systems
Aditya Vishwanath, Matthew Kam, Neha Kumar
2017 A* conf
CHI
Rui Zhou, Jasmine Hentschel, Neha Kumar
2017 A* conf
CHI
Michaelanne Dye, David Nemer, Laura R. Pina, Nithya Sambasivan, Amy S. Bruckman, Neha Kumar
2017 conf
CHI Extended Abstracts
Jofish Kaye, Casey Fiesler, Neha Kumar, Bryan C. Semaan
2017 A* conf
CHI
Jasmine Hentschel, Samyukta Manjayya Sherugar, Rui Zhou, Vaishnav Kameswaran, Rajesh Chandwani, Neha Kumar
2017 A* conf
CHI
Piya Sorcar, Benjamin Strauber, Prashant Loyalka, Neha Kumar, Shelley V. Goldman
2017 J jnl
Proc. ACM Hum. Comput. Interact.
Marisol Wong-Villacres, Cristina M. Velasquez, Neha Kumar
2017 J jnl
Interactions
Neha Kumar, Naveena Karusala, Aaditeshwar Seth, Biswajit Patra
2017 A conf
CSCW
Mehrab Bin Morshed, Michaelanne Dye, Syed Ishtiaque Ahmed, Neha Kumar
2017 A* conf
CHI
Naveena Karusala, Neha Kumar
2017 C conf
ICTD
Jasmine Hentschel, Syed Ishtiaque Ahmed, Faheem Hussain, Nova Ahmed, Neha Kumar
2016 conf
LIMITS
Syed Ishtiaque Ahmed, Nova Ahmed, Faheem Hussain, Neha Kumar
2016 conf
CHI Extended Abstracts
Neha Kumar, Susan M. Dray, Andy Dearden, Nicola Dell, Melissa Densmore, Rebecca E. Grinter, Zhengjie Liu, Mario Alberto Moreno Rocha, Anicia N. Peters, Eunice Ratna Sari, William Thies, Indrani Medhi-Thies, William D. Tucker, Elba del Carmen Valderrama Bahamóndez, Susan Wyche
2016 B conf
L@S
Aditya Vishwanath, Arkadeep Kumar, Neha Kumar
2016 C conf
ICTD
Aditya Vashistha, Neha Kumar, Anil Mishra, Richard Anderson
2016 A* conf
CHI
Nicola Dell, Neha Kumar
2015 conf
ACM DEV
Aditya Vishwanath, Neha Kumar
2015 A* conf
CHI
Neha Kumar, Richard J. Anderson
2015 A conf
CSCW
Nicola Dell, Trevor Perrier, Neha Kumar, Mitchell Lee, Rachel Powers, Gaetano Borriello
2015 C conf
ICTD
Neha Kumar, Trevor Perrier, Michelle Desmond, Kiersten Israel-Ballard, Vikrant Kumar, Sudip Mahapatra, Anil Mishra, Shreya Agarwal, Rikin Gandhi, Pallavi Lal, Richard Anderson
2015 J jnl
First Monday
Neha Kumar
2014 J jnl
New Media Soc.
Neha Kumar
2013 J jnl
Bell Labs Tech. J.
Neha Kumar, Akhil Mathur, Siddhartha Lal
2013 A* conf
CHI
Neha Kumar, Tapan S. Parikh
2013 A* conf
CHI
Neha Kumar, Nimmi Rangaswamy
2011 A* conf
CHI
Neha Kumar, Gopal Chouhan, Tapan S. Parikh
2010 conf
CHI Extended Abstracts
Neha Kumar, Tapan S. Parikh
2005 B conf
GLOBECOM
Shubha U. Nabar, Neha Kumar, Mohsen Bayati, Abtin Keshavarzian
2004 B conf
GLOBECOM
Neha Kumar, Rong Pan, Devavrat Shah
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()