Repository: holoviz/panel

Issue:
List of bugs related to the FileDownload widget
Fixing #1246 allowed me to dig into the code and better understand the behaviour of the ``FileDownload`` widget. I spotted some bugs that add up to this one #1289. Before submitting a PR, I wanted to list these bugs here.

The first one is quite bad, the last example in the Reference Gallery ([here](https://panel.holoviz.org/reference/widgets/FileDownload.html)) is actually broken. At first selecting a year (here 1970) and downloading the generated data works fine. Doing it a second time with another year seems to work fine but the data that is actually downloaded is the same data again, it hasn't been updated.
![filedownload-broken-gallery](https://user-images.githubusercontent.com/35924738/80242190-3ce9de00-8665-11ea-91e5-7d1d29bb991b.gif)

Here is another (minimal) example to reproduce this bug:
```python
from io import StringIO

opts = pn.widgets.Select(options=["a", "b", "c"])

@pn.depends(opts)
def cb(opts):
    return StringIO(opts)

fd = pn.widgets.FileDownload(callback=cb, filename="data.txt")
pn.Column(opts, fd).show()
```
The data available from the widget will always be the first one downloaded, it won't be updated.

Then there is a series of possibly related bugs. They occur after the widget is clicked and that its data/parameters are updated, the Save window pops up at each change. Also note that the ``disabled`` property isn't part of the documented API (see the [docs](https://panel.holoviz.org/reference/widgets/FileDownload.html)) but the widget inherits it. It doesn't actually disable the widget while this would be a useful feature for such a widget.
![filedownload-broken-ui](https://user-images.githubusercontent.com/35924738/80243476-96eba300-8667-11ea-8c20-cda3cc152dc8.gif)

Here is the code to reproduce that:
```python
from io import StringIO
fd = FD(file=StringIO("data"), filename="data.txt")
pn.Row(fd, fd.controls()).show()
```

I've been working on a PR that tries to tackle these bugs one by one (I wasn't aware of #1289 though). It's a rather slow process, mostly because of my inexistent html/typescript skills, as the root cause of all these bugs seem to arise from there.

@philippjfr  I'll submit what I have done so far at some point during the week-end so that you can check whether I went in the right direction.


Discussion:

Resolving patch:
diff --git a/panel/models/widgets.py b/panel/models/widgets.py
--- a/panel/models/widgets.py
+++ b/panel/models/widgets.py
@@ -122,4 +122,8 @@ class FileDownload(InputWidget):
 
     filename = String(help="""Filename to use on download""")
 
+    _transfers = Int(0, help="""
+    A private property to create and click the link.
+    """)
+
     title = Override(default='')
diff --git a/panel/widgets/misc.py b/panel/widgets/misc.py
--- a/panel/widgets/misc.py
+++ b/panel/widgets/misc.py
@@ -190,6 +190,8 @@ class FileDownload(Widget):
 
     _clicks = param.Integer(default=0)
 
+    _transfers = param.Integer(default=0)
+
     _mime_types = {
         'application': {
             'pdf': 'pdf', 'zip': 'zip'
@@ -230,6 +232,11 @@ def __init__(self, file=None, **params):
     def _update_default(self):
         self._default_label = False
 
+    @param.depends('file', watch=True)
+    def _update_filename(self):
+        if isinstance(self.file, str):
+            self.filename = os.path.basename(self.file)
+
     @param.depends('auto', 'file', 'filename', watch=True)
     def _update_label(self):
         label = 'Download' if self._synced or self.auto else 'Transfer'
@@ -300,3 +307,4 @@ def _transfer(self):
 
         self.param.set_param(data=data, filename=filename)
         self._update_label()
+        self._transfers += 1


Test patch:
diff --git a/panel/tests/widgets/test_misc.py b/panel/tests/widgets/test_misc.py
--- a/panel/tests/widgets/test_misc.py
+++ b/panel/tests/widgets/test_misc.py
@@ -74,6 +74,24 @@ def test_file_download_label():
     assert file_download.label == 'Download abc.py'
 
 
+def test_file_download_filename(tmpdir):
+    file_download = FileDownload()
+
+    filepath = tmpdir.join("foo.txt")
+    filepath.write("content")
+    file_download.file = str(filepath)
+
+    assert file_download.filename == "foo.txt"
+
+    file_download._clicks += 1
+    file_download.file = __file__
+
+    assert file_download.filename == "test_misc.py"
+
+    file_download.file = StringIO("data")
+    assert file_download.filename == "test_misc.py"
+
+
 def test_file_download_file():
     with pytest.raises(ValueError):
         FileDownload(StringIO("data"))
@@ -118,6 +136,17 @@ def cb():
     assert file_download.filename == "cba.py"
     assert file_download.label == "Download cba.py"
 
+
+def test_file_download_transfers():
+    file_download = FileDownload(__file__, embed=True)
+    assert file_download._transfers == 1
+
+    file_download = FileDownload(__file__)
+    assert file_download._transfers == 0
+    file_download._clicks += 1
+    assert file_download._transfers == 1
+
+
 def test_file_download_data():
     file_download = FileDownload(__file__, embed=True)