Repository: biolab/orange3

Issue:
Datasets widget settings are incompatible between Windows and Linux/Mac
**What's wrong?**
Here are two workflows: [datasets-platform.zip](https://github.com/biolab/orange3/files/12608329/datasets-platform.zip).  Both were saved with "adult" selected and, in both, Datasets should output adult when opened.

On Windows, only the Windows workflow will work. On Linux (and probably Mac), only the other one will.

The problem is how the selected dataset is saved. Windows saves it as `'selected_id': 'core\\adult.tab'` while Linux saves `'selected_id': 'core/adult.tab'`.


Discussion:

Resolving patch:
diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py
--- a/Orange/widgets/data/owdatasets.py
+++ b/Orange/widgets/data/owdatasets.py
@@ -26,7 +26,8 @@
 
 import Orange.data
 from Orange.misc.environ import data_dir
-from Orange.widgets import settings, gui
+from Orange.widgets import  gui
+from Orange.widgets.settings import Setting
 from Orange.widgets.utils.signals import Output
 from Orange.widgets.utils.widgetpreview import WidgetPreview
 from Orange.widgets.widget import OWWidget, Msg
@@ -203,12 +204,12 @@ class Outputs:
         data = Output("Data", Orange.data.Table)
 
     #: Selected dataset id
-    selected_id = settings.Setting(None)   # type: Optional[str]
-    language = settings.Setting(DEFAULT_LANG)
+    selected_id = Setting(None)   # type: Optional[str]
+    language = Setting(DEFAULT_LANG)
 
     #: main area splitter state
-    splitter_state = settings.Setting(b'')  # type: bytes
-    header_state = settings.Setting(b'')    # type: bytes
+    splitter_state = Setting(b'')  # type: bytes
+    header_state = Setting(b'')    # type: bytes
 
     def __init__(self):
         super().__init__()
@@ -408,7 +409,8 @@ def update_model(self):
             row = [item1, item2, item3, item4, item5, item6, item7]
             model.appendRow(row)
 
-            if os.path.join(*file_path) == self.selected_id:
+            # for settings do not use os.path.join (Windows separator is different)
+            if "/".join(file_path) == self.selected_id:
                 current_index = i
 
         return model, current_index
@@ -524,7 +526,8 @@ def __on_selection(self):
             di = current.data(Qt.UserRole)
             text = description_html(di)
             self.descriptionlabel.setText(text)
-            self.selected_id = os.path.join(di.prefix, di.filename)
+            # for settings do not use os.path.join (Windows separator is different)
+            self.selected_id = "/".join(di.file_path)
         else:
             self.descriptionlabel.setText("")
             self.selected_id = None
@@ -640,6 +643,12 @@ def load_and_output(self, path):
     def load_data(path):
         return Orange.data.Table(path)
 
+    @classmethod
+    def migrate_settings(cls, settings, _):
+        # until including 3.36.0 selected dataset was saved with \ on Windows
+        if "selected_id" in settings and isinstance(settings["selected_id"], str):
+            settings["selected_id"] = settings["selected_id"].replace("\\", "/")
+
 
 class FutureWatcher(QObject):
     done = Signal(object)


Test patch:
diff --git a/Orange/widgets/data/tests/test_owdatasets.py b/Orange/widgets/data/tests/test_owdatasets.py
--- a/Orange/widgets/data/tests/test_owdatasets.py
+++ b/Orange/widgets/data/tests/test_owdatasets.py
@@ -96,6 +96,25 @@ def test_download_iris(self):
         # select the only dataset
         sel_type = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
         w.view.selectionModel().select(w.view.model().index(0, 0), sel_type)
+        self.assertEqual(w.selected_id, "core/iris.tab")
+        w.commit()
+        iris = self.get_output(w.Outputs.data, w)
+        self.assertEqual(len(iris), 150)
+
+    @patch("Orange.widgets.data.owdatasets.list_remote",
+           Mock(return_value={('dir1', 'dir2', 'foo.tab'): {}}))
+    @patch("Orange.widgets.data.owdatasets.list_local",
+           Mock(return_value={}))
+    @patch("Orange.widgets.data.owdatasets.ensure_local",
+           Mock(return_value="iris.tab"))
+    @WidgetTest.skipNonEnglish
+    def test_download_multidir(self):
+        w = self.create_widget(OWDataSets)  # type: OWDataSets
+        self.wait_until_stop_blocking(w)
+        # select the only dataset
+        sel_type = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
+        w.view.selectionModel().select(w.view.model().index(0, 0), sel_type)
+        self.assertEqual(w.selected_id, "dir1/dir2/foo.tab")
         w.commit()
         iris = self.get_output(w.Outputs.data, w)
         self.assertEqual(len(iris), 150)
@@ -112,6 +131,23 @@ def test_dir_depth(self):
         self.wait_until_stop_blocking(w)
         self.assertEqual(w.view.model().rowCount(), 2)
 
+    def test_migrate_selected_id(self):
+        settings = {}
+        OWDataSets.migrate_settings(settings, 0)
+        self.assertNotIn("selected_id", settings)
+
+        settings = {"selected_id": None}
+        OWDataSets.migrate_settings(settings, 0)
+        self.assertEqual(settings["selected_id"], None)
+
+        settings = {"selected_id": "dir1\\bar"}
+        OWDataSets.migrate_settings(settings, 0)
+        self.assertEqual(settings["selected_id"], "dir1/bar")
+
+        settings = {"selected_id": "dir1/bar"}
+        OWDataSets.migrate_settings(settings, 0)
+        self.assertEqual(settings["selected_id"], "dir1/bar")
+
 
 if __name__ == "__main__":
     unittest.main()