File size: 10,840 Bytes
985c397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2025 sliptonic <shopinthewoods@gmail.com>               *
# *                                                                         *
# *   This file is part of FreeCAD.                                         *
# *                                                                         *
# *   FreeCAD is free software: you can redistribute it and/or modify it    *
# *   under the terms of the GNU Lesser General Public License as           *
# *   published by the Free Software Foundation, either version 2.1 of the  *
# *   License, or (at your option) any later version.                       *
# *                                                                         *
# *   FreeCAD is distributed in the hope that it will be useful, but        *
# *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
# *   Lesser General Public License for more details.                       *
# *                                                                         *
# *   You should have received a copy of the GNU Lesser General Public      *
# *   License along with FreeCAD. If not, see                               *
# *   <https://www.gnu.org/licenses/>.                                      *
# *                                                                         *
# ***************************************************************************
"""
CAM Asset Migration Module

Handles migration of CAM assets during FreeCAD version upgrades.
"""

import FreeCAD
import Path
import Path.Preferences
import pathlib
import os
import glob
from ..assets.ui import AssetOpenDialog
from ..camassets import cam_assets
from ..library.serializers import all_serializers as library_serializers
from ..library.models import Library

# Logging setup - same pattern as Job.py
if False:
    Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
    Path.Log.trackModule(Path.Log.thisModule())
else:
    Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())

if FreeCAD.GuiUp:
    import FreeCADGui
    from PySide.QtWidgets import QApplication, QMessageBox
    from PySide.QtCore import Qt


class CAMAssetMigrator:
    """
    Handles migration of CAM assets during FreeCAD version upgrades.

    This class provides functionality to:
    - Check if migration is needed for custom CAM asset locations
    - Offer migration to users through a dialog
    - Perform the actual asset migration with versioned directories
    """

    def __init__(self):
        self.pref_group_path = "User parameter:BaseApp/Preferences/Mod/CAM/Migration"

    def check_migration_needed(self):
        self.check_asset_location()
        self.check_tool_library_workdir()

    def check_asset_location(self):
        """
        Check if CAM asset migration is needed for version upgrade.

        This method determines if the current CAM assets are stored in a custom
        location outside the default user data directory and if migration has
        not been offered for the current FreeCAD version.
        """
        Path.Log.debug("Starting CAM asset migration check")

        try:
            # Get current directories
            user_app_data_dir = FreeCAD.getUserAppDataDir()
            user_app_data_path = pathlib.Path(user_app_data_dir)
            Path.Log.debug(f"User app data directory: {user_app_data_dir}")

            # Get the current CAM asset path (may be naked or versioned)
            current_asset_path = Path.Preferences.getAssetPath()
            current_asset_pathlib = pathlib.Path(current_asset_path)
            Path.Log.debug(f"Current CAM asset path: {current_asset_path}")

            # Only migrate if CamAssets is outside the standard user data directory
            if current_asset_pathlib.is_relative_to(user_app_data_path):
                Path.Log.debug("CamAssets is in default location, no custom migration needed")
                return

            # Check if migration has already been offered for this version
            if self.has_migration_been_offered():
                Path.Log.debug("Migration has already been offered for this version, skipping")
                return

            # Determine the base path (naked path without version)
            if FreeCAD.ApplicationDirectories.isVersionedPath(str(current_asset_path)):
                # Check if we're already using the current version
                if FreeCAD.ApplicationDirectories.usingCurrentVersionConfig(
                    str(current_asset_path)
                ):
                    Path.Log.debug("Already using current version, no migration needed")
                    return

            Path.Log.info("Asset relocation is needed and should be offered")
            if self._offer_asset_relocation():
                self._migrate_assets(str(current_asset_path))
            return

        except Exception as e:
            Path.Log.error(f"Error checking CAM asset migration: {e}")
            import traceback

            Path.Log.info(f"Full traceback: {traceback.format_exc()}")
            return

    def check_tool_library_workdir(self):
        workdir_str = "LastPathToolLibrary"
        migrated_str = "Migrated" + workdir_str
        workdir = Path.Preferences.preferences().GetString(workdir_str)
        migrated_dir = Path.Preferences.preferences().GetString(migrated_str)
        Path.Log.debug(f"workdir: {workdir}, migrated: {migrated_dir}")
        if workdir and not migrated_dir:
            # Look for tool libraries to import
            if os.path.isdir(workdir):
                libraries = [f for f in glob.glob(workdir + os.path.sep + "*.fctl")]
                libraries.sort()
                if len(libraries):
                    # Migrate libraries, automatically and silently
                    Path.Log.info("Migrating tool libraries into CAM assets")
                    for library in libraries:
                        Path.Log.info("Migrating " + library)
                        import_dialog = AssetOpenDialog(
                            cam_assets,
                            asset_class=Library,
                            serializers=library_serializers,
                            parent=None,
                        )
                        asset = import_dialog.deserialize_file(pathlib.Path(library), quiet=True)
                        if asset:
                            cam_assets.add(asset)

                    # Mark directory as migrated
                    Path.Preferences.preferences().SetString(migrated_str, workdir)

    def _offer_asset_relocation(self):
        """
        Present asset relocation dialog to user.

        Returns:
            bool: True if user accepted relocation, False otherwise
        """
        # Get current version info
        major = int(FreeCAD.ConfigGet("BuildVersionMajor"))
        minor = int(FreeCAD.ConfigGet("BuildVersionMinor"))
        current_version = FreeCAD.ApplicationDirectories.versionStringForPath(major, minor)

        # Get current asset path for display
        current_asset_path = Path.Preferences.getAssetPath()

        Path.Log.debug(f"Offering asset relocation to user for version {current_version}")

        if not FreeCAD.GuiUp:
            Path.Log.debug("GUI not available, skipping migration offer")
            return False

        msg = (
            f"FreeCAD has been upgraded to version {current_version}.\n\n"
            f"Your CAM assets are stored in a custom location:\n{current_asset_path}\n\n"
            "Would you like to migrate your CAM assets to a versioned directory "
            "to preserve them during future upgrades?\n\n"
            "This will copy your assets to a new directory."
        )

        Path.Log.debug("Showing asset relocation dialog to user")

        reply = QMessageBox.question(
            None, "CAM Asset Migration", msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes
        )

        # Record that we offered migration for this version
        pref_group = FreeCAD.ParamGet(self.pref_group_path)
        offered_versions = pref_group.GetString("OfferedToMigrateCAMAssets", "")
        known_versions = set(offered_versions.split(",")) if offered_versions else set()
        known_versions.add(current_version)
        pref_group.SetString("OfferedToMigrateCAMAssets", ",".join(known_versions))
        Path.Log.debug(f"Updated offered versions: {known_versions}")

        if reply == QMessageBox.Yes:
            Path.Log.info("User accepted migration, starting asset migration")
            return True
        else:
            Path.Log.info("User declined migration")
            return False

    def _migrate_assets(self, source_path):
        """
        Perform actual directory copying and preference updates.

        Args:
            source_path: Current CAM asset directory path
        """
        Path.Log.info(f"Starting asset migration from {source_path}")

        try:
            FreeCAD.ApplicationDirectories.migrateAllPaths([source_path])
            Path.Log.info(
                "Migration complete - preferences will be handled automatically by the system"
            )

            if FreeCAD.GuiUp:
                QMessageBox.information(
                    None,
                    "Migration Complete",
                    f"CAM assets have been migrated from:\n{source_path}\n\n"
                    "The system will automatically handle preference updates.",
                )

        except Exception as e:
            error_msg = f"Failed to migrate CAM assets: {e}"
            Path.Log.error(error_msg)
            import traceback

            Path.Log.debug(f"Migration error traceback: {traceback.format_exc()}")
            if FreeCAD.GuiUp:
                QMessageBox.critical(None, "Migration Failed", error_msg)

    def has_migration_been_offered(self):
        """
        Check if migration has been offered for current version.

        Returns:
            bool: True if migration was offered for this version
        """

        pref_group = FreeCAD.ParamGet(self.pref_group_path)
        major = int(FreeCAD.ConfigGet("BuildVersionMajor"))
        minor = int(FreeCAD.ConfigGet("BuildVersionMinor"))

        current_version_string = FreeCAD.ApplicationDirectories.versionStringForPath(major, minor)
        offered_versions = pref_group.GetString("OfferedToMigrateCAMAssets", "")
        known_versions = set(offered_versions.split(",")) if offered_versions else set()
        return current_version_string in known_versions