Saves entry in the given directory. Data products will be copied over if not
        residing in that directory.
        'refresh' is a timestamp, passed to renderIndex(), causing all data products OLDER than the specified time to be regenerated.
        'refresh_index', if true, causes index files to be re-rendered unconditionally

def save(self, dirname=None, refresh=0, refresh_index=True, emit_message=True):
        """Saves entry in the given directory. Data products will be copied over if not
        residing in that directory.
        'refresh' is a timestamp, passed to renderIndex(), causing all data products OLDER than the specified time to be regenerated.
        'refresh_index', if true, causes index files to be re-rendered unconditionally
        """
        if not refresh and not self.updated:
            return
        timestr = time.strftime("%Y%m%d-%H%M%S", time.localtime(self.timestamp))
        Purr.progressMessage("Rendering entry for %s" % timestr)
        if dirname:
            self.pathname = pathname = os.path.join(dirname, "%s-%s" %
                                                    (("ignore" if self.ignore else "entry"), timestr))
        elif not self.pathname:
            raise ValueError("Cannot save entry: pathname not specified")
        else:
            pathname = self.pathname
        # set timestamp
        if not self.timestamp:
            self.timestamp = int(time.time())
        # get canonized path to output directory
        pathname = Purr.canonizePath(pathname)
        if not os.path.exists(pathname):
            os.mkdir(pathname)
        # now save content
        # get device of pathname -- need to know whether we move or copy
        devnum = os.stat(pathname).st_dev
        # copy data products as needed
        dprintf(2, "saving entry %s, %d data products\n", pathname, len(self.dps))
        dps = []
        for dp in self.dps:
            # if archived, this indicates a previously saved data product, so ignore it
            # if ignored, no need to save the DP -- but keep it in list
            if dp.archived or dp.ignored:
                dprintf(3, "dp %s is archived or ignored, skipping\n", dp.sourcepath)
                dps.append(dp)
                continue
            # file missing for some reason (perhaps it got removed on us?) skip data product entirely
            if not os.path.exists(dp.sourcepath):
                dprintf(2, "data product %s missing, ignoring\n", dp.sourcepath)
                continue
            Purr.progressMessage("archiving %s" % dp.filename, sub=True)
            # get normalized source and destination paths
            dprintf(2, "data product: %s, rename %s, policy %s\n", dp.sourcepath, dp.filename, dp.policy)
            sourcepath = Purr.canonizePath(dp.sourcepath)
            destname = dp.fullpath = os.path.join(pathname, dp.filename)
            dprintf(2, "data product: %s -> %s\n", sourcepath, destname)
            # does the destination product already exist? skip if same file, else remove
            if os.path.exists(destname):
                if os.path.samefile(destname, sourcepath):
                    dprintf(2, "same file, skipping\n")
                    dp.timestamp = os.path.getmtime(destname)
                    dps.append(dp)
                    continue
                if os.system("/bin/rm -fr '%s'" % destname):
                    print("Error removing %s, which is in the way of %s" % (destname, sourcepath))
                    print("This data product is not saved.")
                    continue
            # for directories, compress with tar
            if os.path.isdir(sourcepath):
                sourcepath = sourcepath.rstrip('/')
                if dp.policy == "copy" or dp.policy.startswith("move"):
                    dprintf(2, "archiving to tgz\n")
                    if os.system("tar zcf '%s' -C '%s' '%s'" % (destname,
                                                                os.path.dirname(sourcepath),
                                                                os.path.basename(sourcepath))):
                        print("Error archiving %s to %s" % (sourcepath, destname))
                        print("This data product is not saved.")
                        continue
                    if dp.policy.startswith("move"):
                        os.system("/bin/rm -fr '%s'" % sourcepath)
            # else just a file
            else:
                # now copy/move it over
                if dp.policy == "copy":
                    dprintf(2, "copying\n")
                    if _copy_update(sourcepath, destname):
                        print("Error copying %s to %s" % (sourcepath, destname))
                        print("This data product is not saved.")
                        continue
                elif dp.policy.startswith('move'):
                    if _move_update(sourcepath, destname):
                        print("Error moving %s to %s" % (sourcepath, destname))
                        print("This data product is not saved.")
                        continue
            # success, set timestamp and append
            dp.timestamp = os.path.getmtime(destname)
            dp.archived = True
            dps.append(dp)
        # reset list of data products
        self.dps = dps
        # now write out content
        self.cached_include = os.path.join(pathname, 'index.include.html')
        self.cached_include_valid = False
        self.index_file = os.path.join(pathname, "index.html")
        self.generateIndex(refresh=refresh, refresh_index=refresh_index and time.time())
        self.updated = False