Search code examples
pythonjsondatabase

python + json file saving


The issue is saving data in json files lets say it saves like this:

{"discordid1": {"username": "discorduser1", "roblox_username": "robloxuser1"}, "discordid2": {"username": "discorduser2", "roblox_username": "robloxuser2"}, "discordid3": {"username": "discorduser3", "roblox_username": "robloxuser3"}} thats whats its seen now when i do command and after some time (few hrs) it shows only 2 users and points for that 1 missing user dissapear (another json file)

COOLDOWNS_FILE = 'cooldowns.json'
SECOND_COOLDOWNS_FILE = 'second_cooldowns.json'
USER_LOGS_FILE = "user_logs.json"
USER_REGISTRATION_FILE = "user_registration.json"

def load_json_file(file_path):
    try:
        with open(file_path, 'r') as f:
            return json.load(f)
    except FileNotFoundError:
        return {}
    except json.JSONDecodeError:
        print(f"Error loading {file_path}: file is empty or invalid JSON")
        return {}

def save_json_file(data, file_path):
    with open(file_path, "w") as f:
        json.dump(data, f, indent=4)

def load_cooldowns(file_path):
    if not os.path.exists(file_path):
        return {}
    try:
        with open(file_path, "r") as f:
            data = json.load(f)
        data_to_return = {}
        for user_id, cooldown_time in data.items():
            data_to_return[user_id] = dt.datetime.fromisoformat(cooldown_time.replace("Z", "+00:00"))
        return data_to_return
    except json.JSONDecodeError:
        return {}

def save_cooldowns(cooldowns):
    data_to_save = {}
    for user_id, cooldown_time in cooldowns.items():
        data_to_save[user_id] = cooldown_time.isoformat()
    with open("cooldowns.json", "w") as f:
        json.dump(data_to_save, f, indent=4)

def load_second_cooldowns():
    if not os.path.exists(SECOND_COOLDOWNS_FILE):
        return {}
    try:
        with open(SECOND_COOLDOWNS_FILE, "r") as f:
            data = json.load(f)
        data_to_return = {}
        for user_id, cooldown_time in data.items():
            data_to_return[user_id] = dt.datetime.fromisoformat(cooldown_time)
        return data_to_return
    except json.JSONDecodeError:
        return {}

def save_second_cooldowns(second_cooldowns):
    data_to_save = {}
    for user_id, cooldown_time in second_cooldowns.items():
        data_to_save[user_id] = cooldown_time.isoformat()
    with open(SECOND_COOLDOWNS_FILE, "w") as f:
        json.dump(data_to_save, f, indent=4)

cooldowns = load_cooldowns(COOLDOWNS_FILE)
second_cooldowns = load_second_cooldowns()

and thats how it is saving files

i also tried append method but no luck. it shouldnt reset points or users. in leaderboard they all show but after sometime they dissapear and their points are reset that shouldnt happen. any help would be much appriciated

there also adding this code where and how all points are saved

@bot.command(name='update')
async def update(ctx, *points: str):
    user_id = str(ctx.author.id)
    user_logs = load_json_file(USER_LOGS_FILE)
    if not user_logs:  # Check if user_logs is empty
        user_logs = {}

    if user_id not in user_logs:
        user_logs[user_id] = {"username": ctx.author.name, "roblox_username": "", "points": []}

    if user_id in cooldowns:
        cooldown_time = cooldowns[user_id]
        if datetime.now() < cooldown_time:
            time_left = cooldown_time - datetime.now()
            hours, remainder = divmod(time_left.seconds, 3600)
            minutes, seconds = divmod(remainder, 60)
            await ctx.reply(f"You are on cooldown. You can use this command again in {hours} hours, {minutes} minutes, and {seconds} seconds.")
            return

    user_registrations = load_json_file(USER_REGISTRATION_FILE)
    user_logs = load_json_file(USER_LOGS_FILE)

    if user_id not in user_registrations:
        embed = discord.Embed(title="Error", color=0xff0000)
        embed.add_field(name='Error', value='You must register your Roblox username first using `?register <roblox_username>`.', inline=False)
        await ctx.reply(embed=embed)
        return

    if not points:
        embed = discord.Embed(title="Error", color=0xff0000)
        embed.add_field(name='Error', value='Please specify at least one point to add.', inline=False)
        await ctx.reply(embed=embed)
        return

    roblox_username = user_registrations[user_id]["roblox_username"]

    if user_id not in user_logs:
        user_logs[user_id] = {"username": ctx.author.name, "roblox_username": roblox_username, "points": []}

    embed = discord.Embed(title="GLUM - Points", color=0xb0fcff)
    points_added = []

    for point in points:
        # Convert the point to an integer
        if point.lower().endswith("k"):
            point = int(float(point[:-1]) * 1000)
        elif point.lower().endswith("m"):
            point = int(float(point[:-1]) * 1000000)
        elif point.lower().endswith("b"):
            point = int(float(point[:-1]) * 1000000000)
        else:
            point = int(point)

        # Get the highest point the user has logged so far
        if user_logs[user_id]["points"]:
            last_points_entry = user_logs[user_id]["points"][-1]
            last_points = int(last_points_entry.split(": ")[2])  # Extract the points from the last entry
        else:
            last_points = 0

        # Check if new points are higher than the last points
        if point <= last_points:
            embed.add_field(name='Error', value=f'The point `{point}` is not higher than your last point `{last_points}`.', inline=False)
       
            continue

        # Add the new point to the user's logs
        user_logs[user_id]["points"].append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: point{len(user_logs[user_id]['points']) + 1}: {point}")
        points_added.append(point)

    with open(USER_LOGS_FILE, 'w') as f:
        json.dump(user_logs, f)

    if points_added:
        embed.add_field(name='Success', value=f'Added points: {", ".join(map(str, points_added))}', inline=False)
    else:
        embed.add_field(name='Error', value='No valid points were provided.', inline=False)


    await ctx.reply(embed=embed)


# Add a new cooldown
    cooldowns[user_id] = datetime.now() + timedelta(hours=6)
    save_cooldowns(cooldowns)

    # Wait for the cooldown to finish
    await asyncio.sleep(21600)

    # Send a DM to the user
    user = bot.get_user(int(user_id))
    if user_id in user_logs:

            if user_logs[user_id].get("last_message", "") == "1":
                await user.send("Please update your points and send image proof in channel")
                user_logs[user_id]["last_message"] = "2"
            else:
                await user.send("Please update your points")
                user_logs[user_id]["last_message"] = "1"
            second_cooldowns[user_id] = datetime.now() + timedelta(minutes=1)
            save_second_cooldowns()
            with open(USER_LOGS_FILE, 'w') as f:
                json.dump(user_logs, f)

    else:
        await user.send("Please update your points")
        user_logs[user_id]["last_message"] = "Please update your points"
        second_cooldowns[user_id] = datetime.now() + timedelta(minutes=1)
        save_second_cooldowns()
        with open(USER_LOGS_FILE, 'w') as f:
            json.dump(user_logs, f)

Solution

  • Here’s what we can do to fix the problem:

    Save data to a temporary file first, then rename it. This way, you won't lose data if something goes wrong while writing. If your program uses multiple threads, make sure they don’t mess with the files at the same time. Catch and handle errors more gracefully to avoid data loss.

    Improved code should be something like this:

    import json
    import os
    import datetime as dt
    import threading
    
    # File paths
    COOLDOWNS_FILE = 'cooldowns.json'
    SECOND_COOLDOWNS_FILE = 'second_cooldowns.json'
    USER_LOGS_FILE = "user_logs.json"
    USER_REGISTRATION_FILE = "user_registration.json"
    
    # Lock for safety when using threads
    lock = threading.Lock()
    
    def load_json_file(file_path):
        """Load data from a JSON file."""
        with lock:
            try:
                if not os.path.exists(file_path):
                    return {}
                with open(file_path, 'r') as f:
                    return json.load(f)
            except (FileNotFoundError, json.JSONDecodeError):
                return {}
    
    def save_json_file(data, file_path):
        """Save data to a JSON file safely."""
        with lock:
            temp_file_path = file_path + '.tmp'
            with open(temp_file_path, "w") as f:
                json.dump(data, f, indent=4)
            os.replace(temp_file_path, file_path)
    
    def load_cooldowns(file_path):
        """Load cooldown times from a JSON file."""
        data = load_json_file(file_path)
        data_to_return = {}
        for user_id, cooldown_time in data.items():
            data_to_return[user_id] = dt.datetime.fromisoformat(cooldown_time.replace("Z", "+00:00"))
        return data_to_return
    
    def save_cooldowns(cooldowns):
        """Save cooldown times to a JSON file."""
        data_to_save = {user_id: cooldown_time.isoformat() for user_id, cooldown_time in cooldowns.items()}
        save_json_file(data_to_save, COOLDOWNS_FILE)
    
    def load_second_cooldowns():
        """Load second cooldown times from a JSON file."""
        data = load_json_file(SECOND_COOLDOWNS_FILE)
        data_to_return = {}
        for user_id, cooldown_time in data.items():
            data_to_return[user_id] = dt.datetime.fromisoformat(cooldown_time)
        return data_to_return
    
    def save_second_cooldowns(second_cooldowns):
        """Save second cooldown times to a JSON file."""
        data_to_save = {user_id: cooldown_time.isoformat() for user_id, cooldown_time in second_cooldowns.items()}
        save_json_file(data_to_save, SECOND_COOLDOWNS_FILE)
    
    # Load initial data
    cooldowns = load_cooldowns(COOLDOWNS_FILE)
    second_cooldowns = load_second_cooldowns()