Skip to content

Commit

Permalink
إضافة مكتبة خادم الويب للتعامل مع ملفات الزكاة - بدون اختبار
Browse files Browse the repository at this point in the history
  • Loading branch information
vzool committed Jul 7, 2024
1 parent cd2e26f commit 30b2f2a
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 31 deletions.
Binary file added images/zakat-social-media-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/zakat-social-media-preview.xcf
Binary file not shown.
Empty file added tests/test_file_server.py
Empty file.
7 changes: 6 additions & 1 deletion zakat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@
This file provides the ZakatLibrary classes, functions for tracking and calculating Zakat.
"""
# Importing necessary classes and functions from the main module
from .zakat_tracker import (
from zakat.zakat_tracker import (
ZakatTracker,
Action,
JSONEncoder,
MathOperation,
)

from zakat.file_server import (
start_file_server,
)

# Version information for the module
__version__ = ZakatTracker.Version()
__all__ = [
"ZakatTracker",
"Action",
"JSONEncoder",
"MathOperation",
"start_file_server",
]
124 changes: 104 additions & 20 deletions zakat/file_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import os
import uuid
import cgi
from enum import Enum
from zakat_tracker import ZakatTracker, Action
from enum import Enum, auto
import shutil
import json

Expand All @@ -15,14 +14,79 @@ class FileType(Enum):
CSV = 'csv'


def find_available_port():
# SAFE Circular Imports (Duplicated class again)
class Action(Enum):
CREATE = auto()
TRACK = auto()
LOG = auto()
SUB = auto()
ADD_FILE = auto()
REMOVE_FILE = auto()
BOX_TRANSFER = auto()
EXCHANGE = auto()
REPORT = auto()
ZAKAT = auto()


def find_available_port() -> int:
"""
Finds and returns an available TCP port on the local machine.
This function utilizes a TCP server socket to bind to port 0, which
instructs the operating system to automatically assign an available
port. The assigned port is then extracted and returned.
Returns:
int: The available TCP port number.
Raises:
OSError: If an error occurs during the port binding process, such
as all ports being in use.
Example:
port = find_available_port()
print(f"Available port: {port}")
"""
with socketserver.TCPServer(("localhost", 0), None) as s:
return s.server_address[1]


def start_file_server(database_path: str, debug: bool = False):
def start_file_server(database_path: str, database_callback: callable = None, csv_callback: callable = None,
debug: bool = False) -> tuple:
"""
Starts an HTTP server to serve a file (GET) and accept file uploads (POST) with UUID-based URIs.
Starts a multi-purpose HTTP server to manage file interactions for a Zakat application.
This server facilitates the following functionalities:
1. GET /{file_uuid}/get: Download the database file specified by `database_path`.
2. GET /{file_uuid}/upload: Display an HTML form for uploading files.
3. POST /{file_uuid}/upload: Handle file uploads, distinguishing between:
- Database File (.db): Replaces the existing database with the uploaded one.
- CSV File (.csv): Imports data from the CSV into the existing database.
Args:
database_path (str): The path to the pickle database file.
database_callback (callable, optional): A function to call after a successful database upload.
It receives the uploaded database path as its argument.
csv_callback (callable, optional): A function to call after a successful CSV upload. It receives the uploaded CSV path,
the database path, and the debug flag as its arguments.
debug (bool, optional): If True, print debugging information. Defaults to False.
Returns:
Tuple[str, str, str, threading.Thread, Callable[[], None]]: A tuple containing:
- file_name (str): The name of the database file.
- download_url (str): The URL to download the database file.
- upload_url (str): The URL to access the file upload form.
- server_thread (threading.Thread): The thread running the server.
- shutdown_server (Callable[[], None]): A function to gracefully shut down the server.
Example:
_, download_url, upload_url, server_thread, shutdown_server = start_file_server("zakat.db")
print(f"Download database: {download_url}")
print(f"Upload files: {upload_url}")
server_thread.start()
# ... later ...
shutdown_server()
"""
file_uuid = uuid.uuid4()
file_name = os.path.basename(database_path)
Expand Down Expand Up @@ -113,7 +177,9 @@ def do_POST(self):

try:
# 6. Verify database file
ZakatTracker(db_path=file_path)
# ZakatTracker(db_path=file_path) # FATAL, Circular Imports Error
if database_callback is not None:
database_callback(file_path)

# 7. Copy database into the original path
shutil.copy2(file_path, database_path)
Expand All @@ -124,14 +190,17 @@ def do_POST(self):
case FileType.CSV.value:
# 6. Verify CSV file
try:
x = ZakatTracker(db_path=database_path)
result = x.import_csv(file_path, debug=debug)
if debug:
print(f'CSV imported: {result}')
if len(result[2]) != 0:
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps(result).encode())
# x = ZakatTracker(db_path=database_path) # FATAL, Circular Imports Error
# result = x.import_csv(file_path, debug=debug)
if csv_callback is not None:
result = csv_callback(file_path, database_path, debug)
if debug:
print(f'CSV imported: {result}')
if len(result[2]) != 0:
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps(result).encode())
return
except Exception as e:
self.send_error(400, str(e))
return
Expand All @@ -149,15 +218,27 @@ def shutdown_server():
httpd.server_close() # Close the socket
server_thread.join() # Wait for the thread to finish

server_thread.start()

return file_name, download_url, upload_url, shutdown_server
return file_name, download_url, upload_url, server_thread, shutdown_server


def main():
from zakat_tracker import ZakatTracker, Action # SAFE Circular Imports
# Example usage (replace with your file path)
file_to_share = "your_file.txt" # Or any other file type
file_name, download_url, upload_url, shutdown_server = start_file_server(file_to_share, debug=True)
file_to_share = f"{uuid.uuid4()}.pickle" # Or any other file type

def database_callback(file_path):
ZakatTracker(db_path=file_path)

def csv_callback(file_path, database_path, debug):
x = ZakatTracker(db_path=database_path)
return x.import_csv(file_path, debug=debug)

file_name, download_url, upload_url, server_thread, shutdown_server = start_file_server(
file_to_share,
database_callback=database_callback,
csv_callback=csv_callback,
debug=True,
)

print(f"\nTo download '{file_name}', use this URL:")
print(download_url)
Expand All @@ -166,8 +247,11 @@ def main():
print(upload_url)
print("(The uploaded file will replace the existing one.)")

input("\nPress Enter to stop the server...")
print("\nString the server...")
server_thread.start()
print("The server started.")

input("\nPress Enter to stop the server...")
shutdown_server()


Expand Down
21 changes: 11 additions & 10 deletions zakat/zakat_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ def transfer(self, amount: int, from_account: str, to_account: str, desc: str =
ValueError: The log transaction happened again in the same nanosecond time.
"""
if from_account == to_account:
raise ValueError(f'Transfer to the same account is forbidden. {to_account}')
raise ValueError(f'Transfer to the same account is forbidden. {to_account}')
if amount <= 0:
return []
if created is None:
Expand Down Expand Up @@ -1533,7 +1533,8 @@ def generate_random_date(start_date: datetime.datetime, end_date: datetime.datet
return start_date + datetime.timedelta(days=random_number_of_days)

@staticmethod
def generate_random_csv_file(path: str = "data.csv", count: int = 1000, with_rate: bool = False, debug: bool = False) -> int:
def generate_random_csv_file(path: str = "data.csv", count: int = 1000, with_rate: bool = False,
debug: bool = False) -> int:
"""
Generate a random CSV file with specified parameters.
Expand Down Expand Up @@ -1563,7 +1564,7 @@ def generate_random_csv_file(path: str = "data.csv", count: int = 1000, with_rat
value *= -1
row = [account, desc, value, date]
if with_rate:
rate = random.randint(1,100) * 0.12
rate = random.randint(1, 100) * 0.12
if debug:
print('before-append', row)
row.append(rate)
Expand Down Expand Up @@ -1766,12 +1767,12 @@ def test(self, debug: bool = False) -> bool:

# Same account transfer
for x in [1, 'a', True, 1.8, None]:
failed = False
try:
self.transfer(1, x, x, 'same-account', debug= debug)
except:
failed = True
assert failed is True
failed = False
try:
self.transfer(1, x, x, 'same-account', debug=debug)
except:
failed = True
assert failed is True

# Always preserve box age during transfer

Expand Down Expand Up @@ -2404,4 +2405,4 @@ def main():


if __name__ == "__main__":
main()
main()

0 comments on commit 30b2f2a

Please sign in to comment.