/src/sink-symlink.cpp

https://github.com/nickbp/ratesync · C++ · 172 lines · 132 code · 19 blank · 21 comment · 36 complexity · 90a8d518a20f9c549589d0262374449d MD5 · raw file

  1. /*
  2. ratesync - Manages songs according their rating metadata.
  3. Copyright (C) 2010 Nicholas Parker
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #include "sink-symlink.h"
  16. #include "config.h"
  17. #include <sstream>
  18. #include <queue>
  19. #include <sys/stat.h>
  20. #include <sys/types.h>
  21. #include <unistd.h>
  22. #include <string.h>
  23. #include <dirent.h>
  24. namespace {
  25. bool check_symlink(const std::string& linkpath, bool show_err = true) {
  26. struct stat sb;
  27. if (stat(linkpath.c_str(), &sb) != 0) {
  28. ratesync::config::error("Unable to stat file %s.",
  29. linkpath.c_str());
  30. return false;
  31. }
  32. if (!S_ISLNK(sb.st_mode)) {
  33. ratesync::config::error("Not a symlink: %s",
  34. linkpath.c_str());
  35. return false;
  36. }
  37. return true;
  38. }
  39. bool scan_rating_subdir(const std::string& subdir, ratesync::rating_t rating,
  40. std::map<ratesync::song_t, ratesync::rating_t>& out_rating) {
  41. std::queue<std::string> dirqueue;
  42. dirqueue.push(subdir);
  43. char symdest_c[1024];
  44. memset(&symdest_c, 0, sizeof(symdest_c));
  45. while (!dirqueue.empty()) {
  46. std::string dir = dirqueue.front();
  47. dirqueue.pop();
  48. DIR* dp = opendir(dir.c_str());
  49. if (dp == NULL) {
  50. ratesync::config::error("Couldn't open directory %s",
  51. dir.c_str());
  52. return false;
  53. }
  54. struct dirent* ep;
  55. while (ep = readdir(dp)) {
  56. //"This is the only field you can count on in all POSIX systems":
  57. ratesync::song_t filepath = dir+ep->d_name;
  58. ratesync::config::debug(filepath.c_str());
  59. struct stat sb;
  60. if (stat(filepath.c_str(), &sb) != 0) {
  61. ratesync::config::error("Unable to stat file %s.",
  62. filepath.c_str());
  63. closedir(dp);
  64. return false;
  65. }
  66. if (S_ISDIR(sb.st_mode)) {
  67. dirqueue.push(filepath+SEP);
  68. } else if (S_ISLNK(sb.st_mode)) {
  69. ssize_t len = readlink(filepath.c_str(), symdest_c, sizeof(symdest_c));
  70. if (len < 0) {
  71. ratesync::config::error("Unable to read symlink %s.",
  72. filepath.c_str());
  73. return false;
  74. }
  75. struct stat lsb;
  76. if (lstat(symdest_c, &lsb) != 0) {//TODO assuming != 0 when dangling
  77. if (unlink(filepath.c_str()) == 0) {
  78. continue;
  79. } else {
  80. ratesync::config::error("Unable to delete dangling symlink %s -> %s.",
  81. filepath.c_str(), symdest_c);
  82. return false;
  83. }
  84. }
  85. std::string symdest(symdest_c);
  86. std::map<ratesync::song_t, ratesync::rating_t>::iterator
  87. iter = out_rating.find(symdest);
  88. if (iter != out_rating.end()) {
  89. ratesync::config::error("Duplicate symlink to same file: %s -> %s",
  90. filepath.c_str(), symdest_c);
  91. continue;
  92. }
  93. out_rating.insert(std::make_pair(symdest, rating));
  94. }
  95. }
  96. closedir(dp);
  97. }
  98. }
  99. }
  100. bool ratesync::sink::Symlink::Get(std::map<song_t,rating_t>& out_rating) {
  101. struct stat sb;
  102. if (stat(symlink_dir.c_str(), &sb) != 0) {//TODO assuming != 0 when doesnt exist
  103. if (mkdir(symlink_dir.c_str(), 0777) == 0) {
  104. //just created dir, assume no symlinks
  105. return true;
  106. } else {
  107. //dont bother with recursive creation
  108. config::error("Unable to create symlink dir: %s", symlink_dir.c_str());
  109. return false;
  110. }
  111. }
  112. if (!scan_rating_subdir(symlink_dir+"unrated"+SEP, UNRATED, out_rating) ||
  113. !scan_rating_subdir(symlink_dir+"1"+SEP, 1, out_rating) ||
  114. !scan_rating_subdir(symlink_dir+"2"+SEP, 2, out_rating) ||
  115. !scan_rating_subdir(symlink_dir+"3"+SEP, 3, out_rating) ||
  116. !scan_rating_subdir(symlink_dir+"4"+SEP, 4, out_rating) ||
  117. !scan_rating_subdir(symlink_dir+"5"+SEP, 5, out_rating)) {
  118. return false;
  119. }
  120. return true;
  121. }
  122. bool ratesync::sink::Symlink::Set(const song_ratings_t& song) {
  123. symlink_t oldpath = link_path(song.path, song.rating_old);
  124. if (check_symlink(oldpath, false) && unlink(oldpath.c_str()) != 0) {
  125. config::error("Unable to delete old symlink: %s", oldpath.c_str());
  126. return false;
  127. }
  128. symlink_t newpath = link_path(song.path, song.rating_new);
  129. if (symlink(song.path.c_str(), newpath.c_str()) != 0) {
  130. config::error("Unable to create symlink: %s", newpath.c_str());
  131. return false;
  132. }
  133. return true;
  134. }
  135. bool ratesync::sink::Symlink::Clear(const song_rating_t& song) {
  136. symlink_t linkpath = link_path(song.path, song.rating);
  137. return check_symlink(linkpath) && (unlink(linkpath.c_str()) == 0);
  138. }
  139. ratesync::sink::Symlink::symlink_t
  140. ratesync::sink::Symlink::link_path(const song_t& song, const rating_t rating) {
  141. std::ostringstream oss;
  142. if (rating == UNRATED) {
  143. oss << symlink_dir << "unrated" << SEP;
  144. } else {
  145. oss << symlink_dir << rating << SEP;
  146. }
  147. // /dir1/music_dir/dir2/file -> dir2/file
  148. oss << song.substr(music_dir.length());
  149. return oss.str();
  150. }