def delete(self, href=None): if href is None: # Delete the collection parent_dir = os.path.dirname(self._filesystem_path) try: os.rmdir(self._filesystem_path) except OSError: with TemporaryDirectory( prefix=".Radicale.tmp-", dir=parent_dir) as tmp: os.rename(self._filesystem_path, os.path.join( tmp, os.path.basename(self._filesystem_path))) self._sync_directory(parent_dir) else: self._sync_directory(parent_dir) else: # Delete an item if not pathutils.is_safe_filesystem_path_component(href): raise pathutils.UnsafePathError(href) path = pathutils.path_to_filesystem(self._filesystem_path, href) if not os.path.isfile(path): raise storage.ComponentNotFoundError(href) os.remove(path) self._sync_directory(os.path.dirname(path)) # Track the change self._update_history_etag(href, None) self._clean_history()
def move(self, item, to_collection, to_href): if not pathutils.is_safe_filesystem_path_component(to_href): raise pathutils.UnsafePathError(to_href) os.replace( pathutils.path_to_filesystem(item.collection._filesystem_path, item.href), pathutils.path_to_filesystem(to_collection._filesystem_path, to_href)) self._sync_directory(to_collection._filesystem_path) if item.collection._filesystem_path != to_collection._filesystem_path: self._sync_directory(item.collection._filesystem_path) # Move the item cache entry cache_folder = os.path.join(item.collection._filesystem_path, ".Radicale.cache", "item") to_cache_folder = os.path.join(to_collection._filesystem_path, ".Radicale.cache", "item") self._makedirs_synced(to_cache_folder) try: os.replace(os.path.join(cache_folder, item.href), os.path.join(to_cache_folder, to_href)) except FileNotFoundError: pass else: self._makedirs_synced(to_cache_folder) if cache_folder != to_cache_folder: self._makedirs_synced(cache_folder) # Track the change to_collection._update_history_etag(to_href, item) item.collection._update_history_etag(item.href, None) to_collection._clean_history() if item.collection._filesystem_path != to_collection._filesystem_path: item.collection._clean_history()
def _upload_all_nonatomic(self, items, suffix=""): """Upload a new set of items. This takes a list of vobject items and uploads them nonatomic and without existence checks. """ cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache", "item") self._storage._makedirs_synced(cache_folder) hrefs = set() for item in items: uid = item.uid try: cache_content = self._item_cache_content(item) except Exception as e: raise ValueError( "Failed to store item %r in temporary collection %r: %s" % (uid, self.path, e)) from e href_candidate_funtions = [] if os.name in ("nt", "posix"): href_candidate_funtions.append(lambda: uid if uid.lower( ).endswith(suffix.lower()) else uid + suffix) href_candidate_funtions.extend( (lambda: radicale_item.get_etag(uid).strip('"') + suffix, lambda: radicale_item.find_available_uid( hrefs.__contains__, suffix))) href = f = None while href_candidate_funtions: href = href_candidate_funtions.pop(0)() if href in hrefs: continue if not pathutils.is_safe_filesystem_path_component(href): if not href_candidate_funtions: raise pathutils.UnsafePathError(href) continue try: f = open(pathutils.path_to_filesystem( self._filesystem_path, href), "w", newline="", encoding=self._encoding) break except OSError as e: if href_candidate_funtions and ( os.name == "posix" and e.errno == 22 or os.name == "nt" and e.errno == 123): continue raise with f: f.write(item.serialize()) f.flush() self._storage._fsync(f) hrefs.add(href) with open(os.path.join(cache_folder, href), "wb") as f: pickle.dump(cache_content, f) f.flush() self._storage._fsync(f) self._storage._sync_directory(cache_folder) self._storage._sync_directory(self._filesystem_path)
def upload(self, href, item): if not pathutils.is_safe_filesystem_path_component(href): raise pathutils.UnsafePathError(href) try: self._store_item_cache(href, item) except Exception as e: raise ValueError("Failed to store item %r in collection %r: %s" % (href, self.path, e)) from e path = pathutils.path_to_filesystem(self._filesystem_path, href) with self._atomic_write(path, newline="") as fd: fd.write(item.serialize()) # Clean the cache after the actual item is stored, or the cache entry # will be removed again. self._clean_item_cache() # Track the change self._update_history_etag(href, item) self._clean_history() return self._get(href, verify_href=False)
def replace_fn(source, target): nonlocal href while href_candidates: href = href_candidates.pop(0)() if href in hrefs: continue if not pathutils.is_safe_filesystem_path_component(href): if not href_candidates: raise pathutils.UnsafePathError(href) continue try: return os.replace(source, pathutils.path_to_filesystem( self._filesystem_path, href)) except OSError as e: if href_candidates and ( os.name == "posix" and e.errno == 22 or os.name == "nt" and e.errno == 123): continue raise
def _get(self, href, verify_href=True): if verify_href: try: if not pathutils.is_safe_filesystem_path_component(href): raise pathutils.UnsafePathError(href) path = pathutils.path_to_filesystem(self._filesystem_path, href) except ValueError as e: logger.debug( "Can't translate name %r safely to filesystem in %r: %s", href, self.path, e, exc_info=True) return None else: path = os.path.join(self._filesystem_path, href) try: with open(path, "rb") as f: raw_text = f.read() except (FileNotFoundError, IsADirectoryError): return None except PermissionError: # Windows raises ``PermissionError`` when ``path`` is a directory if (os.name == "nt" and os.path.isdir(path) and os.access(path, os.R_OK)): return None raise # The hash of the component in the file system. This is used to check, # if the entry in the cache is still valid. input_hash = self._item_cache_hash(raw_text) cache_hash, uid, etag, text, name, tag, start, end = \ self._load_item_cache(href, input_hash) if input_hash != cache_hash: with self._acquire_cache_lock("item"): # Lock the item cache to prevent multpile processes from # generating the same data in parallel. # This improves the performance for multiple requests. if self._lock.locked == "r": # Check if another process created the file in the meantime cache_hash, uid, etag, text, name, tag, start, end = \ self._load_item_cache(href, input_hash) if input_hash != cache_hash: try: vobject_items = tuple( vobject.readComponents( raw_text.decode(self._encoding))) radicale_item.check_and_sanitize_items( vobject_items, tag=self.get_meta("tag")) vobject_item, = vobject_items temp_item = radicale_item.Item( collection=self, vobject_item=vobject_item) cache_hash, uid, etag, text, name, tag, start, end = \ self._store_item_cache( href, temp_item, input_hash) except Exception as e: raise RuntimeError("Failed to load item %r in %r: %s" % (href, self.path, e)) from e # Clean cache entries once after the data in the file # system was edited externally. if not self._item_cache_cleaned: self._item_cache_cleaned = True self._clean_item_cache() last_modified = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(os.path.getmtime(path))) # Don't keep reference to ``vobject_item``, because it requires a lot # of memory. return radicale_item.Item(collection=self, href=href, last_modified=last_modified, etag=etag, text=text, uid=uid, name=name, component_name=tag, time_range=(start, end))